Merge "Address HCE API review comments." into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index c1a58d2..f67a01b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5355,13 +5355,13 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.Bundle getExtras();
+ method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
method public long getMaxExecutionDelayMillis();
method public long getMinLatencyMillis();
method public int getNetworkCapabilities();
method public android.content.ComponentName getService();
- method public int getTaskId();
method public boolean isPeriodic();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
@@ -5374,7 +5374,7 @@
field public static final int LINEAR = 0; // 0x0
}
- public final class Task.Builder {
+ public static final class Task.Builder {
ctor public Task.Builder(int, android.content.ComponentName);
method public android.app.task.Task build();
method public android.app.task.Task.Builder setBackoffCriteria(long, int);
@@ -5388,8 +5388,9 @@
}
public static abstract interface Task.NetworkType {
- field public static final int ANY = 0; // 0x0
- field public static final int UNMETERED = 1; // 0x1
+ field public static final int ANY = 1; // 0x1
+ field public static final int NONE = 0; // 0x0
+ field public static final int UNMETERED = 2; // 0x2
}
public abstract class TaskManager {
@@ -5398,8 +5399,8 @@
method public abstract void cancelAll();
method public abstract java.util.List<android.app.task.Task> getAllPendingTasks();
method public abstract int schedule(android.app.task.Task);
- field public static final int RESULT_INVALID_PARAMETERS = -1; // 0xffffffff
- field public static final int RESULT_OVER_QUOTA = -2; // 0xfffffffe
+ field public static final int RESULT_FAILURE = 0; // 0x0
+ field public static final int RESULT_SUCCESS = 1; // 0x1
}
public class TaskParams implements android.os.Parcelable {
diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java
index dd184a5..ca4aeb2 100644
--- a/core/java/android/app/task/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -27,10 +27,13 @@
* using the {@link Task.Builder}.
*/
public class Task implements Parcelable {
-
public interface NetworkType {
- public final int ANY = 0;
- public final int UNMETERED = 1;
+ /** Default. */
+ public final int NONE = 0;
+ /** This task requires network connectivity. */
+ public final int ANY = 1;
+ /** This task requires network connectivity that is unmetered. */
+ public final int UNMETERED = 2;
}
/**
@@ -48,6 +51,8 @@
private final ComponentName service;
private final boolean requireCharging;
private final boolean requireDeviceIdle;
+ private final boolean hasEarlyConstraint;
+ private final boolean hasLateConstraint;
private final int networkCapabilities;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
@@ -59,7 +64,7 @@
/**
* Unique task id associated with this class. This is assigned to your task by the scheduler.
*/
- public int getTaskId() {
+ public int getId() {
return taskId;
}
@@ -146,6 +151,24 @@
return backoffPolicy;
}
+ /**
+ * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasEarlyConstraint() {
+ return hasEarlyConstraint;
+ }
+
+ /**
+ * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasLateConstraint() {
+ return hasLateConstraint;
+ }
+
private Task(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
@@ -159,6 +182,8 @@
intervalMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
+ hasEarlyConstraint = in.readInt() == 1;
+ hasLateConstraint = in.readInt() == 1;
}
private Task(Task.Builder b) {
@@ -174,6 +199,8 @@
intervalMillis = b.mIntervalMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
+ hasEarlyConstraint = b.mHasEarlyConstraint;
+ hasLateConstraint = b.mHasLateConstraint;
}
@Override
@@ -195,6 +222,8 @@
out.writeLong(intervalMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
+ out.writeInt(hasEarlyConstraint ? 1 : 0);
+ out.writeInt(hasLateConstraint ? 1 : 0);
}
public static final Creator<Task> CREATOR = new Creator<Task>() {
@@ -212,7 +241,7 @@
/**
* Builder class for constructing {@link Task} objects.
*/
- public final class Builder {
+ public static final class Builder {
private int mTaskId;
private Bundle mExtras;
private ComponentName mTaskService;
@@ -225,6 +254,8 @@
private long mMaxExecutionDelayMillis;
// Periodic parameters.
private boolean mIsPeriodic;
+ private boolean mHasEarlyConstraint;
+ private boolean mHasLateConstraint;
private long mIntervalMillis;
// Back-off parameters.
private long mInitialBackoffMillis = 5000L;
@@ -307,6 +338,7 @@
public Builder setPeriodic(long intervalMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
+ mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
@@ -320,6 +352,7 @@
*/
public Builder setMinimumLatency(long minLatencyMillis) {
mMinLatencyMillis = minLatencyMillis;
+ mHasEarlyConstraint = true;
return this;
}
@@ -332,6 +365,7 @@
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
+ mHasLateConstraint = true;
return this;
}
@@ -360,31 +394,18 @@
* @return The task object to hand to the TaskManager. This object is immutable.
*/
public Task build() {
- // Check that extras bundle only contains primitive types.
- try {
- for (String key : extras.keySet()) {
- Object value = extras.get(key);
- if (value == null) continue;
- if (value instanceof Long) continue;
- if (value instanceof Integer) continue;
- if (value instanceof Boolean) continue;
- if (value instanceof Float) continue;
- if (value instanceof Double) continue;
- if (value instanceof String) continue;
- throw new IllegalArgumentException("Unexpected value type: "
- + value.getClass().getName());
- }
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (RuntimeException exc) {
- throw new IllegalArgumentException("error unparcelling Bundle", exc);
+ if (mExtras == null) {
+ mExtras = Bundle.EMPTY;
+ }
+ if (mTaskId < 0) {
+ throw new IllegalArgumentException("Task id must be greater than 0.");
}
// Check that a deadline was not set on a periodic task.
- if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
+ if (mIsPeriodic && mHasLateConstraint) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic task.");
}
- if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
+ if (mIsPeriodic && mHasEarlyConstraint) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic task");
}
diff --git a/core/java/android/app/task/TaskManager.java b/core/java/android/app/task/TaskManager.java
index 0fbe37d..00f57da 100644
--- a/core/java/android/app/task/TaskManager.java
+++ b/core/java/android/app/task/TaskManager.java
@@ -34,14 +34,13 @@
* if the run-time for your task is too short, or perhaps the system can't resolve the
* requisite {@link TaskService} in your package.
*/
- public static final int RESULT_INVALID_PARAMETERS = -1;
-
+ public static final int RESULT_FAILURE = 0;
/**
* Returned from {@link #schedule(Task)} if this application has made too many requests for
* work over too short a time.
*/
// TODO: Determine if this is necessary.
- public static final int RESULT_OVER_QUOTA = -2;
+ public static final int RESULT_SUCCESS = 1;
/**
* @param task The task you wish scheduled. See
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index cea68d2..2f5b4fe 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -822,8 +822,8 @@
* @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL
* @see #REQUEST_AVAILABLE_CAPABILITIES_DNG
*/
- public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES =
- new Key<Integer>("android.request.availableCapabilities", int.class);
+ public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
+ new Key<int[]>("android.request.availableCapabilities", int[].class);
/**
* <p>A list of all keys that the camera device has available
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index e489e05..64516e6 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -94,11 +94,26 @@
mNetId = netId;
}
+ private void connectToHost(Socket socket, String host, int port) throws IOException {
+ // Lookup addresses only on this Network.
+ InetAddress[] hostAddresses = getAllByName(host);
+ // Try all but last address ignoring exceptions.
+ for (int i = 0; i < hostAddresses.length - 1; i++) {
+ try {
+ socket.connect(new InetSocketAddress(hostAddresses[i], port));
+ return;
+ } catch (IOException e) {
+ }
+ }
+ // Try last address. Do throw exceptions.
+ socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port));
+ }
+
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
Socket socket = createSocket();
socket.bind(new InetSocketAddress(localHost, localPort));
- socket.connect(new InetSocketAddress(host, port));
+ connectToHost(socket, host, port);
return socket;
}
@@ -121,7 +136,7 @@
@Override
public Socket createSocket(String host, int port) throws IOException {
Socket socket = createSocket();
- socket.connect(new InetSocketAddress(host, port));
+ connectToHost(socket, host, port);
return socket;
}
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
index 6dd7c00..64a4c41 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -61,6 +61,7 @@
import com.google.android.gles_jni.EGLImpl;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -106,7 +107,6 @@
private static final String[] VISUALIZERS = {
PROFILE_PROPERTY_VISUALIZE_BARS,
- PROFILE_PROPERTY_VISUALIZE_LINES
};
private static final String[] OVERDRAW = {
@@ -674,7 +674,7 @@
mProfilePaint = null;
if (value) {
- mDebugDataProvider = new DrawPerformanceDataProvider(graphType);
+ mDebugDataProvider = new GraphDataProvider(graphType);
} else {
mDebugDataProvider = null;
}
@@ -742,7 +742,7 @@
}
@Override
- void dumpGfxInfo(PrintWriter pw) {
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
if (mProfileEnabled) {
pw.printf("\n\tDraw\tProcess\tExecute\n");
@@ -763,11 +763,6 @@
}
}
- @Override
- long getFrameCount() {
- return mFrameCount;
- }
-
/**
* Indicates whether this renderer instance can track and update dirty regions.
*/
@@ -1446,7 +1441,18 @@
private static native void nPrepareTree(long displayListPtr);
- class DrawPerformanceDataProvider extends GraphDataProvider {
+ class GraphDataProvider {
+ /**
+ * Draws the graph as bars. Frame elements are stacked on top of
+ * each other.
+ */
+ public static final int GRAPH_TYPE_BARS = 0;
+ /**
+ * Draws the graph as lines. The number of series drawn corresponds
+ * to the number of elements.
+ */
+ public static final int GRAPH_TYPE_LINES = 1;
+
private final int mGraphType;
private int mVerticalUnit;
@@ -1454,11 +1460,10 @@
private int mHorizontalMargin;
private int mThresholdStroke;
- DrawPerformanceDataProvider(int graphType) {
+ public GraphDataProvider(int graphType) {
mGraphType = graphType;
}
- @Override
void prepare(DisplayMetrics metrics) {
final float density = metrics.density;
@@ -1468,64 +1473,52 @@
mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
}
- @Override
int getGraphType() {
return mGraphType;
}
- @Override
int getVerticalUnitSize() {
return mVerticalUnit;
}
- @Override
int getHorizontalUnitSize() {
return mHorizontalUnit;
}
- @Override
int getHorizontaUnitMargin() {
return mHorizontalMargin;
}
- @Override
float[] getData() {
return mProfileData;
}
- @Override
float getThreshold() {
return 16;
}
- @Override
int getFrameCount() {
return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
}
- @Override
int getElementCount() {
return PROFILE_FRAME_DATA_COUNT;
}
- @Override
int getCurrentFrame() {
return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
}
- @Override
void setupGraphPaint(Paint paint, int elementIndex) {
paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
}
- @Override
void setupThresholdPaint(Paint paint) {
paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
paint.setStrokeWidth(mThresholdStroke);
}
- @Override
void setupCurrentFramePaint(Paint paint) {
paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 3c4d83f..60f8ee3 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,13 +17,13 @@
package android.view;
import android.graphics.Bitmap;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.util.DisplayMetrics;
import android.view.Surface.OutOfResourcesException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -61,11 +61,9 @@
* Possible values:
* "true", to enable profiling
* "visual_bars", to enable profiling and visualize the results on screen
- * "visual_lines", to enable profiling and visualize the results on screen
* "false", to disable profiling
*
* @see #PROFILE_PROPERTY_VISUALIZE_BARS
- * @see #PROFILE_PROPERTY_VISUALIZE_LINES
*
* @hide
*/
@@ -80,14 +78,6 @@
public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
/**
- * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
- * value, profiling data will be visualized on screen as a line chart.
- *
- * @hide
- */
- public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines";
-
- /**
* System property used to specify the number of frames to be used
* when doing hardware rendering profiling.
* The default value of this property is #PROFILE_MAX_FRAMES.
@@ -298,16 +288,8 @@
/**
* Outputs extra debugging information in the specified file descriptor.
- * @param pw
*/
- abstract void dumpGfxInfo(PrintWriter pw);
-
- /**
- * Outputs the total number of frames rendered (used for fps calculations)
- *
- * @return the number of frames rendered
- */
- abstract long getFrameCount();
+ abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
/**
* Loads system properties used by the renderer. This method is invoked
@@ -583,98 +565,4 @@
*/
public void notifyFramePending() {
}
-
- /**
- * Describes a series of frames that should be drawn on screen as a graph.
- * Each frame is composed of 1 or more elements.
- */
- abstract class GraphDataProvider {
- /**
- * Draws the graph as bars. Frame elements are stacked on top of
- * each other.
- */
- public static final int GRAPH_TYPE_BARS = 0;
- /**
- * Draws the graph as lines. The number of series drawn corresponds
- * to the number of elements.
- */
- public static final int GRAPH_TYPE_LINES = 1;
-
- /**
- * Returns the type of graph to render.
- *
- * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES}
- */
- abstract int getGraphType();
-
- /**
- * This method is invoked before the graph is drawn. This method
- * can be used to compute sizes, etc.
- *
- * @param metrics The display metrics
- */
- abstract void prepare(DisplayMetrics metrics);
-
- /**
- * @return The size in pixels of a vertical unit.
- */
- abstract int getVerticalUnitSize();
-
- /**
- * @return The size in pixels of a horizontal unit.
- */
- abstract int getHorizontalUnitSize();
-
- /**
- * @return The size in pixels of the margin between horizontal units.
- */
- abstract int getHorizontaUnitMargin();
-
- /**
- * An optional threshold value.
- *
- * @return A value >= 0 to draw the threshold, a negative value
- * to ignore it.
- */
- abstract float getThreshold();
-
- /**
- * The data to draw in the graph. The number of elements in the
- * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}.
- * If a value is negative the following values will be ignored.
- */
- abstract float[] getData();
-
- /**
- * Returns the number of frames to render in the graph.
- */
- abstract int getFrameCount();
-
- /**
- * Returns the number of elements in each frame. This directly affects
- * the number of series drawn in the graph.
- */
- abstract int getElementCount();
-
- /**
- * Returns the current frame, if any. If the returned value is negative
- * the current frame is ignored.
- */
- abstract int getCurrentFrame();
-
- /**
- * Prepares the paint to draw the specified element (or series.)
- */
- abstract void setupGraphPaint(Paint paint, int elementIndex);
-
- /**
- * Prepares the paint to draw the threshold.
- */
- abstract void setupThresholdPaint(Paint paint);
-
- /**
- * Prepares the paint to draw the current frame indicator.
- */
- abstract void setupCurrentFramePaint(Paint paint);
- }
}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index cf125bc..e63829e 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -850,6 +850,13 @@
nOutput(mNativeRenderNode);
}
+ /**
+ * Gets the size of the DisplayList for debug purposes.
+ */
+ public int getDebugSize() {
+ return nGetDebugSize(mNativeRenderNode);
+ }
+
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////
@@ -941,6 +948,7 @@
private static native float nGetPivotX(long renderNode);
private static native float nGetPivotY(long renderNode);
private static native void nOutput(long renderNode);
+ private static native int nGetDebugSize(long renderNode);
///////////////////////////////////////////////////////////////////////////
// Animations
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 1765c43..2a9f7d5 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -405,7 +405,9 @@
// To cancel updates, the easiest thing to do is simply to remove the
// updates listener
if (visibility == VISIBLE) {
- mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ if (mLayer != null) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ }
updateLayerAndInvalidate();
} else {
mSurface.setOnFrameAvailableListener(null);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 9c9a939..cac23a8 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -22,12 +22,14 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.Log;
import android.util.TimeUtils;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -60,11 +62,16 @@
// Needs a ViewRoot invalidate
private static final int SYNC_INVALIDATE_REQUIRED = 0x1;
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ };
+
private int mWidth, mHeight;
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
private Choreographer mChoreographer;
+ private boolean mProfilingEnabled;
ThreadedRenderer(boolean translucent) {
AtlasInitializer.sInstance.init();
@@ -77,6 +84,8 @@
// Setup timing
mChoreographer = Choreographer.getInstance();
nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
+
+ loadSystemProperties();
}
@Override
@@ -166,19 +175,33 @@
}
@Override
- void dumpGfxInfo(PrintWriter pw) {
- // TODO Auto-generated method stub
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+ pw.flush();
+ nDumpProfileInfo(mNativeProxy, fd);
}
- @Override
- long getFrameCount() {
- // TODO Auto-generated method stub
- return 0;
+ private static int search(String[] values, String value) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) return i;
+ }
+ return -1;
+ }
+
+ private static boolean checkIfProfilingRequested() {
+ String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
+ int graphType = search(VISUALIZERS, profiling);
+ return (graphType >= 0) || Boolean.parseBoolean(profiling);
}
@Override
boolean loadSystemProperties() {
- return nLoadSystemProperties(mNativeProxy);
+ boolean changed = nLoadSystemProperties(mNativeProxy);
+ boolean wantProfiling = checkIfProfilingRequested();
+ if (wantProfiling != mProfilingEnabled) {
+ mProfilingEnabled = wantProfiling;
+ changed = true;
+ }
+ return changed;
}
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
@@ -210,14 +233,24 @@
long frameTimeNanos = mChoreographer.getFrameTimeNanos();
attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
+ long recordDuration = 0;
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime();
+ }
+
updateRootDisplayList(view, callbacks);
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime() - recordDuration;
+ }
+
attachInfo.mIgnoreDirtyState = false;
if (dirty == null) {
dirty = NULL_RECT;
}
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
+ recordDuration, view.getResources().getDisplayMetrics().density,
dirty.left, dirty.top, dirty.right, dirty.bottom);
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
@@ -354,7 +387,8 @@
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos,
+ private static native int nSyncAndDrawFrame(long nativeProxy,
+ long frameTimeNanos, long recordDuration, float density,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
private static native void nDestroyCanvasAndSurface(long nativeProxy);
@@ -370,4 +404,6 @@
private static native void nFence(long nativeProxy);
private static native void nNotifyFramePending(long nativeProxy);
+
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fc7bf0e..aa06d15 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5329,7 +5329,7 @@
RenderNode renderNode = view.mRenderNode;
info[0]++;
if (renderNode != null) {
- info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */
+ info[1] += renderNode.getDebugSize();
}
if (view instanceof ViewGroup) {
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 96c0ed2..b4779f4 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -430,7 +430,7 @@
HardwareRenderer renderer =
root.getView().mAttachInfo.mHardwareRenderer;
if (renderer != null) {
- renderer.dumpGfxInfo(pw);
+ renderer.dumpGfxInfo(pw, fd);
}
}
@@ -447,11 +447,6 @@
String name = getWindowName(root);
pw.printf(" %s\n %d views, %.2f kB of display lists",
name, info[0], info[1] / 1024.0f);
- HardwareRenderer renderer =
- root.getView().mAttachInfo.mHardwareRenderer;
- if (renderer != null) {
- pw.printf(", %d frames rendered", renderer.getFrameCount());
- }
pw.printf("\n\n");
viewsCount += info[0];
diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
index 550aa6d..624f67c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtilsCache.java
+++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
@@ -28,6 +28,11 @@
*/
public class LockPatternUtilsCache implements ILockSettings {
+ private static final String HAS_LOCK_PATTERN_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPatternCacheKey";
+ private static final String HAS_LOCK_PASSWORD_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPasswordCacheKey";
+
private static LockPatternUtilsCache sInstance;
private final ILockSettings mService;
@@ -109,7 +114,9 @@
@Override
public void setLockPattern(String pattern, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
mService.setLockPattern(pattern, userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null);
}
@Override
@@ -119,7 +126,9 @@
@Override
public void setLockPassword(String password, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
mService.setLockPassword(password, userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null);
}
@Override
@@ -134,12 +143,24 @@
@Override
public boolean havePattern(int userId) throws RemoteException {
- return mService.havePattern(userId);
+ Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePattern(userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result);
+ return result;
}
@Override
public boolean havePassword(int userId) throws RemoteException {
- return mService.havePassword(userId);
+ Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePassword(userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result);
+ return result;
}
@Override
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 041790f..3bab8a2 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -31,9 +31,14 @@
namespace android {
-static jlong FontFamily_create(JNIEnv* env, jobject clazz) {
+static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
#ifdef USE_MINIKIN
- return (jlong)new FontFamily();
+ FontLanguage fontLanguage;
+ if (lang != NULL) {
+ ScopedUtfChars str(env, lang);
+ fontLanguage = FontLanguage(str.c_str(), str.size());
+ }
+ return (jlong)new FontFamily(fontLanguage, variant);
#else
return 0;
#endif
@@ -67,7 +72,7 @@
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gFontFamilyMethods[] = {
- { "nCreateFamily", "()J", (void*)FontFamily_create },
+ { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create },
{ "nUnrefFamily", "(J)V", (void*)FontFamily_unref },
{ "nAddFont", "(JLjava/lang/String;)Z", (void*)FontFamily_addFont },
};
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index ee04d6f..79381ad 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -28,11 +28,17 @@
layout->setFontCollection(resolvedFace->fFontCollection);
FontStyle style = resolvedFace->fStyle;
char css[256];
- sprintf(css, "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d",
+ int off = snprintf(css, sizeof(css),
+ "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d;",
(int)paint->getTextSize(),
style.getWeight() * 100,
style.getItalic() ? "italic" : "normal",
flags);
+ SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
+ off += snprintf(css + off, sizeof(css) - off, " lang: %s;", langString.c_str());
+ SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
+ const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact";
+ off += snprintf(css + off, sizeof(css) - off, " -minikin-variant: %s;", varstr);
layout->setProperties(css);
}
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 867c1b1..26022e0 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -48,6 +48,12 @@
renderNode->output();
}
+static jint android_view_RenderNode_getDebugSize(JNIEnv* env,
+ jobject clazz, jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ return renderNode->getDebugSize();
+}
+
static jlong android_view_RenderNode_create(JNIEnv* env, jobject clazz, jstring name) {
RenderNode* renderNode = new RenderNode();
renderNode->incStrong(0);
@@ -505,6 +511,7 @@
{ "nDestroyRenderNode", "(J)V", (void*) android_view_RenderNode_destroyRenderNode },
{ "nSetDisplayListData", "(JJ)V", (void*) android_view_RenderNode_setDisplayListData },
{ "nOutput", "(J)V", (void*) android_view_RenderNode_output },
+ { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize },
{ "nSetCaching", "(JZ)V", (void*) android_view_RenderNode_setCaching },
{ "nSetStaticMatrix", "(JJ)V", (void*) android_view_RenderNode_setStaticMatrix },
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 6f256f0..bd016fd 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -238,10 +238,11 @@
}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlong frameTimeNanos, jint dirtyLeft, jint dirtyTop,
- jint dirtyRight, jint dirtyBottom) {
+ jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density,
+ jint dirtyLeft, jint dirtyTop, jint dirtyRight, jint dirtyBottom) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- return proxy->syncAndDrawFrame(frameTimeNanos, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+ return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density,
+ dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
}
static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz,
@@ -311,6 +312,13 @@
proxy->notifyFramePending();
}
+static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jobject javaFileDescriptor) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
+ proxy->dumpProfileInfo(fd);
+}
+
#endif
// ----------------------------------------------------------------------------
@@ -332,7 +340,7 @@
{ "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
{ "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
- { "nSyncAndDrawFrame", "(JJIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
+ { "nSyncAndDrawFrame", "(JJJFIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
{ "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
{ "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
@@ -343,6 +351,7 @@
{ "nFlushCaches", "(JI)V", (void*) android_view_ThreadedRenderer_flushCaches },
{ "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
+ { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
#endif
};
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 210ea86b..6802b9a 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -30,9 +30,22 @@
public long mNativePtr;
public FontFamily() {
- mNativePtr = nCreateFamily();
+ mNativePtr = nCreateFamily(null, 0);
if (mNativePtr == 0) {
- throw new RuntimeException();
+ throw new IllegalStateException("error creating native FontFamily");
+ }
+ }
+
+ public FontFamily(String lang, String variant) {
+ int varEnum = 0;
+ if ("compact".equals(variant)) {
+ varEnum = 1;
+ } else if ("elegant".equals(variant)) {
+ varEnum = 2;
+ }
+ mNativePtr = nCreateFamily(lang, varEnum);
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("error creating native FontFamily");
}
}
@@ -49,7 +62,7 @@
return nAddFont(mNativePtr, path.getAbsolutePath());
}
- static native long nCreateFamily();
+ static native long nCreateFamily(String lang, int variant);
static native void nUnrefFamily(long nativePtr);
static native boolean nAddFont(long nativeFamily, String path);
}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index f304f4e..a863a06 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -34,14 +34,18 @@
public class FontListParser {
public static class Family {
- public Family(List<String> names, List<String> fontFiles) {
+ public Family(List<String> names, List<String> fontFiles, String lang, String variant) {
this.names = names;
this.fontFiles = fontFiles;
+ this.lang = lang;
+ this.variant = variant;
}
public List<String> names;
// todo: need attributes for font files
public List<String> fontFiles;
+ public String lang;
+ public String variant;
}
/* Parse fallback list (no names) */
@@ -75,6 +79,8 @@
throws XmlPullParserException, IOException {
List<String> names = null;
List<String> fontFiles = new ArrayList<String>();
+ String lang = null;
+ String variant = null;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
@@ -82,6 +88,12 @@
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (parser.getName().equals("file")) {
+ if (lang == null) {
+ lang = parser.getAttributeValue(null, "lang");
+ }
+ if (variant == null) {
+ variant = parser.getAttributeValue(null, "variant");
+ }
String filename = parser.nextText();
String fullFilename = "/system/fonts/" + filename;
fontFiles.add(fullFilename);
@@ -98,7 +110,7 @@
}
}
}
- return new Family(names, fontFiles);
+ return new Family(names, fontFiles, lang, variant);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index b7613fb..2b07c3f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -245,7 +245,7 @@
private static FontFamily makeFamilyFromParsed(FontListParser.Family family) {
// TODO: expand to handle attributes like lang and variant
- FontFamily fontFamily = new FontFamily();
+ FontFamily fontFamily = new FontFamily(family.lang, family.variant);
for (String fontFile : family.fontFiles) {
fontFamily.addFont(new File(fontFile));
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 2cadf09..442f327 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -23,6 +23,7 @@
DisplayListLogBuffer.cpp \
DisplayListRenderer.cpp \
Dither.cpp \
+ DrawProfiler.cpp \
Extensions.cpp \
FboCache.cpp \
GradientCache.cpp \
diff --git a/libs/hwui/DrawProfiler.cpp b/libs/hwui/DrawProfiler.cpp
new file mode 100644
index 0000000..971a66e
--- /dev/null
+++ b/libs/hwui/DrawProfiler.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "DrawProfiler.h"
+
+#include <cutils/compiler.h>
+
+#include "OpenGLRenderer.h"
+#include "Properties.h"
+
+#define DEFAULT_MAX_FRAMES 128
+
+#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone)) return
+
+#define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f)
+
+#define PROFILE_DRAW_WIDTH 3
+#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
+#define PROFILE_DRAW_DP_PER_MS 7
+
+// Number of floats we want to display from FrameTimingData
+// If this is changed make sure to update the indexes below
+#define NUM_ELEMENTS 4
+
+#define RECORD_INDEX 0
+#define PREPARE_INDEX 1
+#define PLAYBACK_INDEX 2
+#define SWAPBUFFERS_INDEX 3
+
+// Must be NUM_ELEMENTS in size
+static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 };
+static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d;
+static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
+
+// We could get this from TimeLord and use the actual frame interval, but
+// this is good enough
+#define FRAME_THRESHOLD 16
+
+namespace android {
+namespace uirenderer {
+
+static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+}
+
+DrawProfiler::DrawProfiler()
+ : mType(kNone)
+ , mDensity(0)
+ , mData(NULL)
+ , mDataSize(0)
+ , mCurrentFrame(-1)
+ , mPreviousTime(0)
+ , mVerticalUnit(0)
+ , mHorizontalUnit(0)
+ , mThresholdStroke(0) {
+ setDensity(1);
+}
+
+DrawProfiler::~DrawProfiler() {
+ destroyData();
+}
+
+void DrawProfiler::setDensity(float density) {
+ if (CC_UNLIKELY(mDensity != density)) {
+ mDensity = density;
+ mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
+ mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
+ mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
+ }
+}
+
+void DrawProfiler::startFrame(nsecs_t recordDurationNanos) {
+ RETURN_IF_DISABLED();
+ mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos);
+ mPreviousTime = systemTime(CLOCK_MONOTONIC);
+}
+
+void DrawProfiler::markPlaybackStart() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+}
+
+void DrawProfiler::markPlaybackEnd() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+}
+
+void DrawProfiler::finishFrame() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+ mCurrentFrame = (mCurrentFrame + 1) % mDataSize;
+}
+
+void DrawProfiler::unionDirty(Rect* dirty) {
+ RETURN_IF_DISABLED();
+ // Not worth worrying about minimizing the dirty region for debugging, so just
+ // dirty the entire viewport.
+ if (dirty) {
+ dirty->setEmpty();
+ }
+}
+
+void DrawProfiler::draw(OpenGLRenderer* canvas) {
+ if (CC_LIKELY(mType != kBars)) {
+ return;
+ }
+
+ prepareShapes(canvas->getViewportHeight());
+ drawGraph(canvas);
+ drawCurrentFrame(canvas);
+ drawThreshold(canvas);
+}
+
+void DrawProfiler::createData() {
+ if (mData) return;
+
+ mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES);
+ if (mDataSize <= 0) mDataSize = 1;
+ if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum
+ mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData));
+ mRects = new float*[NUM_ELEMENTS];
+ for (int i = 0; i < NUM_ELEMENTS; i++) {
+ // 4 floats per rect
+ mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float));
+ }
+ mCurrentFrame = 0;
+}
+
+void DrawProfiler::destroyData() {
+ delete mData;
+ mData = NULL;
+}
+
+void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) {
+ r.top = r.bottom - (data * mVerticalUnit);
+ shapeOutput[0] = r.left;
+ shapeOutput[1] = r.top;
+ shapeOutput[2] = r.right;
+ shapeOutput[3] = r.bottom;
+ r.bottom = r.top;
+}
+
+void DrawProfiler::prepareShapes(const int baseline) {
+ Rect r;
+ r.right = mHorizontalUnit;
+ for (int i = 0; i < mDataSize; i++) {
+ const int shapeIndex = i * 4;
+ r.bottom = baseline;
+ addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex);
+ addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex);
+ addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex);
+ addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex);
+ r.translate(mHorizontalUnit, 0);
+ }
+}
+
+void DrawProfiler::drawGraph(OpenGLRenderer* canvas) {
+ SkPaint paint;
+ for (int i = 0; i < NUM_ELEMENTS; i++) {
+ paint.setColor(ELEMENT_COLORS[i]);
+ canvas->drawRects(mRects[i], mDataSize * 4, &paint);
+ }
+}
+
+void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) {
+ // This draws a solid rect over the entirety of the current frame's shape
+ // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1]
+ // which will therefore fully overlap the previously drawn rects
+ SkPaint paint;
+ paint.setColor(CURRENT_FRAME_COLOR);
+ const int i = mCurrentFrame * 4;
+ canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2],
+ mRects[0][i+3], &paint);
+}
+
+void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) {
+ SkPaint paint;
+ paint.setColor(THRESHOLD_COLOR);
+ paint.setStrokeWidth(mThresholdStroke);
+
+ float pts[4];
+ pts[0] = 0.0f;
+ pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
+ pts[2] = canvas->getViewportWidth();
+ canvas->drawLines(pts, 4, &paint);
+}
+
+DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() {
+ ProfileType type = kNone;
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+ if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+ if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) {
+ type = kBars;
+ } else if (!strcmp(buf, "true")) {
+ type = kConsole;
+ }
+ }
+ return type;
+}
+
+bool DrawProfiler::loadSystemProperties() {
+ ProfileType newType = loadRequestedProfileType();
+ if (newType != mType) {
+ mType = newType;
+ if (mType == kNone) {
+ destroyData();
+ } else {
+ createData();
+ }
+ return true;
+ }
+ return false;
+}
+
+void DrawProfiler::dumpData(int fd) {
+ RETURN_IF_DISABLED();
+
+ // This method logs the last N frames (where N is <= mDataSize) since the
+ // last call to dumpData(). In other words if there's a dumpData(), draw frame,
+ // dumpData(), the last dumpData() should only log 1 frame.
+
+ const FrameTimingData emptyData = {0, 0, 0, 0};
+
+ FILE *file = fdopen(fd, "a");
+ fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n");
+
+ for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) {
+ int i = (mCurrentFrame + frameOffset) % mDataSize;
+ if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) {
+ continue;
+ }
+ fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
+ mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers);
+ }
+ // reset the buffer
+ memset(mData, 0, sizeof(FrameTimingData) * mDataSize);
+ mCurrentFrame = 0;
+
+ fflush(file);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DrawProfiler.h b/libs/hwui/DrawProfiler.h
new file mode 100644
index 0000000..c1aa1c6
--- /dev/null
+++ b/libs/hwui/DrawProfiler.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DRAWPROFILER_H
+#define DRAWPROFILER_H
+
+#include <utils/Timers.h>
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class OpenGLRenderer;
+
+class DrawProfiler {
+public:
+ DrawProfiler();
+ ~DrawProfiler();
+
+ bool loadSystemProperties();
+ void setDensity(float density);
+
+ void startFrame(nsecs_t recordDurationNanos = 0);
+ void markPlaybackStart();
+ void markPlaybackEnd();
+ void finishFrame();
+
+ void unionDirty(Rect* dirty);
+ void draw(OpenGLRenderer* canvas);
+
+ void dumpData(int fd);
+
+private:
+ enum ProfileType {
+ kNone,
+ kConsole,
+ kBars,
+ };
+
+ typedef struct {
+ float record;
+ float prepare;
+ float playback;
+ float swapBuffers;
+ } FrameTimingData;
+
+ void createData();
+ void destroyData();
+
+ void addRect(Rect& r, float data, float* shapeOutput);
+ void prepareShapes(const int baseline);
+ void drawGraph(OpenGLRenderer* canvas);
+ void drawCurrentFrame(OpenGLRenderer* canvas);
+ void drawThreshold(OpenGLRenderer* canvas);
+
+ ProfileType loadRequestedProfileType();
+
+ ProfileType mType;
+ float mDensity;
+
+ FrameTimingData* mData;
+ int mDataSize;
+
+ int mCurrentFrame;
+ nsecs_t mPreviousTime;
+
+ int mVerticalUnit;
+ int mHorizontalUnit;
+ int mThresholdStroke;
+
+ /*
+ * mRects represents an array of rect shapes, divided into NUM_ELEMENTS
+ * groups such that each group is drawn with the same paint.
+ * For example mRects[0] is the array of rect floats suitable for
+ * OpenGLRenderer:drawRects() that makes up all the FrameTimingData:record
+ * information.
+ */
+ float** mRects;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DRAWPROFILER_H */
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 20b8f2f..12241b8 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -89,6 +89,36 @@
#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling"
/**
+ * System property used to enable or disable hardware rendering profiling.
+ * The default value of this property is assumed to be false.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "true", to enable profiling
+ * "visual_bars", to enable profiling and visualize the results on screen
+ * "false", to disable profiling
+ */
+#define PROPERTY_PROFILE "debug.hwui.profile"
+#define PROPERTY_PROFILE_VISUALIZE_BARS "visual_bars"
+
+/**
+ * System property used to specify the number of frames to be used
+ * when doing hardware rendering profiling.
+ * The default value of this property is #PROFILE_MAX_FRAMES.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "60", to set the limit of frames to 60
+ */
+#define PROPERTY_PROFILE_MAXFRAMES "debug.hwui.profile.maxframes"
+
+/**
* Used to enable/disable non-rectangular clipping debugging.
*
* The accepted values are:
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index df74f31..d964efc 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -93,6 +93,17 @@
ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName());
}
+int RenderNode::getDebugSize() {
+ int size = sizeof(RenderNode);
+ if (mStagingDisplayListData) {
+ size += mStagingDisplayListData->allocator.usedSize();
+ }
+ if (mDisplayListData && mDisplayListData != mStagingDisplayListData) {
+ size += mDisplayListData->allocator.usedSize();
+ }
+ return size;
+}
+
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1811a7b..1a5377b 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -119,6 +119,7 @@
void replayNodeInParent(ReplayStateStruct& replayStruct, const int level);
ANDROID_API void output(uint32_t level = 1);
+ ANDROID_API int getDebugSize();
bool isRenderable() const {
return mDisplayListData && mDisplayListData->hasDrawOps;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 160fbea..f91e90e 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
#define GLES_VERSION 2
-#define USE_TEXTURE_ATLAS false
// Android-specific addition that is used to show when frames began in systrace
EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
@@ -229,7 +228,7 @@
}
void GlobalContext::initAtlas() {
- if (USE_TEXTURE_ATLAS) {
+ if (mAtlasBuffer.get()) {
Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize);
}
}
@@ -486,6 +485,8 @@
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawDisplayList called on a context with no canvas or surface!");
+ profiler().markPlaybackStart();
+
EGLint width, height;
mGlobalContext->beginFrame(mEglSurface, &width, &height);
if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
@@ -493,6 +494,8 @@
dirty = NULL;
} else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
dirty = NULL;
+ } else {
+ profiler().unionDirty(dirty);
}
status_t status;
@@ -506,14 +509,17 @@
Rect outBounds;
status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds);
- // TODO: Draw debug info
- // TODO: Performance tracking
+ profiler().draw(mCanvas);
mCanvas->finish();
+ profiler().markPlaybackEnd();
+
if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}
+
+ profiler().finishFrame();
}
// Called by choreographer to do an RT-driven animation
@@ -524,6 +530,8 @@
ATRACE_CALL();
+ profiler().startFrame();
+
TreeInfo info;
info.evaluateAnimations = true;
info.performStagingPush = false;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index da85d448..a04269b 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -23,6 +23,7 @@
#include <utils/Functor.h>
#include <utils/Vector.h>
+#include "../DrawProfiler.h"
#include "../RenderNode.h"
#include "RenderTask.h"
#include "RenderThread.h"
@@ -77,6 +78,8 @@
void notifyFramePending();
+ DrawProfiler& profiler() { return mProfiler; }
+
private:
friend class RegisterFrameCallbackTask;
@@ -100,6 +103,8 @@
bool mHaveNewSurface;
const sp<RenderNode> mRootRenderNode;
+
+ DrawProfiler mProfiler;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index ee3e059..7ea358f 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -34,6 +34,8 @@
: mRenderThread(NULL)
, mContext(NULL)
, mFrameTimeNanos(0)
+ , mRecordDurationNanos(0)
+ , mDensity(1.0f) // safe enough default
, mSyncResult(kSync_OK) {
}
@@ -64,15 +66,17 @@
mDirty.set(left, top, right, bottom);
}
-int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos) {
+int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = kSync_OK;
mFrameTimeNanos = frameTimeNanos;
+ mRecordDurationNanos = recordDurationNanos;
postAndWait();
// Reset the single-frame data
mFrameTimeNanos = 0;
+ mRecordDurationNanos = 0;
mDirty.setEmpty();
return mSyncResult;
@@ -87,6 +91,9 @@
void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
+ mContext->profiler().setDensity(mDensity);
+ mContext->profiler().startFrame(mRecordDurationNanos);
+
bool canUnblockUiThread;
bool canDrawThisFrame;
{
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index acbc02a..30c8880 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -60,7 +60,8 @@
void removeLayer(DeferredLayerUpdater* layer);
void setDirty(int left, int top, int right, int bottom);
- int drawFrame(nsecs_t frameTimeNanos);
+ void setDensity(float density) { mDensity = density; }
+ int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos);
virtual void run();
@@ -80,6 +81,8 @@
*********************************************/
Rect mDirty;
nsecs_t mFrameTimeNanos;
+ nsecs_t mRecordDurationNanos;
+ float mDensity;
int mSyncResult;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 8e772f2..77c0aa7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -99,16 +99,20 @@
post(task);
}
-CREATE_BRIDGE0(loadSystemProperties) {
+CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) {
bool needsRedraw = false;
if (Caches::hasInstance()) {
needsRedraw = Caches::getInstance().initProperties();
}
+ if (args->context->profiler().loadSystemProperties()) {
+ needsRedraw = true;
+ }
return (void*) needsRedraw;
}
bool RenderProxy::loadSystemProperties() {
SETUP_TASK(loadSystemProperties);
+ args->context = mContext;
return (bool) postAndWait(task);
}
@@ -175,10 +179,11 @@
post(task);
}
-int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos,
- int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
+int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
+ float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
mDrawFrameTask.setDirty(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
- return mDrawFrameTask.drawFrame(frameTimeNanos);
+ mDrawFrameTask.setDensity(density);
+ return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
}
CREATE_BRIDGE1(destroyCanvasAndSurface, CanvasContext* context) {
@@ -315,6 +320,18 @@
mRenderThread.queueAtFront(task);
}
+CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) {
+ args->context->profiler().dumpData(args->fd);
+ return NULL;
+}
+
+void RenderProxy::dumpProfileInfo(int fd) {
+ SETUP_TASK(dumpProfileInfo);
+ args->context = mContext;
+ args->fd = fd;
+ postAndWait(task);
+}
+
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 22d4e22..c8d42ec 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -69,8 +69,8 @@
ANDROID_API void pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
ANDROID_API void setOpaque(bool opaque);
- ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos,
- int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
+ ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
+ float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
ANDROID_API void destroyCanvasAndSurface();
ANDROID_API void invokeFunctor(Functor* functor, bool waitForCompletion);
@@ -87,6 +87,8 @@
ANDROID_API void fence();
ANDROID_API void notifyFramePending();
+ ANDROID_API void dumpProfileInfo(int fd);
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/packages/SystemUI/res/drawable/ic_qs_close.xml b/packages/SystemUI/res/drawable/ic_qs_back.xml
similarity index 78%
rename from packages/SystemUI/res/drawable/ic_qs_close.xml
rename to packages/SystemUI/res/drawable/ic_qs_back.xml
index dd43e6c..52039f5 100644
--- a/packages/SystemUI/res/drawable/ic_qs_close.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_back.xml
@@ -24,5 +24,5 @@
<path
android:fill="#FFFFFFFF"
- android:pathData="M19.0,6.4l-1.3999996,-1.4000001 -5.6000004,5.6000004 -5.6,-5.6000004 -1.4000001,1.4000001 5.6000004,5.6 -5.6000004,5.6000004 1.4000001,1.3999996 5.6,-5.6000004 5.6000004,5.6000004 1.3999996,-1.3999996 -5.6000004,-5.6000004z"/>
+ android:pathData="M20.0,11.0L7.8,11.0l5.6,-5.6L12.0,4.0l-8.0,8.0l8.0,8.0l1.4,-1.4L7.8,13.0L20.0,13.0L20.0,11.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
index 73886ec..88a2c3a 100644
--- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
@@ -23,8 +23,6 @@
android:viewportHeight="24.0"/>
<path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
+ android:fill="#4DFFFFFF"
+ android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
index 8dff318..0e933cf 100644
--- a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
@@ -24,5 +24,5 @@
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
+ android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
index ea5ab70..51be1a8 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
@@ -23,8 +23,6 @@
android:viewportHeight="24.0"/>
<path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
+ android:fill="#4DFFFFFF"
+ android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
index 44024f3..c8c217d 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
@@ -24,5 +24,5 @@
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
+ android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
index afab88f..8b104d17 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
@@ -24,5 +24,5 @@
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
+ android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index e73b431..e1c460c 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -26,7 +26,7 @@
android:layout_alignParentStart="true"
android:contentDescription="@string/accessibility_quick_settings_close"
android:padding="@dimen/qs_panel_padding"
- android:src="@drawable/ic_qs_close" />
+ android:src="@drawable/ic_qs_back" />
<TextView
android:id="@android:id/title"
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 7de421c..85d2f16 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -25,36 +25,37 @@
<com.android.systemui.recents.views.TaskBarView
android:id="@+id/task_view_bar"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="56dp"
android:layout_gravity="top|center_horizontal"
android:background="@color/recents_task_bar_default_background_color">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/recents_task_view_application_icon_size"
android:layout_height="@dimen/recents_task_view_application_icon_size"
- android:layout_gravity="center_vertical|start"
- android:padding="8dp" />
+ android:layout_marginStart="16dp"
+ android:layout_gravity="center_vertical|start" />
<TextView
android:id="@+id/activity_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|left"
- android:layout_marginStart="@dimen/recents_task_view_application_icon_size"
- android:layout_marginEnd="@dimen/recents_task_view_application_icon_size"
- android:textSize="22sp"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="64dp"
+ android:layout_marginEnd="64dp"
+ android:textSize="16sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
- android:fontFamily="sans-serif-light"
+ android:fontFamily="sans-serif-medium"
android:singleLine="true"
android:maxLines="2"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<ImageView
android:id="@+id/dismiss_task"
- android:layout_width="@dimen/recents_task_view_application_icon_size"
- android:layout_height="@dimen/recents_task_view_application_icon_size"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="4dp"
android:layout_gravity="center_vertical|end"
- android:padding="23dp"
+ android:padding="18dp"
android:src="@drawable/recents_dismiss_light" />
</com.android.systemui.recents.views.TaskBarView>
</com.android.systemui.recents.views.TaskView>
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index 8b344000..6d63bb0 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -16,11 +16,14 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/zen_mode_condition_detail_button_padding"
+ android:layout_marginRight="@dimen/zen_mode_condition_detail_button_padding" >
<RadioButton
android:id="@android:id/checkbox"
- android:layout_width="32dp"
+ android:layout_width="40dp"
+ android:layout_marginStart="2dp"
android:layout_height="@dimen/zen_mode_condition_height"
android:layout_alignParentStart="true"
android:gravity="center" />
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 0936cc2..0a8f852 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -21,9 +21,7 @@
android:layout_height="wrap_content"
android:background="@color/system_primary_color"
android:orientation="vertical"
- android:paddingTop="@dimen/qs_panel_padding"
- android:paddingLeft="@dimen/qs_panel_padding"
- android:paddingRight="@dimen/qs_panel_padding" >
+ android:paddingTop="@dimen/qs_panel_padding" >
<TextView
android:id="@android:id/title"
@@ -31,8 +29,10 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginBottom="8dp"
+ android:layout_marginStart="@dimen/qs_panel_padding"
+ android:layout_marginEnd="@dimen/qs_panel_padding"
android:text="@string/zen_mode_title"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
+ android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
<LinearLayout
android:id="@android:id/content"
@@ -46,6 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
+ android:layout_marginEnd="4dp"
android:layout_gravity="end"
android:text="@string/quick_settings_more_settings"
android:textAppearance="@style/TextAppearance.QS.DetailButton" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4e37dbb..e6fa535 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -67,7 +67,7 @@
<!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
<color name="recents_task_bar_light_text_color">#ffeeeeee</color>
<!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
- <color name="recents_task_bar_dark_text_color">#ff222222</color>
+ <color name="recents_task_bar_dark_text_color">#ff333333</color>
<!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
<color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color>
<!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6405ae6..0184df2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -113,9 +113,9 @@
<!-- The min animation duration for animating views that are newly visible. -->
<integer name="recents_filter_animate_new_views_min_duration">125</integer>
<!-- The min animation duration for animating the task bar in. -->
- <integer name="recents_animate_task_bar_enter_duration">225</integer>
+ <integer name="recents_animate_task_bar_enter_duration">300</integer>
<!-- The min animation duration for animating the task bar out. -->
- <integer name="recents_animate_task_bar_exit_duration">175</integer>
+ <integer name="recents_animate_task_bar_exit_duration">150</integer>
<!-- The animation duration for animating in the info pane. -->
<integer name="recents_animate_task_view_info_pane_duration">150</integer>
<!-- The animation duration for animating the removal of a task view. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 610b376..61ed3cf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -196,6 +196,9 @@
<dimen name="qs_dual_tile_height">109dp</dimen>
<dimen name="qs_dual_tile_padding">12dp</dimen>
+ <!-- How far the hidden header peeks from the top of the screen when QS is in detail mode. -->
+ <dimen name="qs_header_peek_height">8dp</dimen>
+
<!-- How far the expanded QS panel peeks from the header in collapsed state. -->
<dimen name="qs_peek_height">8dp</dimen>
@@ -212,7 +215,7 @@
<dimen name="glowpadview_inner_radius">15dip</dimen>
<!-- The size of the application icon in the recents task view. -->
- <dimen name="recents_task_view_application_icon_size">60dp</dimen>
+ <dimen name="recents_task_view_application_icon_size">32dp</dimen>
<!-- The size of the activity icon in the recents task view. -->
<dimen name="recents_task_view_activity_icon_size">60dp</dimen>
@@ -221,10 +224,13 @@
<dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
<!-- The min translation in the Z index for the last task. -->
- <dimen name="recents_task_view_z_min">3dp</dimen>
+ <dimen name="recents_task_view_z_min">5dp</dimen>
<!-- The translation in the Z index for each task above the last task. -->
- <dimen name="recents_task_view_z_increment">5dp</dimen>
+ <dimen name="recents_task_view_z_increment">10dp</dimen>
+
+ <!-- The amount of bottom inset in the shadow outline. -->
+ <dimen name="recents_task_view_shadow_outline_bottom_inset">5dp</dimen>
<!-- The amount to translate when animating the removal of a task. -->
<dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
@@ -302,4 +308,7 @@
<!-- Volume panel dialog width -->
<dimen name="volume_panel_width">300dp</dimen>
+
+ <!-- Volume panel z depth -->
+ <dimen name="volume_panel_z">3dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 626fc0d..6ce0e48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -47,8 +47,10 @@
private int mCellHeight;
private int mLargeCellWidth;
private int mLargeCellHeight;
+ private boolean mExpanded;
private TileRecord mDetailRecord;
+ private Callback mCallback;
public QSPanel(Context context) {
this(context, null);
@@ -66,6 +68,10 @@
updateResources();
}
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
public void updateResources() {
final Resources res = mContext.getResources();
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
@@ -84,12 +90,14 @@
}
public void setExpanded(boolean expanded) {
- if (!expanded) {
+ if (mExpanded == expanded) return;
+ mExpanded = expanded;
+ if (!mExpanded) {
showDetail(false /*show*/, mDetailRecord);
}
for (TileRecord r : mRecords) {
- r.tile.setListening(expanded);
- if (expanded) {
+ r.tile.setListening(mExpanded);
+ if (mExpanded) {
r.tile.refreshState();
}
}
@@ -156,6 +164,7 @@
if (mDetailRecord == null) return;
listener = mTeardownDetailWhenDone;
}
+ fireShowingDetail(show);
int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
mClipper.animateCircularClip(x, y, show, listener);
@@ -239,6 +248,12 @@
return cols;
}
+ private void fireShowingDetail(boolean showingDetail) {
+ if (mCallback != null) {
+ mCallback.onShowingDetail(showingDetail);
+ }
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
@@ -265,4 +280,8 @@
mDetailRecord = null;
};
};
+
+ public interface Callback {
+ void onShowingDetail(boolean showingDetail);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index ffd64d4..ca9bb94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -35,6 +35,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -49,7 +50,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
/** A proxy implementation for the recents component */
-public class AlternateRecentsComponent {
+public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
/** A handler for messages from the recents implementation */
class RecentsMessageHandler extends Handler {
@@ -62,12 +63,15 @@
Bundle replyData = msg.getData().getParcelable(KEY_CONFIGURATION_DATA);
mSingleCountFirstTaskRect = replyData.getParcelable(KEY_SINGLE_TASK_STACK_RECT);
mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight);
+ mTwoCountFirstTaskRect = replyData.getParcelable(KEY_TWO_TASK_STACK_RECT);
+ mTwoCountFirstTaskRect.offset(0, (int) statusBarHeight);
mMultipleCountFirstTaskRect = replyData.getParcelable(KEY_MULTIPLE_TASK_STACK_RECT);
mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight);
if (Console.Enabled) {
Console.log(Constants.Log.App.RecentsComponent,
"[RecentsComponent|RecentsMessageHandler|handleMessage]",
"singleTaskRect: " + mSingleCountFirstTaskRect +
+ " twoTaskRect: " + mTwoCountFirstTaskRect +
" multipleTaskRect: " + mMultipleCountFirstTaskRect);
}
@@ -123,6 +127,7 @@
final public static int MSG_SHOW_RECENTS = 4;
final public static int MSG_HIDE_RECENTS = 5;
final public static int MSG_TOGGLE_RECENTS = 6;
+ final public static int MSG_START_ENTER_ANIMATION = 7;
final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail";
final public static String EXTRA_FROM_ALT_TAB = "recents.triggeredFromAltTab";
@@ -130,6 +135,7 @@
final public static String KEY_WINDOW_RECT = "recents.windowRect";
final public static String KEY_SYSTEM_INSETS = "recents.systemInsets";
final public static String KEY_SINGLE_TASK_STACK_RECT = "recents.singleCountTaskRect";
+ final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect";
final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect";
@@ -155,6 +161,7 @@
boolean mTriggeredFromAltTab;
Rect mSingleCountFirstTaskRect = new Rect();
+ Rect mTwoCountFirstTaskRect = new Rect();
Rect mMultipleCountFirstTaskRect = new Rect();
long mLastToggleTime;
@@ -261,8 +268,10 @@
/** Returns whether we have valid task rects to animate to. */
boolean hasValidTaskRects() {
return mSingleCountFirstTaskRect != null && mSingleCountFirstTaskRect.width() > 0 &&
- mSingleCountFirstTaskRect.height() > 0 && mMultipleCountFirstTaskRect != null &&
- mMultipleCountFirstTaskRect.width() > 0 && mMultipleCountFirstTaskRect.height() > 0;
+ mSingleCountFirstTaskRect.height() > 0 && mTwoCountFirstTaskRect != null &&
+ mTwoCountFirstTaskRect.width() > 0 && mTwoCountFirstTaskRect.height() > 0 &&
+ mMultipleCountFirstTaskRect != null && mMultipleCountFirstTaskRect.width() > 0 &&
+ mMultipleCountFirstTaskRect.height() > 0;
}
/** Updates each of the task animation rects. */
@@ -303,8 +312,8 @@
return null;
}
- /** Returns whether there is are multiple recents tasks */
- boolean hasMultipleRecentsTask(List<ActivityManager.RecentTaskInfo> tasks) {
+ /** Returns the proper rect to use for the animation, given the number of tasks. */
+ Rect getAnimationTaskRect(List<ActivityManager.RecentTaskInfo> tasks) {
// NOTE: Currently there's no method to get the number of non-home tasks, so we have to
// compute this ourselves
SystemServicesProxy ssp = mSystemServicesProxy;
@@ -318,7 +327,13 @@
continue;
}
}
- return (tasks.size() > 1);
+ if (tasks.size() <= 1) {
+ return mSingleCountFirstTaskRect;
+ } else if (tasks.size() <= 2) {
+ return mTwoCountFirstTaskRect;
+ } else {
+ return mMultipleCountFirstTaskRect;
+ }
}
/** Converts from the device rotation to the degree */
@@ -392,7 +407,7 @@
}
return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail,
- taskRect.left, taskRect.top, null);
+ taskRect.left, taskRect.top, this);
}
/** Returns whether the recents is currently running */
@@ -472,9 +487,8 @@
// which can differ depending on the number of items in the list.
SystemServicesProxy ssp = mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> recentTasks =
- ssp.getRecentTasks(2, UserHandle.CURRENT.getIdentifier());
- Rect taskRect = hasMultipleRecentsTask(recentTasks) ? mMultipleCountFirstTaskRect :
- mSingleCountFirstTaskRect;
+ ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier());
+ Rect taskRect = getAnimationTaskRect(recentTasks);
boolean useThumbnailTransition = !isTopTaskHome &&
hasValidTaskRects();
@@ -517,4 +531,18 @@
mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
}
+
+
+ /**** OnAnimationStartedListener Implementation ****/
+
+ @Override
+ public void onAnimationStarted() {
+ // Notify recents to start the enter animation
+ try {
+ Message msg = Message.obtain(null, MSG_START_ENTER_ANIMATION, 0, 0);
+ mService.send(msg);
+ } catch (RemoteException re) {
+ re.printStackTrace();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 57957a8..4db81bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -36,7 +36,7 @@
// Enables the search bar layout
public static final boolean EnableSearchLayout = true;
// Enables the dynamic shadows behind each task
- public static final boolean EnableShadows = false;
+ public static final boolean EnableShadows = true;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// For debugging, this enables us to create mock recents tasks
@@ -102,7 +102,7 @@
public static final int FilterStartDelay = 25;
// The padding will be applied to the smallest dimension, and then applied to all sides
- public static final float StackPaddingPct = 0.1f;
+ public static final float StackPaddingPct = 0.085f;
// The overlap height relative to the task height
public static final float StackOverlapPct = 0.65f;
// The height of the peek space relative to the stack height
@@ -113,4 +113,4 @@
public static final int StackPeekNumCards = 3;
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index befe8b4..df387c1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -98,6 +98,9 @@
// If there are no filtered stacks, dismiss recents and launch the first task
dismissRecentsIfVisible();
}
+ } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
+ // Try and start the enter animation
+ mRecentsView.startOnEnterAnimation();
}
}
};
@@ -345,6 +348,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register the broadcast receiver to handle messages when the screen is turned off
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 03f7e36..1ae7a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -46,7 +46,9 @@
public float animationPxMovementPerSecond;
- public Interpolator defaultBezierInterpolator;
+ public Interpolator fastOutSlowInInterpolator;
+ public Interpolator fastOutLinearInInterpolator;
+ public Interpolator linearOutSlowInInterpolator;
public int filteringCurrentViewsMinAnimDuration;
public int filteringNewViewsMinAnimDuration;
@@ -59,6 +61,7 @@
public int taskViewRemoveAnimTranslationXPx;
public int taskViewTranslationZMinPx;
public int taskViewTranslationZIncrementPx;
+ public int taskViewShadowOutlineBottomInsetPx;
public int taskViewRoundedCornerRadiusPx;
public int searchBarSpaceHeightPx;
@@ -130,6 +133,8 @@
taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
taskViewTranslationZIncrementPx =
res.getDimensionPixelSize(R.dimen.recents_task_view_z_increment);
+ taskViewShadowOutlineBottomInsetPx =
+ res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset);
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
taskBarViewDefaultBackgroundColor =
@@ -141,8 +146,12 @@
taskBarViewDarkTextColor =
res.getColor(R.color.recents_task_bar_dark_text_color);
- defaultBezierInterpolator = AnimationUtils.loadInterpolator(context,
+ fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
+ fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_linear_in);
+ linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.linear_out_slow_in);
// Check if the developer options are enabled
ContentResolver cr = context.getContentResolver();
@@ -167,6 +176,13 @@
appWidgetId).apply();
}
+ /** Called when the configuration has changed, and we want to reset any configuration specific
+ * members. */
+ public void updateOnConfigurationChange() {
+ launchedFromAltTab = false;
+ launchedWithThumbnailAnimation = false;
+ }
+
/** Returns whether the search bar app widget exists */
public boolean hasSearchBarAppWidget() {
return searchBarAppWidgetId >= 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 4bdbb20..0c2c11d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -55,7 +55,8 @@
if (msg.what == AlternateRecentsComponent.MSG_UPDATE_FOR_CONFIGURATION) {
RecentsTaskLoader.initialize(context);
- RecentsConfiguration.reinitialize(context);
+ RecentsConfiguration config = RecentsConfiguration.reinitialize(context);
+ config.updateOnConfigurationChange();
try {
Bundle data = msg.getData();
@@ -73,7 +74,6 @@
// Get the task stack and search bar bounds
Rect taskStackBounds = new Rect();
- RecentsConfiguration config = RecentsConfiguration.getInstance();
config.getTaskStackBounds(windowRect.width(), windowRect.height(), taskStackBounds);
// Calculate the target task rect for when there is one task.
@@ -83,20 +83,29 @@
stack.addTask(new Task());
tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
systemInsets.top - systemInsets.bottom, 0, 0);
- tsv.boundScroll();
+ tsv.setStackScrollToInitialState();
transform = tsv.getStackTransform(0, tsv.getStackScroll());
transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT,
new Rect(transform.rect));
- // Also calculate the target task rect when there are multiple tasks.
+ // Also calculate the target task rect when there are two tasks.
stack.addTask(new Task());
tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
systemInsets.top - systemInsets.bottom, 0, 0);
- tsv.setStackScrollRaw(Integer.MAX_VALUE);
- tsv.boundScroll();
+ tsv.setStackScrollToInitialState();
transform = tsv.getStackTransform(1, tsv.getStackScroll());
transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
+ replyData.putParcelable(AlternateRecentsComponent.KEY_TWO_TASK_STACK_RECT,
+ new Rect(transform.rect));
+
+ // Also calculate the target task rect when there are two tasks.
+ stack.addTask(new Task());
+ tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
+ systemInsets.top - systemInsets.bottom, 0, 0);
+ tsv.setStackScrollToInitialState();
+ transform = tsv.getStackTransform(2, tsv.getStackScroll());
+ transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT,
new Rect(transform.rect));
@@ -127,6 +136,11 @@
Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
+ } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) {
+ // Send a broadcast to start the enter animation
+ Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION);
+ intent.setPackage(context.getPackageName());
+ context.sendBroadcast(intent);
}
}
}
@@ -135,6 +149,7 @@
public class RecentsService extends Service {
final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
+ final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
Messenger mSystemUIMessenger = new Messenger(new SystemUIMessageHandler(this));
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 cad9ce5..6005275 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -158,6 +158,18 @@
return false;
}
+ /** Requests all task stacks to start their enter-recents animation */
+ public void startOnEnterAnimation() {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child instanceof TaskStackView) {
+ TaskStackView stackView = (TaskStackView) child;
+ stackView.startOnEnterAnimation();
+ }
+ }
+ }
+
/** Adds the search bar */
public void setSearchBar(View searchBar) {
// Create the search bar (and hide it if we have no recent tasks)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index 3ee0545..cae6bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -278,6 +278,7 @@
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
}
+ mCallback.onSwipeChanged(mCurrView, view.getTranslationX());
}
});
anim.addListener(new AnimatorListenerAdapter() {
@@ -313,6 +314,7 @@
if (mCurrView != null) {
float delta = getPos(ev) - mInitialTouchPos;
setSwipeAmount(delta);
+ mCallback.onSwipeChanged(mCurrView, delta);
}
break;
case MotionEvent.ACTION_UP:
@@ -393,6 +395,8 @@
void onBeginDrag(View v);
+ void onSwipeChanged(View v, float delta);
+
void onChildDismissed(View v);
void onSnapBackCompleted(View v);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index 07caa1b..9e6c98e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents.views;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -81,7 +80,7 @@
.alpha(toTransform.dismissAlpha)
.setStartDelay(0)
.setDuration(duration)
- .setInterpolator(config.defaultBezierInterpolator)
+ .setInterpolator(config.fastOutSlowInInterpolator)
.withLayer()
.start();
} else {
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 2b08b19..1fbaf87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,7 +23,6 @@
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region;
@@ -88,6 +87,7 @@
int mStackViewsAnimationDuration;
boolean mStackViewsDirty = true;
boolean mAwaitingFirstLayout = true;
+ boolean mStartEnterAnimationRequestedAfterLayout;
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
Rect mTmpRect2 = new Rect();
@@ -172,7 +172,7 @@
}
// Set the alphas
- transform.dismissAlpha = Math.max(-1f, Math.min(0f, t)) + 1f;
+ transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
// Update the rect and visibility
transform.rect.set(mTaskRect);
@@ -300,6 +300,15 @@
public void setStackScrollRaw(int value) {
mStackScroll = value;
}
+ /** Sets the current stack scroll to the initial state when you first enter recents */
+ public void setStackScrollToInitialState() {
+ if (mStack.getTaskCount() > 2) {
+ int initialScroll = mMaxScroll - mTaskRect.height() / 2;
+ setStackScroll(initialScroll);
+ } else {
+ setStackScroll(mMaxScroll);
+ }
+ }
/**
* Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
@@ -344,7 +353,7 @@
mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
curScroll, 250));
- mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().defaultBezierInterpolator);
+ mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().fastOutSlowInInterpolator);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
@@ -700,22 +709,9 @@
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately
if (mAwaitingFirstLayout) {
- setStackScroll(mMaxScroll);
+ setStackScrollToInitialState();
requestSynchronizeStackViewsWithModel();
synchronizeStackViewsWithModel();
-
- // Update the focused task index to be the next item to the top task
- if (config.launchedFromAltTab) {
- focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
- }
-
- // Animate the task bar of the first task view
- if (config.launchedWithThumbnailAnimation) {
- TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
- if (tv != null) {
- tv.animateOnEnterRecents();
- }
- }
}
// Measure each of the children
@@ -758,7 +754,47 @@
}
if (mAwaitingFirstLayout) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+
+ // Update the focused task index to be the next item to the top task
+ if (config.launchedFromAltTab) {
+ focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
+ }
+
+ // Prepare the first view for its enter animation
+ if (config.launchedWithThumbnailAnimation) {
+ TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+ if (tv != null) {
+ tv.prepareAnimateOnEnterRecents();
+ }
+ }
+
+ // Mark that we have completely the first layout
mAwaitingFirstLayout = false;
+
+ // If the enter animation started already and we haven't completed a layout yet, do the
+ // enter animation now
+ if (mStartEnterAnimationRequestedAfterLayout) {
+ startOnEnterAnimation();
+ }
+ }
+ }
+
+ /** Requests this task stacks to start it's enter-recents animation */
+ public void startOnEnterAnimation() {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (!config.launchedWithThumbnailAnimation) return;
+
+ // If we are still waiting to layout, then just defer until then
+ if (mAwaitingFirstLayout) {
+ mStartEnterAnimationRequestedAfterLayout = true;
+ return;
+ }
+
+ // Animate the task bar of the first task view
+ TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+ if (tv != null) {
+ tv.animateOnEnterRecents();
}
}
@@ -1529,6 +1565,11 @@
}
@Override
+ public void onSwipeChanged(View v, float delta) {
+ // Do nothing
+ }
+
+ @Override
public void onChildDismissed(View v) {
TaskView tv = (TaskView) v;
mSv.onTaskDismissed(tv);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 8575661..ffa181d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
-import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
@@ -113,7 +112,8 @@
// Update the outline
Outline o = new Outline();
- o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius);
+ o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
+ config.taskViewShadowOutlineBottomInsetPx, radius);
setOutline(o);
}
@@ -167,7 +167,7 @@
.scaleY(toTransform.scale)
.alpha(toTransform.alpha)
.setDuration(duration)
- .setInterpolator(config.defaultBezierInterpolator)
+ .setInterpolator(config.fastOutSlowInInterpolator)
.withLayer()
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
@@ -221,14 +221,21 @@
fromTransform.alpha = 0f;
}
+ /** Prepares this task view for the enter-recents animations. This is called earlier in the
+ * first layout because the actual animation into recents may take a long time. */
+ public void prepareAnimateOnEnterRecents() {
+ mBarView.setVisibility(View.INVISIBLE);
+ }
+
/** Animates this task view as it enters recents */
public void animateOnEnterRecents() {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- mBarView.setAlpha(0f);
+ mBarView.setVisibility(View.VISIBLE);
+ mBarView.setTranslationY(-mBarView.getMeasuredHeight());
mBarView.animate()
- .alpha(1f)
- .setStartDelay(300)
- .setInterpolator(config.defaultBezierInterpolator)
+ .translationY(0)
+ .setStartDelay(200)
+ .setInterpolator(config.fastOutSlowInInterpolator)
.setDuration(config.taskBarEnterAnimDuration)
.withLayer()
.start();
@@ -238,9 +245,9 @@
public void animateOnLeavingRecents(final Runnable r) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
mBarView.animate()
- .alpha(0f)
+ .translationY(-mBarView.getMeasuredHeight())
.setStartDelay(0)
- .setInterpolator(config.defaultBezierInterpolator)
+ .setInterpolator(config.fastOutLinearInInterpolator)
.setDuration(config.taskBarExitAnimDuration)
.withLayer()
.withEndAction(new Runnable() {
@@ -261,7 +268,7 @@
animate().translationX(config.taskViewRemoveAnimTranslationXPx)
.alpha(0f)
.setStartDelay(0)
- .setInterpolator(config.defaultBezierInterpolator)
+ .setInterpolator(config.fastOutSlowInInterpolator)
.setDuration(config.taskViewRemoveAnimDuration)
.withLayer()
.withEndAction(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index fbe76f9..422d47f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -83,6 +83,7 @@
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
@@ -117,6 +118,9 @@
public static final int EXPANDED_LEAVE_ALONE = -10000;
public static final int EXPANDED_FULL_OPEN = -10001;
+ /** If true, delays dismissing the Keyguard until the ActivityManager calls back. */
+ protected static final boolean DELAY_DISMISS_TO_ACTIVITY_LAUNCH = false;
+
protected CommandQueue mCommandQueue;
protected IStatusBarService mBarService;
protected H mHandler = createHandler();
@@ -182,6 +186,7 @@
*/
protected int mState;
protected boolean mBouncerShowing;
+ protected boolean mShowLockscreenNotifications;
protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
@@ -201,6 +206,9 @@
final int mode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
setZenMode(mode);
+ final boolean show = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1) != 0;
+ setShowLockscreenNotifications(show);
}
};
@@ -224,7 +232,7 @@
}
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
- startNotificationActivity(new OnDismissAction() {
+ dismissKeyguardThenExecute(new OnDismissAction() {
@Override
public boolean onDismiss() {
try {
@@ -246,7 +254,8 @@
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
visibilityChanged(false);
}
- return handled; // Wait for activity start.
+ // Wait for activity start.
+ return handled && DELAY_DISMISS_TO_ACTIVITY_LAUNCH;
}
});
return true;
@@ -362,6 +371,9 @@
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+ mSettingsObserver);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -472,7 +484,7 @@
* Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
* @param action A dismiss action that is called if it's safe to start the activity.
*/
- protected void startNotificationActivity(OnDismissAction action) {
+ protected void dismissKeyguardThenExecute(OnDismissAction action) {
action.onDismiss();
}
@@ -1042,7 +1054,7 @@
}
public void onClick(final View v) {
- startNotificationActivity(new OnDismissAction() {
+ dismissKeyguardThenExecute(new OnDismissAction() {
public boolean onDismiss() {
try {
// The intent we are sending is for the application, which
@@ -1062,7 +1074,7 @@
v.getLocationOnScreen(pos);
Intent overlay = new Intent();
overlay.setSourceBounds(new Rect(pos[0], pos[1],
- pos[0]+v.getWidth(), pos[1]+v.getHeight()));
+ pos[0] + v.getWidth(), pos[1] + v.getHeight()));
try {
mIntent.send(mContext, 0, overlay);
sent = true;
@@ -1087,7 +1099,7 @@
visibilityChanged(false);
boolean waitForActivityLaunch = sent && mIntent.isActivity();
- return waitForActivityLaunch;
+ return waitForActivityLaunch && DELAY_DISMISS_TO_ACTIVITY_LAUNCH;
}
});
}
@@ -1247,6 +1259,10 @@
updateNotifications();
}
+ protected void setShowLockscreenNotifications(boolean show) {
+ mShowLockscreenNotifications = show;
+ }
+
protected abstract void haltTicker();
protected abstract void setAreThereNotifications();
protected abstract void updateNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 802e5e5..dce5a30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -34,6 +34,7 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.GestureRecorder;
@@ -51,7 +52,7 @@
PhoneStatusBar mStatusBar;
private StatusBarHeaderView mHeader;
private View mQsContainer;
- private View mQsPanel;
+ private QSPanel mQsPanel;
private View mKeyguardStatusView;
private ObservableScrollView mScrollView;
private View mStackScrollerContainer;
@@ -70,6 +71,7 @@
*/
private boolean mIntercepting;
private boolean mQsExpanded;
+ private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
private float mInitialHeightOnTouch;
private float mInitialTouchX;
@@ -81,13 +83,14 @@
private int mQsMaxExpansionHeight;
private int mMinStackHeight;
private int mQsPeekHeight;
+ private int mQsHeaderPeekHeight;
+ private boolean mQsShowingDetail;
private float mNotificationTranslation;
private int mStackScrollerIntrinsicPadding;
private boolean mQsExpansionEnabled = true;
private ValueAnimator mQsExpansionAnimator;
private FlingAnimationUtils mFlingAnimationUtils;
private int mStatusBarMinHeight;
-
private Interpolator mFastOutSlowInInterpolator;
private ObjectAnimator mClockAnimator;
private int mClockAnimationTarget = -1;
@@ -130,7 +133,8 @@
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
mStackScrollerContainer = findViewById(R.id.notification_container_parent);
mQsContainer = findViewById(R.id.quick_settings_container);
- mQsPanel = findViewById(R.id.quick_settings_panel);
+ mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+ mQsPanel.setCallback(mQsPanelCallback);
mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
mScrollView.setListener(this);
mNotificationStackScroller = (NotificationStackScrollLayout)
@@ -154,6 +158,7 @@
mStatusBarMinHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
+ mQsHeaderPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_header_peek_height);
mClockPositionAlgorithm.loadDimens(getResources());
}
@@ -165,7 +170,9 @@
mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
if (mQsExpanded) {
- setQsStackScrollerPadding(mQsMaxExpansionHeight);
+ if (mQsFullyExpanded) {
+ setQsStackScrollerPadding(mQsMaxExpansionHeight);
+ }
} else {
setQsExpansion(mQsMinExpansionHeight);
positionClockAndNotifications();
@@ -518,10 +525,39 @@
? View.INVISIBLE
: View.VISIBLE);
mScrollView.setTouchEnabled(mQsExpanded);
+ if (mQsShowingDetail) {
+ if (mQsFullyExpanded) {
+ setQsHeaderPeeking(true);
+ }
+ } else {
+ setQsHeaderPeeking(false);
+ }
+ }
+
+ private void setQsHeaderPeeking(boolean peeking) {
+ final boolean stackIsPeeking = mStackScrollerContainer.getTranslationY() != 0;
+ final boolean headerIsPeeking = mHeader.getTranslationY() != 0;
+ final int ty = mQsHeaderPeekHeight - mHeader.getExpandedHeight();
+ if (peeking) {
+ if (!headerIsPeeking) {
+ mHeader.animate().translationY(ty);
+ }
+ if (!stackIsPeeking) {
+ mStackScrollerContainer.animate().translationY(ty);
+ }
+ } else {
+ if (headerIsPeeking) {
+ mHeader.animate().translationY(0);
+ }
+ if (stackIsPeeking) {
+ mStackScrollerContainer.animate().translationY(0);
+ }
+ }
}
private void setQsExpansion(float height) {
height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
+ mQsFullyExpanded = height == mQsMaxExpansionHeight;
if (height > mQsMinExpansionHeight && !mQsExpanded) {
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
@@ -614,10 +650,16 @@
if (!mQsExpansionEnabled) {
return false;
}
+ final float ty = mHeader.getTranslationY();
boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
- && y >= mHeader.getTop() && y <= mHeader.getBottom();
+ && y >= mHeader.getTop() + ty && y <= mHeader.getBottom() + ty;
if (mQsExpanded) {
- return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
+ if (mQsShowingDetail && onHeader) {
+ // bring back the header, crudely
+ setQsHeaderPeeking(false);
+ mQsPanel.setExpanded(false);
+ }
+ return !mQsShowingDetail && onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
} else {
return onHeader;
}
@@ -781,4 +823,12 @@
public View getRightIcon() {
return mKeyguardBottomArea.getCameraImageView();
}
+
+ private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
+ @Override
+ public void onShowingDetail(boolean showingDetail) {
+ mQsShowingDetail = showingDetail;
+ updateQsState();
+ }
+ };
}
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 5dcd61c..dedfff0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -505,6 +505,12 @@
}
@Override
+ protected void setShowLockscreenNotifications(boolean show) {
+ super.setShowLockscreenNotifications(show);
+ updateStackScrollerState();
+ }
+
+ @Override
public void start() {
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
@@ -2342,16 +2348,24 @@
}
};
- public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) {
+ public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned) {
if (onlyProvisioned && !isDeviceProvisioned()) return;
- try {
- // Dismiss the lock screen when Settings starts.
- ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
- } catch (RemoteException e) {
- }
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- animateCollapsePanels();
+
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ try {
+ // Dismiss the lock screen when Settings starts.
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ }
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ animateCollapsePanels();
+
+ return DELAY_DISMISS_TO_ACTIVITY_LAUNCH;
+ }
+ });
}
private View.OnClickListener mClockClickListener = new View.OnClickListener() {
@@ -2412,7 +2426,7 @@
};
@Override
- protected void startNotificationActivity(OnDismissAction action) {
+ protected void dismissKeyguardThenExecute(OnDismissAction action) {
if (mStatusBarKeyguardViewManager.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action);
} else {
@@ -2851,7 +2865,10 @@
}
public void updateStackScrollerState() {
+ if (mStackScroller == null) return;
mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */);
+ mStackScroller.setVisibility(!mShowLockscreenNotifications && mState == StatusBarState.KEYGUARD
+ ? View.INVISIBLE : View.VISIBLE);
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 67f3a3d..cbfc641 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -25,12 +25,15 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.media.AudioService;
import android.media.AudioSystem;
import android.media.RingtoneManager;
import android.media.ToneGenerator;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
@@ -44,6 +47,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -76,6 +80,7 @@
private static final int MAX_VOLUME = 100;
private static final int FREE_DELAY = 10000;
private static final int TIMEOUT_DELAY = 3000;
+ private static final int TIMEOUT_DELAY_EXPANDED = 10000;
private static final int MSG_VOLUME_CHANGED = 0;
private static final int MSG_FREE_RESOURCES = 1;
@@ -103,6 +108,7 @@
private boolean mRingIsSilent;
private boolean mVoiceCapable;
private boolean mZenModeCapable;
+ private int mTimeoutDelay = TIMEOUT_DELAY;
// True if we want to play tones on the system stream when the master stream is specified.
private final boolean mPlayMasterStreamTones;
@@ -301,17 +307,26 @@
lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_width);
lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
+ lp.format = PixelFormat.TRANSLUCENT;
lp.windowAnimations = R.style.Animation_VolumePanel;
- window.setBackgroundDrawableResource(com.android.systemui.R.drawable.qs_panel_background);
window.setAttributes(lp);
window.setGravity(Gravity.TOP);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_NOT_TOUCH_MODAL
- | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+ | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | LayoutParams.FLAG_HARDWARE_ACCELERATED);
mDialog.setCanceledOnTouchOutside(true);
- mDialog.setContentView(layoutId);
+ // temporary workaround for no window shadows
+ final FrameLayout layout = new FrameLayout(mContext);
+ final int z = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_z);
+ layout.setPadding(z, z, z, z);
+ final View v = LayoutInflater.from(mContext).inflate(layoutId, layout, false);
+ v.setBackgroundResource(com.android.systemui.R.drawable.qs_panel_background);
+ v.setElevation(z);
+ layout.addView(v);
+ mDialog.setContentView(layout);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -321,6 +336,7 @@
});
mDialog.create();
+ mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
mView = window.findViewById(R.id.content);
mView.setOnTouchListener(new View.OnTouchListener() {
@@ -513,6 +529,7 @@
@Override
public void onInteraction() {
+ resetTimeout();
if (mZenPanelCallback != null) {
mZenPanelCallback.onInteraction();
}
@@ -521,6 +538,8 @@
}
mZenPanel.setVisibility(View.VISIBLE);
mZenPanelDivider.setVisibility(View.VISIBLE);
+ mTimeoutDelay = TIMEOUT_DELAY_EXPANDED;
+ resetTimeout();
}
private void collapse() {
@@ -529,6 +548,8 @@
mZenPanel.setVisibility(View.GONE);
}
mZenPanelDivider.setVisibility(View.GONE);
+ mTimeoutDelay = TIMEOUT_DELAY;
+ resetTimeout();
}
public void updateStates() {
@@ -1082,7 +1103,7 @@
public void resetTimeout() {
if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis());
removeMessages(MSG_TIMEOUT);
- sendEmptyMessageDelayed(MSG_TIMEOUT, TIMEOUT_DELAY);
+ sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay);
}
private void forceTimeout() {
@@ -1134,7 +1155,12 @@
public void onClick(View v) {
if (v == mExpandButton && mZenController != null) {
final boolean newZen = !mZenController.isZen();
- mZenController.setZen(newZen);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mZenController.setZen(newZen);
+ }
+ });
if (newZen) {
expand();
} else {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0b1c2b8..0cc53d1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -204,6 +204,10 @@
/** Set when we have taken too long waiting to go to sleep. */
boolean mSleepTimeout = false;
+ /** Indicates if we are running on a Leanback-only (TV) device. Only initialized after
+ * setWindowManager is called. **/
+ private boolean mLeanbackOnlyDevice;
+
/**
* We don't want to allow the device to go to sleep while in the process
* of launching an activity. This is primarily to allow alarm intent
@@ -268,6 +272,9 @@
mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+
+ // Initialize this here, now that we can get a valid reference to PackageManager.
+ mLeanbackOnlyDevice = isLeanbackOnlyDevice();
}
}
@@ -1375,7 +1382,10 @@
ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) {
final TaskRecord task = r.task;
- if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) {
+
+ // On leanback only devices we should keep all activities in the same stack.
+ if (!mLeanbackOnlyDevice &&
+ (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
if (task != null) {
final ActivityStack taskStack = task.stack;
if (taskStack.isOnHomeDisplay()) {
@@ -3423,4 +3433,16 @@
return "VirtualActivityDisplay={" + mDisplayId + "}";
}
}
+
+ private boolean isLeanbackOnlyDevice() {
+ boolean onLeanbackOnly = false;
+ try {
+ onLeanbackOnly = AppGlobals.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK_ONLY);
+ } catch (RemoteException e) {
+ // noop
+ }
+
+ return onLeanbackOnly;
+ }
}
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
index db2d4ee..b1a4636 100644
--- a/services/core/java/com/android/server/task/StateChangedListener.java
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -27,9 +27,8 @@
/**
* Called by the controller to notify the TaskManager that it should check on the state of a
* task.
- * @param taskStatus The state of the task which has changed.
*/
- public void onTaskStateChanged(TaskStatus taskStatus);
+ public void onControllerStateChanged();
/**
* Called by the controller to notify the TaskManager that regardless of the state of the task,
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
index 0210442..c53f5ca 100644
--- a/services/core/java/com/android/server/task/TaskCompletedListener.java
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -16,6 +16,8 @@
package com.android.server.task;
+import com.android.server.task.controllers.TaskStatus;
+
/**
* Used for communication between {@link com.android.server.task.TaskServiceContext} and the
* {@link com.android.server.task.TaskManagerService}.
@@ -26,13 +28,5 @@
* Callback for when a task is completed.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
-
- /**
- * Callback for when the implementing class needs to clean up the
- * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
- * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
- * and it needs to clean up).
- */
- public void onAllTasksCompleted(int serviceToken);
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule);
}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 80030b4..d5b70e6 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -19,10 +19,12 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import android.app.task.ITaskManager;
import android.app.task.Task;
+import android.app.task.TaskManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -30,11 +32,17 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
+import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.server.task.controllers.ConnectivityController;
+import com.android.server.task.controllers.IdleController;
+import com.android.server.task.controllers.StateController;
import com.android.server.task.controllers.TaskStatus;
+import com.android.server.task.controllers.TimeController;
+
+import java.util.LinkedList;
/**
* Responsible for taking tasks representing work to be performed by a client app, and determining
@@ -44,61 +52,148 @@
*/
public class TaskManagerService extends com.android.server.SystemService
implements StateChangedListener, TaskCompletedListener {
+ // TODO: Switch this off for final version.
+ private static final boolean DEBUG = true;
+ /** The number of concurrent tasks we run at one time. */
+ private static final int MAX_TASK_CONTEXTS_COUNT = 3;
static final String TAG = "TaskManager";
+ /**
+ * When a task fails, it gets rescheduled according to its backoff policy. To be nice, we allow
+ * this amount of time from the rescheduled time by which the retry must occur.
+ */
+ private static final long RESCHEDULE_WINDOW_SLOP_MILLIS = 5000L;
/** Master list of tasks. */
private final TaskStore mTasks;
+ static final int MSG_TASK_EXPIRED = 0;
+ static final int MSG_CHECK_TASKS = 1;
+
+ // Policy constants
+ /**
+ * Minimum # of idle tasks that must be ready in order to force the TM to schedule things
+ * early.
+ */
+ private static final int MIN_IDLE_COUNT = 1;
+ /**
+ * Minimum # of connectivity tasks that must be ready in order to force the TM to schedule
+ * things early.
+ */
+ private static final int MIN_CONNECTIVITY_COUNT = 2;
+ /**
+ * Minimum # of tasks (with no particular constraints) for which the TM will be happy running
+ * some work early.
+ */
+ private static final int MIN_READY_TASKS_COUNT = 4;
+
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
- private final SparseArray<TaskServiceContext> mActiveServices =
- new SparseArray<TaskServiceContext>();
+ private final List<TaskServiceContext> mActiveServices = new LinkedList<TaskServiceContext>();
+ /** List of controllers that will notify this service of updates to tasks. */
+ private List<StateController> mControllers;
+ /**
+ * Queue of pending tasks. The TaskServiceContext class will receive tasks from this list
+ * when ready to execute them.
+ */
+ private final LinkedList<TaskStatus> mPendingTasks = new LinkedList<TaskStatus>();
private final TaskHandler mHandler;
private final TaskManagerStub mTaskManagerStub;
- /** Check the pending queue and start any tasks. */
- static final int MSG_RUN_PENDING = 0;
- /** Initiate the stop task flow. */
- static final int MSG_STOP_TASK = 1;
- /** */
- static final int MSG_CHECK_TASKS = 2;
+ /**
+ * Entry point from client to schedule the provided task.
+ * This will add the task to the
+ * @param task Task object containing execution parameters
+ * @param uId The package identifier of the application this task is for.
+ * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
+ * of this task.
+ * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ */
+ public int schedule(Task task, int uId, boolean canPersistTask) {
+ TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask);
+ return startTrackingTask(taskStatus) ?
+ TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE;
+ }
- private class TaskHandler extends Handler {
-
- public TaskHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_RUN_PENDING:
-
- break;
- case MSG_STOP_TASK:
-
- break;
- case MSG_CHECK_TASKS:
- checkTasks();
- break;
+ public List<Task> getPendingTasks(int uid) {
+ ArrayList<Task> outList = new ArrayList<Task>();
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ if (ts.getUid() == uid) {
+ outList.add(ts.getTask());
+ }
}
}
+ return outList;
+ }
- /**
- * Called when we need to run through the list of all tasks and start/stop executing one or
- * more of them.
- */
- private void checkTasks() {
- synchronized (mTasks) {
- final SparseArray<TaskStatus> tasks = mTasks.getTasks();
- for (int i = 0; i < tasks.size(); i++) {
- TaskStatus ts = tasks.valueAt(i);
- if (ts.isReady() && ! isCurrentlyActive(ts)) {
- assignTaskToServiceContext(ts);
- }
+ /**
+ * Entry point from client to cancel all tasks originating from their uid.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid To check against for removal of a task.
+ */
+ public void cancelTaskForUid(int uid) {
+ // Remove from master list.
+ synchronized (mTasks) {
+ if (!mTasks.removeAllByUid(uid)) {
+ // If it's not in the master list, it's nowhere.
+ return;
+ }
+ }
+ // Remove from pending queue.
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ }
+ }
+ }
+ // Cancel if running.
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid) {
+ tsc.cancelExecutingTask();
+ }
+ }
+ }
+ }
+
+ /**
+ * Entry point from client to cancel the task corresponding to the taskId provided.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid Uid of the calling client.
+ * @param taskId Id of the task, provided at schedule-time.
+ */
+ public void cancelTask(int uid, int taskId) {
+ synchronized (mTasks) {
+ if (!mTasks.remove(uid, taskId)) {
+ // If it's not in the master list, it's nowhere.
+ return;
+ }
+ }
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ // If we got it from pending, it didn't make it to active so return.
+ return;
+ }
+ }
+ }
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid &&
+ tsc.getRunningTask().getTaskId() == taskId) {
+ tsc.cancelExecutingTask();
+ return;
}
}
}
@@ -118,6 +213,17 @@
mTasks = new TaskStore(context);
mHandler = new TaskHandler(context.getMainLooper());
mTaskManagerStub = new TaskManagerStub();
+ // Create the "runners".
+ for (int i = 0; i < MAX_TASK_CONTEXTS_COUNT; i++) {
+ mActiveServices.add(
+ new TaskServiceContext(this, context.getMainLooper()));
+ }
+
+ mControllers = new LinkedList<StateController>();
+ mControllers.add(ConnectivityController.get(this));
+ mControllers.add(TimeController.get(this));
+ mControllers.add(IdleController.get(this));
+ // TODO: Add BatteryStateController when implemented.
}
@Override
@@ -126,33 +232,156 @@
}
/**
- * Entry point from client to schedule the provided task.
- * This will add the task to the
- * @param task Task object containing execution parameters
- * @param userId The id of the user this task is for.
- * @param uId The package identifier of the application this task is for.
- * @param canPersistTask Whether or not the client has the appropriate permissions for
- * persisting of this task.
- * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ * Called when we have a task status object that we need to insert in our
+ * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know
+ * about.
*/
- public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
- TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
- return 0;
- }
-
- public List<Task> getPendingTasks(int uid) {
- ArrayList<Task> outList = new ArrayList<Task>(3);
+ private boolean startTrackingTask(TaskStatus taskStatus) {
+ boolean added = false;
synchronized (mTasks) {
- final SparseArray<TaskStatus> tasks = mTasks.getTasks();
- final int N = tasks.size();
- for (int i = 0; i < N; i++) {
- TaskStatus ts = tasks.get(i);
- if (ts.getUid() == uid) {
- outList.add(ts.getTask());
- }
+ added = mTasks.add(taskStatus);
+ }
+ if (added) {
+ for (StateController controller : mControllers) {
+ controller.maybeStartTrackingTask(taskStatus);
}
}
- return outList;
+ return added;
+ }
+
+ /**
+ * Called when we want to remove a TaskStatus object that we've finished executing. Returns the
+ * object removed.
+ */
+ private boolean stopTrackingTask(TaskStatus taskStatus) {
+ boolean removed;
+ synchronized (mTasks) {
+ // Remove from store as well as controllers.
+ removed = mTasks.remove(taskStatus);
+ }
+ if (removed) {
+ for (StateController controller : mControllers) {
+ controller.maybeStopTrackingTask(taskStatus);
+ }
+ }
+ return removed;
+ }
+
+ private boolean cancelTaskOnServiceContext(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask() == ts) {
+ tsc.cancelExecutingTask();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @param ts TaskStatus we are querying against.
+ * @return Whether or not the task represented by the status object is currently being run or
+ * is pending.
+ */
+ private boolean isCurrentlyActive(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext serviceContext : mActiveServices) {
+ if (serviceContext.getRunningTask() == ts) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A task is rescheduled with exponential back-off if the client requests this from their
+ * execution logic.
+ * A caveat is for idle-mode tasks, for which the idle-mode constraint will usurp the
+ * timeliness of the reschedule. For an idle-mode task, no deadline is given.
+ * @param failureToReschedule Provided task status that we will reschedule.
+ * @return A newly instantiated TaskStatus with the same constraints as the last task except
+ * with adjusted timing constraints.
+ */
+ private TaskStatus getRescheduleTaskForFailure(TaskStatus failureToReschedule) {
+ final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ final Task task = failureToReschedule.getTask();
+
+ final long initialBackoffMillis = task.getInitialBackoffMillis();
+ final int backoffAttempt = failureToReschedule.getNumFailures() + 1;
+ long newEarliestRuntimeElapsed = elapsedNowMillis;
+
+ switch (task.getBackoffPolicy()) {
+ case Task.BackoffPolicy.LINEAR:
+ newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt;
+ break;
+ default:
+ if (DEBUG) {
+ Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
+ }
+ case Task.BackoffPolicy.EXPONENTIAL:
+ newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt);
+ break;
+ }
+ long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE
+ : newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS;
+ return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
+ newLatestRuntimeElapsed, backoffAttempt);
+ }
+
+ /**
+ * Called after a periodic has executed so we can to re-add it. We take the last execution time
+ * of the task to be the time of completion (i.e. the time at which this function is called).
+ * This could be inaccurate b/c the task can run for as long as
+ * {@link com.android.server.task.TaskServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
+ * to underscheduling at least, rather than if we had taken the last execution time to be the
+ * start of the execution.
+ * @return A new task representing the execution criteria for this instantiation of the
+ * recurring task.
+ */
+ private TaskStatus getRescheduleTaskForPeriodic(TaskStatus periodicToReschedule) {
+ final long elapsedNow = SystemClock.elapsedRealtime();
+ // Compute how much of the period is remaining.
+ long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
+ long newEarliestRunTimeElapsed = elapsedNow + runEarly;
+ long period = periodicToReschedule.getTask().getIntervalMillis();
+ long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
+
+ if (DEBUG) {
+ Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
+ newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
+ }
+ return new TaskStatus(periodicToReschedule, newEarliestRunTimeElapsed,
+ newLatestRuntimeElapsed, 0 /* backoffAttempt */);
+ }
+
+ // TaskCompletedListener implementations.
+
+ /**
+ * A task just finished executing. We fetch the
+ * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
+ * whether we want to reschedule we readd it to the controllers.
+ * @param taskStatus Completed task.
+ * @param needsReschedule Whether the implementing class should reschedule this task.
+ */
+ @Override
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) {
+ if (!stopTrackingTask(taskStatus)) {
+ if (DEBUG) {
+ Slog.e(TAG, "Error removing task: could not find task to remove. Was task" +
+ "removed while executing?");
+ }
+ return;
+ }
+ if (needsReschedule) {
+ TaskStatus rescheduled = getRescheduleTaskForFailure(taskStatus);
+ startTrackingTask(rescheduled);
+ } else if (taskStatus.getTask().isPeriodic()) {
+ TaskStatus rescheduledPeriodic = getRescheduleTaskForPeriodic(taskStatus);
+ startTrackingTask(rescheduledPeriodic);
+ }
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
// StateChangedListener implementations.
@@ -165,70 +394,123 @@
* see which are ready. This will further decouple the controllers from the execution logic.
*/
@Override
- public void onTaskStateChanged(TaskStatus taskStatus) {
- postCheckTasksMessage();
-
+ public void onControllerStateChanged() {
+ // Post a message to to run through the list of tasks and start/stop any that are eligible.
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
@Override
public void onTaskDeadlineExpired(TaskStatus taskStatus) {
-
+ mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
}
- // TaskCompletedListener implementations.
+ private class TaskHandler extends Handler {
- /**
- * A task just finished executing. We fetch the
- * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
- * whether we want to reschedule we readd it to the controllers.
- * @param serviceToken key for the service context in {@link #mActiveServices}.
- * @param taskId Id of the task that is complete.
- * @param needsReschedule Whether the implementing class should reschedule this task.
- */
- @Override
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
- final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
- if (serviceContext == null) {
- Slog.e(TAG, "Task completed for invalid service context; " + serviceToken);
- return;
+ public TaskHandler(Looper looper) {
+ super(looper);
}
- }
-
- @Override
- public void onAllTasksCompleted(int serviceToken) {
-
- }
-
- private void assignTaskToServiceContext(TaskStatus ts) {
- TaskServiceContext serviceContext =
- mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
- mActiveServices.put(ts.getServiceToken(), serviceContext);
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_TASK_EXPIRED:
+ final TaskStatus expired = (TaskStatus) message.obj; // Unused for now.
+ queueReadyTasksForExecutionH();
+ break;
+ case MSG_CHECK_TASKS:
+ // Check the list of tasks and run some of them if we feel inclined.
+ maybeQueueReadyTasksForExecutionH();
+ break;
+ }
+ maybeRunNextPendingTaskH();
+ // Don't remove TASK_EXPIRED in case one came along while processing the queue.
+ removeMessages(MSG_CHECK_TASKS);
}
- serviceContext.addPendingTask(ts);
- }
- /**
- * @param ts TaskStatus we are querying against.
- * @return Whether or not the task represented by the status object is currently being run or
- * is pending.
- */
- private boolean isCurrentlyActive(TaskStatus ts) {
- TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- return false;
+ /**
+ * Run through list of tasks and execute all possible - at least one is expired so we do
+ * as many as we can.
+ */
+ private void queueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ }
}
- return serviceContext.hasTaskPending(ts);
- }
- /**
- * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
- * are eligible.
- */
- private void postCheckTasksMessage() {
- mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ /**
+ * The state of at least one task has changed. Here is where we could enforce various
+ * policies on when we want to execute tasks.
+ * Right now the policy is such:
+ * If >1 of the ready tasks is idle mode we send all of them off
+ * if more than 2 network connectivity tasks are ready we send them all off.
+ * If more than 4 tasks total are ready we send them all off.
+ * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
+ */
+ private void maybeQueueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ int idleCount = 0;
+ int connectivityCount = 0;
+ List<TaskStatus> runnableTasks = new ArrayList<TaskStatus>();
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ if (ts.hasIdleConstraint()) {
+ idleCount++;
+ }
+ if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) {
+ connectivityCount++;
+ }
+ runnableTasks.add(ts);
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
+ runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
+ for (TaskStatus ts : runnableTasks) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks the state of the pending queue against any available
+ * {@link com.android.server.task.TaskServiceContext} that can run a new task.
+ * {@link com.android.server.task.TaskServiceContext}.
+ */
+ private void maybeRunNextPendingTaskH() {
+ TaskStatus nextPending;
+ synchronized (mPendingTasks) {
+ nextPending = mPendingTasks.poll();
+ }
+ if (nextPending == null) {
+ return;
+ }
+
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.isAvailable()) {
+ if (tsc.executeRunnableTask(nextPending)) {
+ return;
+ }
+ }
+ }
+ }
+ }
}
/**
@@ -268,11 +550,10 @@
public int schedule(Task task) throws RemoteException {
final boolean canPersist = canCallerPersistTasks();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getCallingUserId();
long ident = Binder.clearCallingIdentity();
try {
- return TaskManagerService.this.schedule(task, userId, uid, canPersist);
+ return TaskManagerService.this.schedule(task, uid, canPersist);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -280,15 +561,38 @@
@Override
public List<Task> getAllPendingTasks() throws RemoteException {
- return null;
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return TaskManagerService.this.getPendingTasks(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
public void cancelAll() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTaskForUid(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
public void cancel(int taskId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTask(uid, taskId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
/**
@@ -311,9 +615,7 @@
synchronized (mTasks) {
pw.print("Registered tasks:");
if (mTasks.size() > 0) {
- SparseArray<TaskStatus> tasks = mTasks.getTasks();
- for (int i = 0; i < tasks.size(); i++) {
- TaskStatus ts = tasks.get(i);
+ for (TaskStatus ts : mTasks.getTasks()) {
pw.println();
ts.dump(pw, " ");
}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index 165445a..75e9212 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -36,20 +37,18 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.task.controllers.TaskStatus;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
- * is reused to start concurrent tasks on the TaskService. Information here is unique
- * to the service.
- * Functionality provided by this class:
- * - Manages wakelock for the service.
- * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
- * -
+ * Handles client binding and lifecycle of a task. A task will only execute one at a time on an
+ * instance of this class.
*/
public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+ private static final boolean DEBUG = true;
private static final String TAG = "TaskServiceContext";
/** Define the maximum # of tasks allowed to run on a service at once. */
private static final int defaultMaxActiveTasksPerService =
@@ -66,10 +65,10 @@
};
// States that a task occupies while interacting with the client.
- private static final int VERB_STARTING = 0;
- private static final int VERB_EXECUTING = 1;
- private static final int VERB_STOPPING = 2;
- private static final int VERB_PENDING = 3;
+ static final int VERB_BINDING = 0;
+ static final int VERB_STARTING = 1;
+ static final int VERB_EXECUTING = 2;
+ static final int VERB_STOPPING = 3;
// Messages that result from interactions with the client service.
/** System timed out waiting for a response. */
@@ -77,178 +76,173 @@
/** Received a callback from client. */
private static final int MSG_CALLBACK = 1;
/** Run through list and start any ready tasks.*/
- private static final int MSG_CHECK_PENDING = 2;
- /** Cancel an active task. */
+ private static final int MSG_SERVICE_BOUND = 2;
+ /** Cancel a task. */
private static final int MSG_CANCEL = 3;
- /** Add a pending task. */
- private static final int MSG_ADD_PENDING = 4;
- /** Client crashed, so we need to wind things down. */
- private static final int MSG_SHUTDOWN = 5;
+ /** Shutdown the Task. Used when the client crashes and we can't die gracefully.*/
+ private static final int MSG_SHUTDOWN_EXECUTION = 4;
- /** Used to identify this task service context when communicating with the TaskManager. */
- final int token;
- final ComponentName component;
- final int userId;
- ITaskService service;
private final Handler mCallbackHandler;
- /** Tasks that haven't been sent to the client for execution yet. */
- private final SparseArray<ActiveTask> mPending;
+ /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+ private final TaskCompletedListener mCompletedListener;
/** Used for service binding, etc. */
private final Context mContext;
- /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
- final private TaskCompletedListener mCompletedListener;
- private final PowerManager.WakeLock mWakeLock;
+ private PowerManager.WakeLock mWakeLock;
- /** Whether this service is actively bound. */
- boolean mBound;
+ // Execution state.
+ private TaskParams mParams;
+ @VisibleForTesting
+ int mVerb;
+ private AtomicBoolean mCancelled = new AtomicBoolean();
- TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
- mContext = taskManager.getContext();
- this.component = taskStatus.getServiceComponent();
- this.token = taskStatus.getServiceToken();
- this.userId = taskStatus.getUserId();
+ /** All the information maintained about the task currently being executed. */
+ private TaskStatus mRunningTask;
+ /** Binder to the client service. */
+ ITaskService service;
+
+ private final Object mAvailableLock = new Object();
+ /** Whether this context is free. */
+ @GuardedBy("mAvailableLock")
+ private boolean mAvailable;
+
+ TaskServiceContext(TaskManagerService service, Looper looper) {
+ this(service.getContext(), service, looper);
+ }
+
+ @VisibleForTesting
+ TaskServiceContext(Context context, TaskCompletedListener completedListener, Looper looper) {
+ mContext = context;
mCallbackHandler = new TaskServiceHandler(looper);
- mPending = new SparseArray<ActiveTask>();
- mCompletedListener = taskManager;
- final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mCompletedListener = completedListener;
+ }
+
+ /**
+ * Give a task to this context for execution. Callers must first check {@link #isAvailable()}
+ * to make sure this is a valid context.
+ * @param ts The status of the task that we are going to run.
+ * @return True if the task was accepted and is going to run.
+ */
+ boolean executeRunnableTask(TaskStatus ts) {
+ synchronized (mAvailableLock) {
+ if (!mAvailable) {
+ Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
+ return false;
+ }
+ mAvailable = false;
+ }
+
+ final PowerManager pm =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TM_WAKELOCK_PREFIX + component.getPackageName());
- mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
+ TM_WAKELOCK_PREFIX + ts.getServiceComponent().getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(ts.getUid()));
mWakeLock.setReferenceCounted(false);
+
+ mRunningTask = ts;
+ mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+
+ mVerb = VERB_BINDING;
+ final Intent intent = new Intent().setComponent(ts.getServiceComponent());
+ boolean binding = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+ new UserHandle(ts.getUserId()));
+ if (!binding) {
+ if (DEBUG) {
+ Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Used externally to query the running task. Will return null if there is no task running. */
+ TaskStatus getRunningTask() {
+ return mRunningTask;
+ }
+
+ /** Called externally when a task that was scheduled for execution should be cancelled. */
+ void cancelExecutingTask() {
+ mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
+ }
+
+ /**
+ * @return Whether this context is available to handle incoming work.
+ */
+ boolean isAvailable() {
+ synchronized (mAvailableLock) {
+ return mAvailable;
+ }
}
@Override
public void taskFinished(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
+ }
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
.sendToTarget();
}
@Override
public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
+ }
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
.sendToTarget();
}
@Override
public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+ if (!verifyCallingUid()) {
+ return;
+ }
mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
}
/**
- * Queue up this task to run on the client. This will execute the task as quickly as possible.
- * @param ts Status of the task to run.
- */
- public void addPendingTask(TaskStatus ts) {
- final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
- final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
- mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
- if (!mBound) {
- Intent intent = new Intent().setComponent(component);
- boolean binding = mContext.bindServiceAsUser(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
- new UserHandle(userId));
- if (!binding) {
- Log.e(TAG, component.getShortClassName() + " unavailable.");
- cancelPendingTask(ts);
- }
- }
- }
-
- /**
- * Called externally when a task that was scheduled for execution should be cancelled.
- * @param ts The status of the task to cancel.
- */
- public void cancelPendingTask(TaskStatus ts) {
- mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
- .sendToTarget();
- }
-
- /**
- * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
- * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
- * ActiveTask.
- * @param op Operation that is taking place.
- */
- private void scheduleOpTimeOut(ActiveTask op) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
-
- final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
- EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
- op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
- }
- Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
- mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
- }
-
- /**
- * @return true if this task is pending or active within this context.
- */
- public boolean hasTaskPending(TaskStatus taskStatus) {
- synchronized (mPending) {
- return mPending.get(taskStatus.getTaskId()) != null;
- }
- }
-
- public boolean isBound() {
- return mBound;
- }
-
- /**
- * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+ * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
* then we keep the wakelock.
- * @param name The concrete component name of the service that has
- * been connected.
+ * @param name The concrete component name of the service that has been connected.
* @param service The IBinder of the Service's communication channel,
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mBound = true;
+ if (!name.equals(mRunningTask.getServiceComponent())) {
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
+ return;
+ }
this.service = ITaskService.Stub.asInterface(service);
- // Remove all timeouts. We've just connected to the client so there are no other
- // MSG_TIMEOUTs at this point.
+ // Remove all timeouts.
mCallbackHandler.removeMessages(MSG_TIMEOUT);
mWakeLock.acquire();
- mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
+ mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
/**
- * When the client service crashes we can have a couple tasks executing, in various stages of
- * undress. We'll cancel all of them and request that they be rescheduled.
+ * If the client service crashes we reschedule this task and clean up.
* @param name The concrete component name of the service whose
*/
@Override
public void onServiceDisconnected(ComponentName name) {
- // Service disconnected... probably client crashed.
- startShutdown();
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
}
/**
- * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
- * tasks, then we do the shutdown.
+ * This class is reused across different clients, and passes itself in as a callback. Check
+ * whether the client exercising the callback is the client we expect.
+ * @return True if the binder calling is coming from the client we expect.
*/
- private void startShutdown() {
- mCompletedListener.onAllTasksCompleted(token);
- mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
- }
-
- /** Tracks a task across its various state changes. */
- private static class ActiveTask {
- final TaskParams params;
- int verb;
- AtomicBoolean cancelled = new AtomicBoolean();
-
- ActiveTask(TaskParams params, int verb) {
- this.params = params;
- this.verb = verb;
+ private boolean verifyCallingUid() {
+ if (mRunningTask == null || Binder.getCallingUid() != mRunningTask.getUid()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stale callback received, ignoring.");
+ }
+ return false;
}
-
- @Override
- public String toString() {
- return params.getTaskId() + " " + VERB_STRINGS[verb];
- }
+ return true;
}
/**
@@ -264,52 +258,67 @@
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case MSG_ADD_PENDING:
- if (message.obj != null) {
- ActiveTask pendingTask = (ActiveTask) message.obj;
- mPending.put(pendingTask.params.getTaskId(), pendingTask);
- }
- // fall through.
- case MSG_CHECK_PENDING:
- checkPendingTasksH();
+ case MSG_SERVICE_BOUND:
+ handleServiceBoundH();
break;
case MSG_CALLBACK:
- ActiveTask receivedCallback = mPending.get(message.arg1);
- removeMessages(MSG_TIMEOUT, receivedCallback);
-
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask);
}
+ removeMessages(MSG_TIMEOUT);
- if (receivedCallback.verb == VERB_STARTING) {
+ if (mVerb == VERB_STARTING) {
final boolean workOngoing = message.arg2 == 1;
- handleStartedH(receivedCallback, workOngoing);
- } else if (receivedCallback.verb == VERB_EXECUTING ||
- receivedCallback.verb == VERB_STOPPING) {
+ handleStartedH(workOngoing);
+ } else if (mVerb == VERB_EXECUTING ||
+ mVerb == VERB_STOPPING) {
final boolean reschedule = message.arg2 == 1;
- handleFinishedH(receivedCallback, reschedule);
+ handleFinishedH(reschedule);
} else {
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised callback: " + mRunningTask);
}
}
break;
case MSG_CANCEL:
- ActiveTask cancelled = mPending.get(message.arg1);
- handleCancelH(cancelled);
+ handleCancelH();
break;
case MSG_TIMEOUT:
- // Timeout msgs have the ActiveTask ref so we can remove them easily.
- handleOpTimeoutH((ActiveTask) message.obj);
+ handleOpTimeoutH();
break;
- case MSG_SHUTDOWN:
- handleShutdownH();
- break;
+ case MSG_SHUTDOWN_EXECUTION:
+ closeAndCleanupTaskH(true /* needsReschedule */);
default:
Log.e(TAG, "Unrecognised message: " + message);
}
}
+ /** Start the task on the service. */
+ private void handleServiceBoundH() {
+ if (mVerb != VERB_BINDING) {
+ Slog.e(TAG, "Sending onStartTask for a task that isn't pending. "
+ + VERB_STRINGS[mVerb]);
+ closeAndCleanupTaskH(false /* reschedule */);
+ return;
+ }
+ if (mCancelled.get()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Task cancelled while waiting for bind to complete. "
+ + mRunningTask);
+ }
+ closeAndCleanupTaskH(true /* reschedule */);
+ return;
+ }
+ try {
+ mVerb = VERB_STARTING;
+ scheduleOpTimeOut();
+ service.startTask(mParams);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStart message to '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' ", e);
+ }
+ }
+
/**
* State behaviours.
* VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
@@ -317,24 +326,25 @@
* _EXECUTING -> Error
* _STOPPING -> Error
*/
- private void handleStartedH(ActiveTask started, boolean workOngoing) {
- switch (started.verb) {
+ private void handleStartedH(boolean workOngoing) {
+ switch (mVerb) {
case VERB_STARTING:
- started.verb = VERB_EXECUTING;
+ mVerb = VERB_EXECUTING;
if (!workOngoing) {
// Task is finished already so fast-forward to handleFinished.
- handleFinishedH(started, false);
+ handleFinishedH(false);
return;
- } else if (started.cancelled.get()) {
- // Cancelled *while* waiting for acknowledgeStartMessage from client.
- handleCancelH(started);
- return;
- } else {
- scheduleOpTimeOut(started);
}
+ if (mCancelled.get()) {
+ // Cancelled *while* waiting for acknowledgeStartMessage from client.
+ handleCancelH();
+ return;
+ }
+ scheduleOpTimeOut();
break;
default:
- Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+ Log.e(TAG, "Handling started task but task wasn't starting! Was "
+ + VERB_STRINGS[mVerb] + ".");
return;
}
}
@@ -345,155 +355,104 @@
* _STARTING -> Error
* _PENDING -> Error
*/
- private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
- switch (executedTask.verb) {
+ private void handleFinishedH(boolean reschedule) {
+ switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
- closeAndCleanupTaskH(executedTask, reschedule);
+ closeAndCleanupTaskH(reschedule);
break;
default:
- Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
- "executed. " + executedTask);
+ Slog.e(TAG, "Got an execution complete message for a task that wasn't being" +
+ "executed. Was " + VERB_STRINGS[mVerb] + ".");
}
}
/**
* A task can be in various states when a cancel request comes in:
- * VERB_PENDING -> Remove from queue.
- * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+ * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
+ * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
+ * _STARTING -> Mark as cancelled and wait for
+ * {@link TaskServiceContext#acknowledgeStartMessage(int, boolean)}
* _EXECUTING -> call {@link #sendStopMessageH}}.
* _ENDING -> No point in doing anything here, so we ignore.
*/
- private void handleCancelH(ActiveTask cancelledTask) {
- switch (cancelledTask.verb) {
- case VERB_PENDING:
- mPending.remove(cancelledTask.params.getTaskId());
- break;
+ private void handleCancelH() {
+ switch (mVerb) {
+ case VERB_BINDING:
case VERB_STARTING:
- cancelledTask.cancelled.set(true);
+ mCancelled.set(true);
break;
case VERB_EXECUTING:
- cancelledTask.verb = VERB_STOPPING;
- sendStopMessageH(cancelledTask);
+ sendStopMessageH();
break;
case VERB_STOPPING:
// Nada.
break;
default:
- Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+ Slog.e(TAG, "Cancelling a task without a valid verb: " + mVerb);
break;
}
}
- /**
- * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
- * and reschedule them as if they had failed.
- * Before posting this message, caller must invoke
- * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}.
- */
- private void handleShutdownH() {
- for (int i = 0; i < mPending.size(); i++) {
- ActiveTask at = mPending.valueAt(i);
- closeAndCleanupTaskH(at, true /* needsReschedule */);
- }
- mWakeLock.release();
- mContext.unbindService(TaskServiceContext.this);
- service = null;
- mBound = false;
- }
-
- /**
- * MSG_TIMEOUT gets processed here.
- * @param timedOutTask The task that timed out.
- */
- private void handleOpTimeoutH(ActiveTask timedOutTask) {
+ /** Process MSG_TIMEOUT here. */
+ private void handleOpTimeoutH() {
if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
- + timedOutTask.params.getTaskId());
+ Log.d(TAG, "MSG_TIMEOUT of " +
+ mRunningTask.getServiceComponent().getShortClassName() + " : "
+ + mParams.getTaskId());
}
- final int taskId = timedOutTask.params.getTaskId();
- switch (timedOutTask.verb) {
+ final int taskId = mParams.getTaskId();
+ switch (mVerb) {
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
// know what happened so let's log it and notify the TaskManager
// FINISHED/NO-RETRY.
Log.e(TAG, "No response from client for onStartTask '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(false /* needsReschedule */);
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the TaskManager to reschedule.
Log.e(TAG, "No response from client for onStopTask, '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(true /* needsReschedule */);
break;
case VERB_EXECUTING:
// Not an error - client ran out of time.
Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
" Reporting failure and asking for reschedule. " +
- component.getShortClassName() + "' tId: " + taskId);
- sendStopMessageH(timedOutTask);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ sendStopMessageH();
break;
default:
Log.e(TAG, "Handling timeout for an unknown active task state: "
- + timedOutTask);
+ + mRunningTask);
return;
}
}
/**
- * Called on the handler thread. Checks the state of the pending queue and starts the task
- * if it can. The task only starts if there is capacity on the service.
+ * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
+ * VERB_STOPPING.
*/
- private void checkPendingTasksH() {
- if (!mBound) {
- return;
- }
- for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
- ActiveTask at = mPending.valueAt(i);
- if (at.verb != VERB_PENDING) {
- continue;
- }
- sendStartMessageH(at);
- }
- }
-
- /**
- * Already running, need to stop. Rund on handler.
- * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
- * VERB_EXECUTING -> VERB_STOPPING.
- */
- private void sendStopMessageH(ActiveTask stoppingTask) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
- if (stoppingTask.verb != VERB_EXECUTING) {
- Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
- // TODO: Handle error?
+ private void sendStopMessageH() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ if (mVerb != VERB_EXECUTING) {
+ Log.e(TAG, "Sending onStopTask for a task that isn't started. " + mRunningTask);
+ closeAndCleanupTaskH(false /* reschedule */);
return;
}
try {
- service.stopTask(stoppingTask.params);
- stoppingTask.verb = VERB_STOPPING;
- scheduleOpTimeOut(stoppingTask);
+ mVerb = VERB_STOPPING;
+ scheduleOpTimeOut();
+ service.stopTask(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStopTask to client.", e);
- closeAndCleanupTaskH(stoppingTask, false);
- }
- }
-
- /** Start the task on the service. */
- private void sendStartMessageH(ActiveTask pendingTask) {
- if (pendingTask.verb != VERB_PENDING) {
- Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
- // TODO: Handle error?
- }
- try {
- service.startTask(pendingTask.params);
- pendingTask.verb = VERB_STARTING;
- scheduleOpTimeOut(pendingTask);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
- + "' ", e);
+ closeAndCleanupTaskH(false);
}
}
@@ -503,13 +462,42 @@
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
- private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) {
- removeMessages(MSG_TIMEOUT, completedTask);
- mPending.remove(completedTask.params.getTaskId());
- if (mPending.size() == 0) {
- startShutdown();
+ private void closeAndCleanupTaskH(boolean reschedule) {
+ removeMessages(MSG_TIMEOUT);
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ mWakeLock = null;
+
+ mRunningTask = null;
+ mParams = null;
+ mVerb = -1;
+ mCancelled.set(false);
+
+ service = null;
+
+ mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
+ synchronized (mAvailableLock) {
+ mAvailable = true;
}
- mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+ }
+
+ /**
+ * Called when sending a message to the client, over whose execution we have no control. If we
+ * haven't received a response in a certain amount of time, we want to give up and carry on
+ * with life.
+ */
+ private void scheduleOpTimeOut() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+
+ final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
+ EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling time out for '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: " +
+ mParams.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+ }
+ Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
+ mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
}
}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
index 81187c8..f72ab22 100644
--- a/services/core/java/com/android/server/task/TaskStore.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -18,10 +18,16 @@
import android.app.task.Task;
import android.content.Context;
+import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
/**
* Maintain a list of classes, and accessor methods/logic for these tasks.
* This class offers the following functionality:
@@ -35,53 +41,122 @@
* - This class is <strong>not</strong> thread-safe.
*/
public class TaskStore {
-
- /**
- * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
- */
- final SparseArray<TaskStatus> mTasks;
+ private static final String TAG = "TaskManagerStore";
+ /** Threshold to adjust how often we want to write to the db. */
+ private static final int MAX_OPS_BEFORE_WRITE = 1;
+ final ArraySet<TaskStatus> mTasks;
final Context mContext;
+ private int mDirtyOperations;
+
TaskStore(Context context) {
- mTasks = intialiseTaskMapFromDisk();
+ mTasks = intialiseTasksFromDisk();
mContext = context;
+ mDirtyOperations = 0;
}
/**
- * Add a task to the master list, persisting it if necessary.
- * Will first check to see if the task already exists. If so, it will replace it.
- * {@link android.content.pm.PackageManager} is queried to see if the calling package has
- * permission to
- * @param task Task to add.
- * @return The initialised TaskStatus object if this operation was successful, null if it
- * failed.
+ * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists,
+ * it will be replaced.
+ * @param taskStatus Task to add.
+ * @return true if the operation succeeded.
*/
- public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
- boolean canPersistTask) {
- TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
- if (canPersistTask && task.isPeriodic()) {
- if (writeStatusToDisk()) {
- mTasks.put(taskStatus.hashCode(), taskStatus);
+ public boolean add(TaskStatus taskStatus) {
+ if (taskStatus.isPersisted()) {
+ if (!maybeWriteStatusToDisk()) {
+ return false;
}
}
- return taskStatus;
+ mTasks.remove(taskStatus);
+ mTasks.add(taskStatus);
+ return true;
+ }
+
+ public int size() {
+ return mTasks.size();
}
/**
- * Remove the provided task. Will also delete the task if it was persisted. Note that this
- * function does not return the validity of the operation, as we assume a delete will always
- * succeed.
- * @param task Task to remove.
+ * Remove the provided task. Will also delete the task if it was persisted.
+ * @return The TaskStatus that was removed, or null if an invalid token was provided.
*/
- public void remove(Task task) {
+ public boolean remove(TaskStatus taskStatus) {
+ boolean removed = mTasks.remove(taskStatus);
+ if (!removed) {
+ Slog.e(TAG, "Error removing task: " + taskStatus);
+ return false;
+ } else {
+ maybeWriteStatusToDisk();
+ }
+ return true;
+ }
+ /**
+ * Removes all TaskStatus objects for a given uid from the master list. Note that it is
+ * possible to remove a task that is pending/active. This operation will succeed, and the
+ * removal will take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @return True if at least one task was removed, false if nothing matching the provided uId
+ * was found.
+ */
+ public boolean removeAllByUid(int uid) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ boolean removed = false;
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ removed = true;
+ }
+ }
+ if (removed) {
+ maybeWriteStatusToDisk();
+ }
+ return removed;
+ }
+
+ /**
+ * Remove the TaskStatus that matches the provided uId and taskId. Note that it is possible
+ * to remove a task that is pending/active. This operation will succeed, and the removal will
+ * take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @param taskId Task id, specified at schedule-time.
+ * @return true if a removal occurred, false if the provided parameters didn't match anything.
+ */
+ public boolean remove(int uid, int taskId) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ maybeWriteStatusToDisk();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return The live array of TaskStatus objects.
+ */
+ public Set<TaskStatus> getTasks() {
+ return mTasks;
}
/**
* Every time the state changes we write all the tasks in one swathe, instead of trying to
* track incremental changes.
+ * @return Whether the operation was successful. This will only fail for e.g. if the system is
+ * low on storage. If this happens, we continue as normal
*/
- private boolean writeStatusToDisk() {
+ private boolean maybeWriteStatusToDisk() {
+ mDirtyOperations++;
+ if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) {
+ for (TaskStatus ts : mTasks) {
+ //
+ }
+ mDirtyOperations = 0;
+ }
return true;
}
@@ -90,21 +165,7 @@
* @return
*/
// TODO: Implement this.
- private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
- return new SparseArray<TaskStatus>();
- }
-
- /**
- * @return the number of tasks in the store
- */
- public int size() {
- return mTasks.size();
- }
-
- /**
- * @return The live array of TaskStatus objects.
- */
- public SparseArray<TaskStatus> getTasks() {
- return mTasks;
+ private ArraySet<TaskStatus> intialiseTasksFromDisk() {
+ return new ArraySet<TaskStatus>();
}
}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
index a0038c5d..474af8f 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -25,6 +25,7 @@
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
import com.android.server.task.TaskManagerService;
@@ -32,21 +33,33 @@
import java.util.List;
/**
- *
+ * Handles changes in connectivity.
+ * We are only interested in metered vs. unmetered networks, and we're interested in them on a
+ * per-user basis.
*/
public class ConnectivityController extends StateController {
private static final String TAG = "TaskManager.Connectivity";
+ private static final boolean DEBUG = true;
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
+ /** Singleton. */
+ private static ConnectivityController mSingleton;
/** Track whether the latest active network is metered. */
private boolean mMetered;
/** Track whether the latest active network is connected. */
private boolean mConnectivity;
- public ConnectivityController(TaskManagerService service) {
+ public static synchronized ConnectivityController get(TaskManagerService taskManager) {
+ if (mSingleton == null) {
+ mSingleton = new ConnectivityController(taskManager);
+ }
+ return mSingleton;
+ }
+
+ private ConnectivityController(TaskManagerService service) {
super(service);
// Register connectivity changed BR.
IntentFilter intentFilter = new IntentFilter();
@@ -56,7 +69,7 @@
}
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
taskStatus.meteredConstraintSatisfied.set(mMetered);
@@ -65,21 +78,28 @@
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
mTrackedTasks.remove(taskStatus);
}
/**
- *
+ * @param userId Id of the user for whom we are updating the connectivity state.
*/
- private void updateTrackedTasks() {
+ private void updateTrackedTasks(int userId) {
+ boolean changed = false;
for (TaskStatus ts : mTrackedTasks) {
+ if (ts.getUserId() != userId) {
+ continue;
+ }
boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
- mStateChangedListener.onTaskStateChanged(ts);
+ changed = true;
}
}
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
+ }
}
class ConnectivityChangedReceiver extends BroadcastReceiver {
@@ -103,17 +123,20 @@
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
// This broadcast gets sent a lot, only update if the active network has changed.
- if (activeNetwork.getType() == networkType) {
+ if (activeNetwork != null && activeNetwork.getType() == networkType) {
+ final int userid = context.getUserId();
mMetered = false;
mConnectivity =
!intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (mConnectivity) { // No point making the call if we know there's no conn.
mMetered = connManager.isActiveNetworkMetered();
}
- updateTrackedTasks();
+ updateTrackedTasks(userid);
}
} else {
- Log.w(TAG, "Unrecognised action in intent: " + action);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised action in intent: " + action);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java
index a319a31..9489644 100644
--- a/services/core/java/com/android/server/task/controllers/IdleController.java
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -49,7 +49,7 @@
private static Object sCreationLock = new Object();
private static volatile IdleController sController;
- public IdleController getController(TaskManagerService service) {
+ public static IdleController get(TaskManagerService service) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new IdleController(service);
@@ -67,7 +67,7 @@
* StateController interface
*/
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasIdleConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(taskStatus);
@@ -77,7 +77,7 @@
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public void maybeStopTrackingTask(TaskStatus taskStatus) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
}
@@ -90,9 +90,9 @@
synchronized (mTrackedTasks) {
for (TaskStatus task : mTrackedTasks) {
task.idleConstraintSatisfied.set(isIdle);
- mStateChangedListener.onTaskStateChanged(task);
}
}
+ mStateChangedListener.onControllerStateChanged();
}
/**
diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java
index e1cd662..ed31eac 100644
--- a/services/core/java/com/android/server/task/controllers/StateController.java
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -42,10 +42,10 @@
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
- public abstract void maybeTrackTaskState(TaskStatus taskStatus);
+ public abstract void maybeStartTrackingTask(TaskStatus taskStatus);
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
- public abstract void removeTaskStateIfTracked(TaskStatus taskStatus);
+ public abstract void maybeStopTrackingTask(TaskStatus taskStatus);
}
diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java
index d270016..b7f84ec 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -18,7 +18,6 @@
import android.app.task.Task;
import android.content.ComponentName;
-import android.content.pm.PackageParser;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -39,65 +38,67 @@
*/
public class TaskStatus {
final Task task;
- final int taskId;
final int uId;
- final Bundle extras;
+ /** At reschedule time we need to know whether to update task on disk. */
+ final boolean persisted;
+
+ // Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
- final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
- private final boolean hasChargingConstraint;
- private final boolean hasTimingConstraint;
- private final boolean hasIdleConstraint;
- private final boolean hasMeteredConstraint;
- private final boolean hasConnectivityConstraint;
-
+ /**
+ * Earliest point in the future at which this task will be eligible to run. A value of 0
+ * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
+ */
private long earliestRunTimeElapsedMillis;
+ /**
+ * Latest point in the future at which this task must be run. A value of {@link Long#MAX_VALUE}
+ * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
+ */
private long latestRunTimeElapsedMillis;
+ private final int numFailures;
+
/** Provide a handle to the service that this task will be run on. */
public int getServiceToken() {
return uId;
}
- /** Generate a TaskStatus object for a given task and uid. */
- // TODO: reimplement this to reuse these objects instead of creating a new one each time?
- public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
- return new TaskStatus(task, userId, uId);
- }
-
- /** Set up the state of a newly scheduled task. */
- TaskStatus(Task task, int userId, int uId) {
+ /** Create a newly scheduled task. */
+ public TaskStatus(Task task, int uId, boolean persisted) {
this.task = task;
- this.taskId = task.getTaskId();
- this.extras = task.getExtras();
this.uId = uId;
+ this.numFailures = 0;
+ this.persisted = persisted;
- hasChargingConstraint = task.isRequireCharging();
- hasIdleConstraint = task.isRequireDeviceIdle();
-
+ final long elapsedNow = SystemClock.elapsedRealtime();
// Timing constraints
if (task.isPeriodic()) {
- long elapsedNow = SystemClock.elapsedRealtime();
earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
- hasTimingConstraint = true;
- } else if (task.getMinLatencyMillis() != 0L || task.getMaxExecutionDelayMillis() != 0L) {
- earliestRunTimeElapsedMillis = task.getMinLatencyMillis() > 0L ?
- task.getMinLatencyMillis() : Long.MAX_VALUE;
- latestRunTimeElapsedMillis = task.getMaxExecutionDelayMillis() > 0L ?
- task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
- hasTimingConstraint = true;
} else {
- hasTimingConstraint = false;
+ earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
+ elapsedNow + task.getMinLatencyMillis() : 0L;
+ latestRunTimeElapsedMillis = task.hasLateConstraint() ?
+ elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
}
+ }
- // Networking constraints
- hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
- hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
+ public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed,
+ long newLatestRuntimeElapsed, int backoffAttempt) {
+ this.task = rescheduling.task;
+
+ this.uId = rescheduling.getUid();
+ this.persisted = rescheduling.isPersisted();
+ this.numFailures = backoffAttempt;
+
+ earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed;
+ latestRunTimeElapsedMillis = newLatestRuntimeElapsed;
}
public Task getTask() {
@@ -105,7 +106,11 @@
}
public int getTaskId() {
- return taskId;
+ return task.getId();
+ }
+
+ public int getNumFailures() {
+ return numFailures;
}
public ComponentName getServiceComponent() {
@@ -121,52 +126,60 @@
}
public Bundle getExtras() {
- return extras;
+ return task.getExtras();
}
- boolean hasConnectivityConstraint() {
- return hasConnectivityConstraint;
+ public boolean hasConnectivityConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
- boolean hasMeteredConstraint() {
- return hasMeteredConstraint;
+ public boolean hasMeteredConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
}
- boolean hasChargingConstraint() {
- return hasChargingConstraint;
+ public boolean hasChargingConstraint() {
+ return task.isRequireCharging();
}
- boolean hasTimingConstraint() {
- return hasTimingConstraint;
+ public boolean hasTimingDelayConstraint() {
+ return earliestRunTimeElapsedMillis != 0L;
}
- boolean hasIdleConstraint() {
- return hasIdleConstraint;
+ public boolean hasDeadlineConstraint() {
+ return latestRunTimeElapsedMillis != Long.MAX_VALUE;
}
- long getEarliestRunTime() {
+ public boolean hasIdleConstraint() {
+ return task.isRequireDeviceIdle();
+ }
+
+ public long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
- long getLatestRunTime() {
+ public long getLatestRunTimeElapsed() {
return latestRunTimeElapsedMillis;
}
+ public boolean isPersisted() {
+ return persisted;
+ }
/**
- * @return whether this task is ready to run, based on its requirements.
+ * @return Whether or not this task is ready to run, based on its requirements.
*/
public synchronized boolean isReady() {
- return (!hasChargingConstraint || chargingConstraintSatisfied.get())
- && (!hasTimingConstraint || timeConstraintSatisfied.get())
- && (!hasConnectivityConstraint || connectivityConstraintSatisfied.get())
- && (!hasMeteredConstraint || meteredConstraintSatisfied.get())
- && (!hasIdleConstraint || idleConstraintSatisfied.get());
+ return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
+ && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
+ && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
+ && (!hasMeteredConstraint() || meteredConstraintSatisfied.get())
+ && (!hasIdleConstraint() || idleConstraintSatisfied.get())
+ && (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get());
}
@Override
public int hashCode() {
int result = getServiceComponent().hashCode();
- result = 31 * result + taskId;
+ result = 31 * result + task.getId();
result = 31 * result + uId;
return result;
}
@@ -177,14 +190,14 @@
if (!(o instanceof TaskStatus)) return false;
TaskStatus that = (TaskStatus) o;
- return ((taskId == that.taskId)
+ return ((task.getId() == that.task.getId())
&& (uId == that.uId)
&& (getServiceComponent().equals(that.getServiceComponent())));
}
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("Task "); pw.println(taskId);
+ pw.print(prefix); pw.print("Task "); pw.println(task.getId());
pw.print(prefix); pw.print("uid="); pw.println(uId);
pw.print(prefix); pw.print("component="); pw.println(task.getService());
}
diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java
index 6d97a53..72f312c 100644
--- a/services/core/java/com/android/server/task/controllers/TimeController.java
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -23,7 +23,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
-import android.util.Log;
import com.android.server.task.TaskManagerService;
@@ -54,8 +53,17 @@
private AlarmManager mAlarmService = null;
/** List of tracked tasks, sorted asc. by deadline */
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
+ /** Singleton. */
+ private static TimeController mSingleton;
- public TimeController(TaskManagerService service) {
+ public static synchronized TimeController get(TaskManagerService taskManager) {
+ if (mSingleton == null) {
+ mSingleton = new TimeController(taskManager);
+ }
+ return mSingleton;
+ }
+
+ private TimeController(TaskManagerService service) {
super(service);
mTaskExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
@@ -75,8 +83,8 @@
* list.
*/
@Override
- public synchronized void maybeTrackTaskState(TaskStatus task) {
- if (task.hasTimingConstraint()) {
+ public synchronized void maybeStartTrackingTask(TaskStatus task) {
+ if (task.hasTimingDelayConstraint()) {
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
while (it.hasPrevious()) {
TaskStatus ts = it.previous();
@@ -85,13 +93,13 @@
it.remove();
it.add(task);
break;
- } else if (ts.getLatestRunTime() < task.getLatestRunTime()) {
+ } else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
// Insert
it.add(task);
break;
}
}
- maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime());
+ maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
}
}
@@ -100,12 +108,12 @@
* so, update them.
*/
@Override
- public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
handleTaskDelayExpired();
}
- if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) {
+ if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
handleTaskDeadlineExpired();
}
}
@@ -140,10 +148,10 @@
* back and forth.
*/
private boolean canStopTrackingTask(TaskStatus taskStatus) {
- final long elapsedNowMillis = SystemClock.elapsedRealtime();
- return taskStatus.timeConstraintSatisfied.get() &&
- (taskStatus.getLatestRunTime() == Long.MAX_VALUE ||
- taskStatus.getLatestRunTime() < elapsedNowMillis);
+ return (!taskStatus.hasTimingDelayConstraint() ||
+ taskStatus.timeDelayConstraintSatisfied.get()) &&
+ (!taskStatus.hasDeadlineConstraint() ||
+ taskStatus.deadlineConstraintSatisfied.get());
}
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
@@ -174,10 +182,10 @@
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
- final long taskDeadline = ts.getLatestRunTime();
+ final long taskDeadline = ts.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
- ts.timeConstraintSatisfied.set(true);
+ ts.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onTaskDeadlineExpired(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
@@ -199,10 +207,12 @@
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
+ if (!ts.hasTimingDelayConstraint()) {
+ continue;
+ }
final long taskDelayTime = ts.getEarliestRunTime();
if (taskDelayTime < nowElapsedMillis) {
- ts.timeConstraintSatisfied.set(true);
- mStateChangedListener.onTaskStateChanged(ts);
+ ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
it.remove();
}
@@ -212,6 +222,7 @@
}
}
}
+ mStateChangedListener.onControllerStateChanged();
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 4f8b9d7..e007600 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -115,7 +115,7 @@
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
private static final int DEFAULT_APP_TRANSITION_DURATION = 250;
- private static final int THUMBNAIL_APP_TRANSITION_DURATION = 225;
+ private static final int THUMBNAIL_APP_TRANSITION_DURATION = 275;
private final Context mContext;
private final Handler mH;