Merge "Fixing issue with configuration states not being reset on configuration change." into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index e2380f7..9c56fb5 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 {
@@ -21668,10 +21669,6 @@
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
- field public static final int CRYPT_TYPE_DEFAULT = 1; // 0x1
- field public static final int CRYPT_TYPE_PASSWORD = 0; // 0x0
- field public static final int CRYPT_TYPE_PATTERN = 2; // 0x2
- field public static final int CRYPT_TYPE_PIN = 3; // 0x3
}
}
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index d08978bd..5e4ddd0 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -15,14 +15,23 @@
*/
package android.app;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.util.ArrayMap;
+import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
@@ -181,6 +190,11 @@
*/
public static final int MSG_CANCEL = 106;
+ /**
+ * When returning, this is the destination location for the shared element.
+ */
+ public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
+
final private Window mWindow;
final protected ArrayList<String> mAllSharedElementNames;
final protected ArrayList<View> mSharedElements = new ArrayList<View>();
@@ -334,6 +348,210 @@
protected abstract Transition getViewsTransition();
+ private static void setSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] parentLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
+ }
+
+ if (view instanceof ImageView) {
+ int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt >= 0) {
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
+ imageView.setScaleType(scaleType);
+ if (scaleType == ImageView.ScaleType.MATRIX) {
+ float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
+ Matrix matrix = new Matrix();
+ matrix.setValues(matrixValues);
+ imageView.setImageMatrix(matrix);
+ }
+ }
+ }
+
+ float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
+ view.setTranslationZ(z);
+
+ int x = sharedElementBundle.getInt(KEY_SCREEN_X);
+ int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
+ int width = sharedElementBundle.getInt(KEY_WIDTH);
+ int height = sharedElementBundle.getInt(KEY_HEIGHT);
+
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+
+ int left = x - parentLoc[0];
+ int top = y - parentLoc[1];
+ int right = left + width;
+ int bottom = top + height;
+ view.layout(left, top, right, bottom);
+ }
+
+ protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
+ Bundle sharedElementState, final ArrayList<View> snapshots) {
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
+ new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
+ if (sharedElementState != null) {
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
+ name, sharedElementState);
+ if (originalState != null) {
+ originalImageState.put((ImageView) sharedElement, originalState);
+ }
+ View parent = (View) sharedElement.getParent();
+ parent.getLocationOnScreen(tempLoc);
+ setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
+ }
+ }
+ mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
+
+ getDecor().getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
+ snapshots);
+ return true;
+ }
+ }
+ );
+ return originalImageState;
+ }
+
+ private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
+ Bundle transitionArgs) {
+ if (!(view instanceof ImageView)) {
+ return null;
+ }
+ Bundle bundle = transitionArgs.getBundle(name);
+ if (bundle == null) {
+ return null;
+ }
+ int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt < 0) {
+ return null;
+ }
+
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType originalScaleType = imageView.getScaleType();
+
+ Matrix originalMatrix = null;
+ if (originalScaleType == ImageView.ScaleType.MATRIX) {
+ originalMatrix = new Matrix(imageView.getImageMatrix());
+ }
+
+ return Pair.create(originalScaleType, originalMatrix);
+ }
+
+ protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
+ int numSharedElements = names.size();
+ if (numSharedElements == 0) {
+ return null;
+ }
+ ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
+ Context context = getWindow().getContext();
+ int[] parentLoc = new int[2];
+ getDecor().getLocationOnScreen(parentLoc);
+ for (String name: names) {
+ Bundle sharedElementBundle = state.getBundle(name);
+ if (sharedElementBundle != null) {
+ Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
+ View snapshot = new View(context);
+ Resources resources = getWindow().getContext().getResources();
+ if (bitmap != null) {
+ snapshot.setBackground(new BitmapDrawable(resources, bitmap));
+ }
+ snapshot.setViewName(name);
+ setSharedElementState(snapshot, name, state, parentLoc);
+ snapshots.add(snapshot);
+ }
+ }
+ return snapshots;
+ }
+
+ protected static void setOriginalImageViewState(
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
+ for (int i = 0; i < originalState.size(); i++) {
+ ImageView imageView = originalState.keyAt(i);
+ Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
+ imageView.setScaleType(state.first);
+ imageView.setImageMatrix(state.second);
+ }
+ }
+
+ protected Bundle captureSharedElementState() {
+ Bundle bundle = new Bundle();
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ captureSharedElementState(sharedElement, name, bundle, tempLoc);
+ }
+ return bundle;
+ }
+
+ /**
+ * Captures placement information for Views with a shared element name for
+ * Activity Transitions.
+ *
+ * @param view The View to capture the placement information for.
+ * @param name The shared element name in the target Activity to apply the placement
+ * information for.
+ * @param transitionArgs Bundle to store shared element placement information.
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
+ */
+ private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] tempLoc) {
+ Bundle sharedElementBundle = new Bundle();
+ view.getLocationOnScreen(tempLoc);
+ float scaleX = view.getScaleX();
+ sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
+ int width = Math.round(view.getWidth() * scaleX);
+ sharedElementBundle.putInt(KEY_WIDTH, width);
+
+ float scaleY = view.getScaleY();
+ sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
+ int height = Math.round(view.getHeight() * scaleY);
+ sharedElementBundle.putInt(KEY_HEIGHT, height);
+
+ sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+
+ if (width > 0 && height > 0) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
+ }
+
+ if (view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
+ sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
+ if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
+ float[] matrix = new float[9];
+ imageView.getImageMatrix().getValues(matrix);
+ sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
+ }
+ }
+
+ transitionArgs.putBundle(name, sharedElementBundle);
+ }
+
+ private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
+ for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
+ if (scaleType == SCALE_TYPE_VALUES[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
private Rect mEpicenter;
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index bc97852..a8617b8 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,11 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Matrix;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -38,7 +34,6 @@
import android.widget.ImageView;
import java.util.ArrayList;
-import java.util.Collection;
/**
* This ActivityTransitionCoordinator is created by the Activity to manage
@@ -56,6 +51,7 @@
private Handler mHandler;
private boolean mIsCanceled;
private ObjectAnimator mBackgroundAnimator;
+ private boolean mIsExitTransitionComplete;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
ArrayList<String> sharedElementNames,
@@ -76,6 +72,8 @@
}
};
mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
+ Bundle state = captureSharedElementState();
+ mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
}
}
@@ -98,9 +96,8 @@
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mIsCanceled) {
- if (!mSharedElementTransitionStarted) {
- send(resultCode, resultData);
- } else {
+ mIsExitTransitionComplete = true;
+ if (mSharedElementTransitionStarted) {
onRemoteExitTransitionComplete();
}
}
@@ -183,6 +180,7 @@
setViewVisibility(mSharedElements, View.VISIBLE);
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
+ requestLayoutForSharedElements();
boolean startEnterTransition = allowOverlappingTransitions();
boolean startSharedElementTransition = true;
@@ -200,6 +198,13 @@
mResultReceiver = null; // all done sending messages.
}
+ private void requestLayoutForSharedElements() {
+ int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ mSharedElements.get(i).requestLayout();
+ }
+ }
+
private Transition beginTransition(boolean startEnterTransition,
boolean startSharedElementTransition) {
Transition sharedElementTransition = null;
@@ -213,6 +218,19 @@
}
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
+ if (startSharedElementTransition) {
+ if (transition == null) {
+ sharedElementTransitionStarted();
+ } else {
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ sharedElementTransitionStarted();
+ }
+ });
+ }
+ }
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
@@ -224,6 +242,13 @@
return transition;
}
+ private void sharedElementTransitionStarted() {
+ mSharedElementTransitionStarted = true;
+ if (mIsExitTransitionComplete) {
+ send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ }
+ }
+
private void startEnterTransition(Transition transition) {
setViewVisibility(mTransitioningViews, View.VISIBLE);
if (!mIsReturning) {
@@ -310,142 +335,4 @@
startEnterTransition(transition);
}
}
-
- private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
- int numSharedElements = names.size();
- if (numSharedElements == 0) {
- return null;
- }
- ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
- Context context = getWindow().getContext();
- int[] parentLoc = new int[2];
- getDecor().getLocationOnScreen(parentLoc);
- for (String name: names) {
- Bundle sharedElementBundle = state.getBundle(name);
- if (sharedElementBundle != null) {
- Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
- View snapshot = new View(context);
- Resources resources = getWindow().getContext().getResources();
- snapshot.setBackground(new BitmapDrawable(resources, bitmap));
- snapshot.setViewName(name);
- setSharedElementState(snapshot, name, state, parentLoc);
- snapshots.add(snapshot);
- }
- }
- return snapshots;
- }
-
- private static void setSharedElementState(View view, String name, Bundle transitionArgs,
- int[] parentLoc) {
- Bundle sharedElementBundle = transitionArgs.getBundle(name);
- if (sharedElementBundle == null) {
- return;
- }
-
- if (view instanceof ImageView) {
- int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
- if (scaleTypeInt >= 0) {
- ImageView imageView = (ImageView) view;
- ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
- imageView.setScaleType(scaleType);
- if (scaleType == ImageView.ScaleType.MATRIX) {
- float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
- Matrix matrix = new Matrix();
- matrix.setValues(matrixValues);
- imageView.setImageMatrix(matrix);
- }
- }
- }
-
- float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
- view.setTranslationZ(z);
-
- int x = sharedElementBundle.getInt(KEY_SCREEN_X);
- int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
- int width = sharedElementBundle.getInt(KEY_WIDTH);
- int height = sharedElementBundle.getInt(KEY_HEIGHT);
-
- int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
- int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
- view.measure(widthSpec, heightSpec);
-
- int left = x - parentLoc[0];
- int top = y - parentLoc[1];
- int right = left + width;
- int bottom = top + height;
- view.layout(left, top, right, bottom);
- }
-
- private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
- Bundle sharedElementState, final ArrayList<View> snapshots) {
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
- new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
- if (sharedElementState != null) {
- int[] tempLoc = new int[2];
- for (int i = 0; i < mSharedElementNames.size(); i++) {
- View sharedElement = mSharedElements.get(i);
- String name = mSharedElementNames.get(i);
- Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
- name, sharedElementState);
- if (originalState != null) {
- originalImageState.put((ImageView) sharedElement, originalState);
- }
- View parent = (View) sharedElement.getParent();
- parent.getLocationOnScreen(tempLoc);
- setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
- sharedElement.requestLayout();
- }
- }
- mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
-
- getDecor().getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
- mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
- snapshots);
- mSharedElementTransitionStarted = true;
- return true;
- }
- }
- );
- return originalImageState;
- }
-
- private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
- Bundle transitionArgs) {
- if (!(view instanceof ImageView)) {
- return null;
- }
- Bundle bundle = transitionArgs.getBundle(name);
- if (bundle == null) {
- return null;
- }
- int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
- if (scaleTypeInt < 0) {
- return null;
- }
-
- ImageView imageView = (ImageView) view;
- ImageView.ScaleType originalScaleType = imageView.getScaleType();
-
- Matrix originalMatrix = null;
- if (originalScaleType == ImageView.ScaleType.MATRIX) {
- originalMatrix = new Matrix(imageView.getImageMatrix());
- }
-
- return Pair.create(originalScaleType, originalMatrix);
- }
-
- private static void setOriginalImageViewState(
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
- for (int i = 0; i < originalState.size(); i++) {
- ImageView imageView = originalState.keyAt(i);
- Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
- imageView.setScaleType(state.first);
- imageView.setImageMatrix(state.second);
- }
- }
-
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 93eb53e..a71d649 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -19,8 +19,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -29,7 +27,7 @@
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.View;
-import android.widget.ImageView;
+import android.view.ViewTreeObserver;
import java.util.ArrayList;
@@ -62,6 +60,10 @@
private boolean mIsHidden;
+ private boolean mExitTransitionStarted;
+
+ private Bundle mExitSharedElementBundle;
+
public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning),
@@ -102,15 +104,32 @@
setViewVisibility(mSharedElements, View.VISIBLE);
mIsHidden = true;
break;
+ case MSG_SHARED_ELEMENT_DESTINATION:
+ mExitSharedElementBundle = resultData;
+ if (mExitTransitionStarted) {
+ startSharedElementExit();
+ }
+ break;
+ }
+ }
+
+ private void startSharedElementExit() {
+ if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
+ Transition transition = getSharedElementExitTransition();
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
+ mSharedElementNames);
+ setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
}
}
private void hideSharedElements() {
setViewVisibility(mSharedElements, View.INVISIBLE);
+ finishIfNecessary();
}
public void startExit() {
- beginTransition();
+ beginTransitions();
setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
@@ -140,7 +159,30 @@
}
}
}, options);
- startExit();
+ Transition sharedElementTransition = mSharedElements.isEmpty()
+ ? null : getSharedElementTransition();
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
+ }
+ Transition transition = mergeTransitions(sharedElementTransition, getExitTransition());
+ if (transition == null) {
+ mExitTransitionStarted = true;
+ } else {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mExitTransitionStarted = true;
+ if (mExitSharedElementBundle != null) {
+ startSharedElementExit();
+ }
+ notifyComplete();
+ return true;
+ }
+ });
+ }
}
private void fadeOutBackground() {
@@ -162,24 +204,13 @@
}
}
- private void beginTransition() {
- Transition sharedElementTransition = configureTransition(getSharedElementTransition());
- Transition viewsTransition = configureTransition(getViewsTransition());
- viewsTransition = addTargets(viewsTransition, mTransitioningViews);
- if (sharedElementTransition == null || mSharedElements.isEmpty()) {
- sharedElementTransitionComplete();
- sharedElementTransition = null;
- } else {
- sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- sharedElementTransitionComplete();
- }
- });
+ private Transition getExitTransition() {
+ Transition viewsTransition = null;
+ if (!mTransitioningViews.isEmpty()) {
+ viewsTransition = configureTransition(getViewsTransition());
}
- if (viewsTransition == null || mTransitioningViews.isEmpty()) {
+ if (viewsTransition == null) {
exitTransitionComplete();
- viewsTransition = null;
} else {
viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
@@ -189,13 +220,46 @@
setViewVisibility(mTransitioningViews, View.VISIBLE);
}
}
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ super.onTransitionCancel(transition);
+ }
});
}
+ return viewsTransition;
+ }
+
+ private Transition getSharedElementExitTransition() {
+ Transition sharedElementTransition = null;
+ if (!mSharedElements.isEmpty()) {
+ sharedElementTransition = configureTransition(getSharedElementTransition());
+ }
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
+ } else {
+ sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ sharedElementTransitionComplete();
+ if (mIsHidden) {
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ }
+ }
+ });
+ mSharedElements.get(0).invalidate();
+ }
+ return sharedElementTransition;
+ }
+
+ private void beginTransitions() {
+ Transition sharedElementTransition = getSharedElementExitTransition();
+ Transition viewsTransition = getExitTransition();
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
- TransitionManager.beginDelayedTransition(getDecor(), transition);
- if (viewsTransition == null && sharedElementTransition != null) {
- mSharedElements.get(0).requestLayout();
+ mExitTransitionStarted = true;
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
}
}
@@ -205,18 +269,12 @@
}
protected boolean isReadyToNotify() {
- return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
+ return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady
+ && mExitTransitionStarted;
}
private void sharedElementTransitionComplete() {
- Bundle bundle = new Bundle();
- int[] tempLoc = new int[2];
- for (int i = 0; i < mSharedElementNames.size(); i++) {
- View sharedElement = mSharedElements.get(i);
- String name = mSharedElementNames.get(i);
- captureSharedElementState(sharedElement, name, bundle, tempLoc);
- }
- mSharedElementBundle = bundle;
+ mSharedElementBundle = captureSharedElementState();
notifyComplete();
}
@@ -230,15 +288,23 @@
mExitNotified = true;
mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
mResultReceiver = null; // done talking
- if (mIsReturning) {
- mActivity.finish();
- mActivity.overridePendingTransition(0, 0);
- }
- mActivity = null;
+ finishIfNecessary();
}
}
}
+ private void finishIfNecessary() {
+ if (mIsReturning && mExitNotified && (mSharedElements.isEmpty()
+ || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) {
+ mActivity.finish();
+ mActivity.overridePendingTransition(0, 0);
+ mActivity = null;
+ }
+ if (!mIsReturning && mExitNotified) {
+ mActivity = null; // don't need it anymore
+ }
+ }
+
@Override
protected Transition getViewsTransition() {
if (mIsReturning) {
@@ -255,58 +321,4 @@
return getWindow().getSharedElementExitTransition();
}
}
-
- /**
- * Captures placement information for Views with a shared element name for
- * Activity Transitions.
- *
- * @param view The View to capture the placement information for.
- * @param name The shared element name in the target Activity to apply the placement
- * information for.
- * @param transitionArgs Bundle to store shared element placement information.
- * @param tempLoc A temporary int[2] for capturing the current location of views.
- */
- private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
- int[] tempLoc) {
- Bundle sharedElementBundle = new Bundle();
- view.getLocationOnScreen(tempLoc);
- float scaleX = view.getScaleX();
- sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
- int width = Math.round(view.getWidth() * scaleX);
- sharedElementBundle.putInt(KEY_WIDTH, width);
-
- float scaleY = view.getScaleY();
- sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
- int height = Math.round(view.getHeight() * scaleY);
- sharedElementBundle.putInt(KEY_HEIGHT, height);
-
- sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
-
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- view.draw(canvas);
- sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
-
- if (view instanceof ImageView) {
- ImageView imageView = (ImageView) view;
- int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
- sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
- if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
- float[] matrix = new float[9];
- imageView.getImageMatrix().getValues(matrix);
- sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
- }
- }
-
- transitionArgs.putBundle(name, sharedElementBundle);
- }
-
- private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
- for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
- if (scaleType == SCALE_TYPE_VALUES[i]) {
- return i;
- }
- }
- return -1;
- }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 25f24b1..90aeaae 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3716,7 +3716,7 @@
* this notification. This action will no longer display separately from the
* notification's content.
*
- * <p>For notifications with multiple pages, child pages can also have content action's
+ * <p>For notifications with multiple pages, child pages can also have content actions
* set, although the list of available actions comes from the main notification and not
* from the child page's notification.
*
@@ -3731,16 +3731,18 @@
}
/**
- * Get the action index from this notification's actions to be clickable with the
- * content of this notification. This action will no longer display separately
+ * Get the index of the notification action, if any, that was specified as being clickable
+ * with the content of this notification. This action will no longer display separately
* from the notification's content.
*
- * <p>For notifications with multiple pages, child pages can also have content action's
+ * <p>For notifications with multiple pages, child pages can also have content actions
* set, although the list of available actions comes from the main notification and not
* from the child page's notification.
*
* <p>If wearable specific actions were added to the main notification, this index will
* apply to that list, otherwise it will apply to the regular actions list.
+ *
+ * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
*/
public int getContentAction() {
return mContentActionIndex;
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/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d3e9089..e5bf7d0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -317,9 +317,9 @@
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
/**
- * Sent to providers after AppWidget state related to the provider has been restored from
- * backup. The intent contains information about how to translate AppWidget ids from the
- * restored data to their new equivalents.
+ * Sent to an {@link AppWidgetProvider} after AppWidget state related to that provider has
+ * been restored from backup. The intent contains information about how to translate AppWidget
+ * ids from the restored data to their new equivalents.
*
* <p>The intent will contain the following extras:
*
@@ -343,7 +343,7 @@
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
- * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast
+ * @see #ACTION_APPWIDGET_HOST_RESTORED
*/
public static final String ACTION_APPWIDGET_RESTORED
= "android.appwidget.action.APPWIDGET_RESTORED";
@@ -352,7 +352,7 @@
* Sent to widget hosts after AppWidget state related to the host has been restored from
* backup. The intent contains information about how to translate AppWidget ids from the
* restored data to their new equivalents. If an application maintains multiple separate
- * widget hosts instances, it will receive this broadcast separately for each one.
+ * widget host instances, it will receive this broadcast separately for each one.
*
* <p>The intent will contain the following extras:
*
@@ -380,7 +380,7 @@
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
- * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast
+ * @see #ACTION_APPWIDGET_RESTORED
*/
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index e0d69e3..e489e05 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,10 +16,13 @@
package android.net;
+import android.net.NetworkUtils;
import android.os.Parcelable;
import android.os.Parcel;
+import java.io.IOException;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
@@ -38,6 +41,8 @@
*/
public final int netId;
+ private NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
+
/**
* @hide
*/
@@ -79,6 +84,59 @@
}
/**
+ * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
+ */
+ private class NetworkBoundSocketFactory extends SocketFactory {
+ private final int mNetId;
+
+ public NetworkBoundSocketFactory(int netId) {
+ super();
+ mNetId = netId;
+ }
+
+ @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));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ Socket socket = createSocket();
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(address, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket socket = createSocket();
+ socket.connect(new InetSocketAddress(host, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ Socket socket = createSocket();
+ socket.connect(new InetSocketAddress(host, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ Socket socket = new Socket();
+ // Query a property of the underlying socket to ensure the underlying
+ // socket exists so a file descriptor is available to bind to a network.
+ socket.getReuseAddress();
+ NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId);
+ return socket;
+ }
+ }
+
+ /**
* Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by
* this factory will have its traffic sent over this {@code Network}. Note that if this
* {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
@@ -88,7 +146,10 @@
* {@code Network}.
*/
public SocketFactory socketFactory() {
- return null;
+ if (mNetworkBoundSocketFactory == null) {
+ mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
+ }
+ return mNetworkBoundSocketFactory;
}
/**
@@ -99,6 +160,29 @@
* doesn't accidentally use sockets it thinks are still bound to a particular {@code Network}.
*/
public void bindProcess() {
+ NetworkUtils.bindProcessToNetwork(netId);
+ }
+
+ /**
+ * Binds host resolutions performed by this process to this network. {@link #bindProcess}
+ * takes precedence over this setting.
+ *
+ * @hide
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public void bindProcessForHostResolution() {
+ NetworkUtils.bindProcessToNetworkForHostResolution(netId);
+ }
+
+ /**
+ * Clears any process specific {@link Network} binding for host resolution. This does
+ * not clear bindings enacted via {@link #bindProcess}.
+ *
+ * @hide
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public void unbindProcessForHostResolution() {
+ NetworkUtils.unbindProcessToNetworkForHostResolution();
}
/**
@@ -107,7 +191,7 @@
* @return {@code Network} to which this process is bound.
*/
public static Network getProcessBoundNetwork() {
- return null;
+ return new Network(NetworkUtils.getNetworkBoundToProcess());
}
/**
@@ -115,6 +199,7 @@
* {@link Network#bindProcess}.
*/
public static void unbindProcess() {
+ NetworkUtils.unbindProcessToNetwork();
}
// implement the Parcelable interface
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index b24d396..edb3286 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -109,6 +109,50 @@
public native static void markSocket(int socketfd, int mark);
/**
+ * Binds the current process to the network designated by {@code netId}. All sockets created
+ * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
+ * {@link Network#socketFactory}) will be bound to this network. Note that if this
+ * {@code Network} ever disconnects all sockets created in this way will cease to work. This
+ * is by design so an application doesn't accidentally use sockets it thinks are still bound to
+ * a particular {@code Network}.
+ */
+ public native static void bindProcessToNetwork(int netId);
+
+ /**
+ * Clear any process specific {@code Network} binding. This reverts a call to
+ * {@link #bindProcessToNetwork}.
+ */
+ public native static void unbindProcessToNetwork();
+
+ /**
+ * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
+ * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
+ */
+ public native static int getNetworkBoundToProcess();
+
+ /**
+ * Binds host resolutions performed by this process to the network designated by {@code netId}.
+ * {@link #bindProcessToNetwork} takes precedence over this setting.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void bindProcessToNetworkForHostResolution(int netId);
+
+ /**
+ * Clears any process specific {@link Network} binding for host resolution. This does
+ * not clear bindings enacted via {@link #bindProcessToNetwork}.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void unbindProcessToNetworkForHostResolution();
+
+ /**
+ * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
+ * overrides any binding via {@link #bindProcessToNetwork}.
+ */
+ public native static void bindSocketToNetwork(int socketfd, int netId);
+
+ /**
* Convert a IPv4 address from an integer to an InetAddress.
* @param hostAddress an int corresponding to the IPv4 address in network byte order
*/
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4963991..68b91cb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -58,24 +58,6 @@
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
public class StorageManager {
-
- /// Consts to match the password types in cryptfs.h
- /** Master key is encrypted with a password.
- */
- public static final int CRYPT_TYPE_PASSWORD = 0;
-
- /** Master key is encrypted with the default password.
- */
- public static final int CRYPT_TYPE_DEFAULT = 1;
-
- /** Master key is encrypted with a pattern.
- */
- public static final int CRYPT_TYPE_PATTERN = 2;
-
- /** Master key is encrypted with a pin.
- */
- public static final int CRYPT_TYPE_PIN = 3;
-
private static final String TAG = "StorageManager";
private final ContentResolver mResolver;
@@ -663,4 +645,14 @@
return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
DEFAULT_FULL_THRESHOLD_BYTES);
}
+
+ /// Consts to match the password types in cryptfs.h
+ /** @hide */
+ public static final int CRYPT_TYPE_PASSWORD = 0;
+ /** @hide */
+ public static final int CRYPT_TYPE_DEFAULT = 1;
+ /** @hide */
+ public static final int CRYPT_TYPE_PATTERN = 2;
+ /** @hide */
+ public static final int CRYPT_TYPE_PIN = 3;
}
diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java
index e4f93a8..0d90a16 100644
--- a/core/java/android/provider/TvContract.java
+++ b/core/java/android/provider/TvContract.java
@@ -74,7 +74,7 @@
*
* @hide
*/
- public static final String PARAM_BROWSABLE_ONLY = "browable_only";
+ public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
/**
* Builds a URI that points to a specific channel.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 34d1f0e..af16185 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -120,6 +120,7 @@
boolean isKeyguardSecure();
boolean inKeyguardRestrictedInputMode();
void dismissKeyguard();
+ void keyguardGoingAway();
void closeSystemDialogs(String reason);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 20194eb..d45d686 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -608,8 +608,15 @@
* Return whether the given window should forcibly hide everything
* behind it. Typically returns true for the keyguard.
*/
- public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs);
-
+ public boolean doesForceHide(WindowManager.LayoutParams attrs);
+
+
+ /**
+ * Return whether the given window can become one that passes doesForceHide() test.
+ * Typically returns true for the StatusBar.
+ */
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
/**
* Determine if a window that is behind one that is force hiding
* (as determined by {@link #doesForceHide}) should actually be hidden.
@@ -618,7 +625,7 @@
* will conflict with what you set.
*/
public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs);
-
+
/**
* Called when the system would like to show a UI to indicate that an
* application is starting. You can use this to add a
@@ -1149,12 +1156,6 @@
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
- * Show the recents task list app.
- * @hide
- */
- public void showRecentApps();
-
- /**
* @return The current height of the input method window.
*/
public int getInputMethodWindowVisibleHeightLw();
@@ -1195,4 +1196,9 @@
* @return True if the window is a top level one.
*/
public boolean isTopLevelWindow(int windowType);
+
+ /**
+ * Notifies the keyguard to start fading out.
+ */
+ public void startKeyguardExitAnimation(long fadeoutDuration);
}
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 8c67bb7..5033bee 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -592,7 +592,7 @@
*/
public void setNavigationContentDescription(int resId) {
ensureNavButtonView();
- mNavButtonView.setContentDescription(getContext().getText(resId));
+ mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null);
}
/**
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index a0c75a6..5c7a4e6 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -342,7 +342,7 @@
@Override
public void setCustomView(int resId) {
setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId,
- (ViewGroup) mDecorToolbar, false));
+ mDecorToolbar.getViewGroup(), false));
}
@Override
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index b78c70f..f22800c 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -56,4 +56,10 @@
oneway void dispatch(in MotionEvent event);
oneway void launchCamera();
oneway void onBootCompleted();
+
+ /**
+ * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper
+ * and keyguard flag.
+ */
+ oneway void startKeyguardExitAnimation(long fadeoutDuration);
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index a159715..f446c3a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -173,8 +173,10 @@
$(call include-path-for, bluedroid) \
$(call include-path-for, libhardware)/hardware \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
+ $(TOP)/bionic/libc/dns/include \
$(TOP)/frameworks/av/include \
$(TOP)/system/media/camera/include \
+ $(TOP)/system/netd/include \
external/pdfium/core/include/fpdfapi \
external/pdfium/core/include/fpdfdoc \
external/pdfium/fpdfsdk/include \
@@ -233,6 +235,7 @@
libaudioutils \
libpdfium \
libimg_utils \
+ libnetd_client \
ifeq ($(USE_OPENGL_RENDERER),true)
LOCAL_SHARED_LIBRARIES += libhwui
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 6d23c32..bc5e1b3 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,6 +18,8 @@
#include "jni.h"
#include "JNIHelp.h"
+#include "NetdClient.h"
+#include "resolv_netid.h"
#include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
@@ -250,6 +252,36 @@
}
}
+static void android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+{
+ setNetworkForProcess(netId);
+}
+
+static void android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
+{
+ setNetworkForProcess(NETID_UNSET);
+}
+
+static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
+{
+ return getNetworkForProcess();
+}
+
+static void android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, jint netId)
+{
+ setNetworkForResolv(netId);
+}
+
+static void android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
+{
+ setNetworkForResolv(NETID_UNSET);
+}
+
+static void android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, jint netId)
+{
+ setNetworkForSocket(netId, socket);
+}
+
// ----------------------------------------------------------------------------
/*
@@ -267,6 +299,12 @@
{ "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease },
{ "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
{ "markSocket", "(II)V", (void*) android_net_utils_markSocket },
+ { "bindProcessToNetwork", "(I)V", (void*) android_net_utils_bindProcessToNetwork },
+ { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
+ { "unbindProcessToNetwork", "()V", (void*) android_net_utils_unbindProcessToNetwork },
+ { "bindProcessToNetworkForHostResolution", "(I)V", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
+ { "unbindProcessToNetworkForHostResolution", "()V", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
+ { "bindSocketToNetwork", "(II)V", (void*) android_net_utils_bindSocketToNetwork },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index cb47b3c..4a956d7 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -20,9 +20,8 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#ff000000" android:shareInterpolator="false">
<alpha
- android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:fromAlpha="1.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
android:interpolator="@interpolator/decelerate_quint"
- android:startOffset="@android:integer/config_shortAnimTime"
- android:duration="@android:integer/config_shortAnimTime"/>
+ android:duration="0"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
index c29fd1a..f7a6a65 100644
--- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
@@ -23,6 +23,6 @@
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
android:interpolator="@interpolator/decelerate_quad"
- android:startOffset="@android:integer/config_shortAnimTime"
+ android:startOffset="@android:integer/config_mediumAnimTime"
android:duration="@android:integer/config_shortAnimTime"/>
</set>
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index ea32681..6943533 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -783,7 +783,6 @@
<item name="itemPadding">8dip</item>
<item name="homeLayout">@layout/action_bar_home_quantum</item>
<item name="gravity">center_vertical</item>
- <item name="contentInsetStart">56dp</item>
</style>
<style name="Widget.Quantum.ActionBar.Solid">
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index fe51215..afc2931 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -1032,11 +1032,14 @@
float[] redOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_RED);
float[] greenOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_GREEN);
float[] blueOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_BLUE);
- assertTrue("Input and output tonemap curve should match", Arrays.equals(red, redOut));
- assertTrue("Input and output tonemap curve should match", Arrays.equals(green, greenOut));
- assertTrue("Input and output tonemap curve should match", Arrays.equals(blue, blueOut));
+ assertArrayEquals(red, redOut);
+ assertArrayEquals(green, greenOut);
+ assertArrayEquals(blue, blueOut);
TonemapCurve tcOut = mMetadata.get(CaptureResult.TONEMAP_CURVE);
- assertTrue("Input and output tonemap curve should match", tcIn.equals(tcOut));
+ assertEquals(tcIn, tcOut);
+ mMetadata.set(CaptureResult.TONEMAP_CURVE_GREEN, null);
+ // If any of channel has null curve, return a null TonemapCurve
+ assertNull(mMetadata.get(CaptureResult.TONEMAP_CURVE));
}
/**
diff --git a/packages/SystemUI/res/layout/volume_panel.xml b/packages/SystemUI/res/layout/volume_panel.xml
index bc7288d..046862f 100644
--- a/packages/SystemUI/res/layout/volume_panel.xml
+++ b/packages/SystemUI/res/layout/volume_panel.xml
@@ -24,6 +24,7 @@
android:id="@+id/slider_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="64dip"
android:layout_toLeftOf="@+id/expand_button_divider" />
<ImageView
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index ae04bf5..0936cc2 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -21,7 +21,9 @@
android:layout_height="wrap_content"
android:background="@color/system_primary_color"
android:orientation="vertical"
- android:padding="@dimen/qs_panel_padding" >
+ android:paddingTop="@dimen/qs_panel_padding"
+ android:paddingLeft="@dimen/qs_panel_padding"
+ android:paddingRight="@dimen/qs_panel_padding" >
<TextView
android:id="@android:id/title"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 41c0e78..4c7f3df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -202,6 +202,12 @@
checkPermission();
mKeyguardViewMediator.onBootCompleted();
}
+
+ @Override
+ public void startKeyguardExitAnimation(long fadeoutDuration) {
+ checkPermission();
+ mKeyguardViewMediator.startKeyguardExitAnimation(fadeoutDuration);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b2872fa..7110d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -46,7 +46,8 @@
import android.util.Log;
import android.util.Slog;
import android.view.ViewGroup;
-import android.view.WindowManager;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import com.android.internal.policy.IKeyguardExitCallback;
@@ -137,6 +138,7 @@
private static final int SET_OCCLUDED = 12;
private static final int KEYGUARD_TIMEOUT = 13;
private static final int DISMISS = 17;
+ private static final int START_KEYGUARD_EXIT_ANIM = 18;
/**
* The default amount of time we stay awake (used for all key input)
@@ -180,6 +182,9 @@
/** High level access to the power manager for WakeLocks */
private PowerManager mPM;
+ /** High level access to the window manager for dismissing keyguard animation */
+ private IWindowManager mWM;
+
/** UserManager for querying number of users */
private UserManager mUserManager;
@@ -440,6 +445,7 @@
private void setup() {
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWM = WindowManagerGlobal.getWindowManagerService();
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
@@ -1076,6 +1082,9 @@
case DISMISS:
handleDismiss();
break;
+ case START_KEYGUARD_EXIT_ANIM:
+ handleStartKeyguardExitAnimation((Long) msg.obj);
+ break;
}
}
};
@@ -1207,6 +1216,19 @@
private void handleHide() {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
+ try {
+
+ // Don't actually hide the Keyguard at the moment, wait for window manager until
+ // it tells us it's safe to do so with startKeyguardExitAnimation.
+ mWM.keyguardGoingAway();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while calling WindowManager", e);
+ }
+ }
+ }
+
+ private void handleStartKeyguardExitAnimation(long fadeoutDuration) {
+ synchronized (KeyguardViewMediator.this) {
// only play "unlock" noises if not on a call (since the incall UI
// disables the keyguard)
@@ -1324,6 +1346,11 @@
return mStatusBarKeyguardViewManager;
}
+ public void startKeyguardExitAnimation(long fadeoutDuration) {
+ Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, fadeoutDuration);
+ mHandler.sendMessage(msg);
+ }
+
public ViewMediatorCallback getViewMediatorCallback() {
return mViewMediatorCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index bdac7a0..626fc0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -191,15 +191,23 @@
final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
record.tileView.measure(exactly(cw), exactly(ch));
}
- final int actualHeight = rows == 0 ? 0 : getRowTop(rows);
- mDetail.measure(exactly(width), exactly(actualHeight));
- setMeasuredDimension(width, actualHeight);
+ int h = rows == 0 ? 0 : getRowTop(rows);
+ mDetail.measure(exactly(width), unspecified());
+ if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) {
+ final int dmh = mDetail.getMeasuredHeight();
+ if (dmh > 0) h = dmh;
+ }
+ setMeasuredDimension(width, h);
}
private static int exactly(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
+ private static int unspecified() {
+ return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
index 267786b..20bbf8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
@@ -108,11 +108,7 @@
@Override
protected void handleClick() {
- if (mState.zen) {
- mZenController.setZen(false);
- } else {
- showDetail(true);
- }
+ showDetail(true);
}
@Override
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 0c27ab0..802e5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -164,7 +164,9 @@
// Calculate quick setting heights.
mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
- if (!mQsExpanded) {
+ if (mQsExpanded) {
+ setQsStackScrollerPadding(mQsMaxExpansionHeight);
+ } else {
setQsExpansion(mQsMinExpansionHeight);
positionClockAndNotifications();
mNotificationStackScroller.setStackHeight(getExpandedHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 06f4c2e..67f3a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -1035,8 +1035,8 @@
if (isShowing()) {
if (mDialog != null) {
mDialog.dismiss();
+ mActiveStreamType = -1;
}
- mActiveStreamType = -1;
}
synchronized (sConfirmSafeVolumeLock) {
if (sConfirmSafeVolumeDialog != null) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 0754b12..8e68dfc 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -483,7 +483,6 @@
private static final int MSG_DISABLE_POINTER_LOCATION = 2;
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
- private static final int MSG_DISPATCH_SHOW_RECENTS = 5;
private class PolicyHandler extends Handler {
@Override
@@ -501,9 +500,6 @@
case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK:
dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
break;
- case MSG_DISPATCH_SHOW_RECENTS:
- showRecentApps(false);
- break;
}
}
}
@@ -1576,11 +1572,16 @@
}
@Override
- public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs) {
+ public boolean doesForceHide(WindowManager.LayoutParams attrs) {
return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
}
@Override
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
+ return attrs.type == TYPE_STATUS_BAR;
+ }
+
+ @Override
public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_STATUS_BAR:
@@ -2467,12 +2468,6 @@
}
}
- @Override
- public void showRecentApps() {
- mHandler.removeMessages(MSG_DISPATCH_SHOW_RECENTS);
- mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_RECENTS);
- }
-
private void showRecentApps(boolean triggeredFromAltTab) {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
try {
@@ -4570,6 +4565,18 @@
}
}
+ @Override
+ public void startKeyguardExitAnimation(final long fadeoutDuration) {
+ if (mKeyguardDelegate != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardDelegate.startKeyguardExitAnimation(fadeoutDuration);
+ }
+ });
+ }
+ }
+
void sendCloseSystemWindows() {
sendCloseSystemWindows(mContext, null);
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
index 966924b..faf7020 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
@@ -274,6 +274,12 @@
mKeyguardState.currentUser = newUserId;
}
+ public void startKeyguardExitAnimation(long fadeoutDuration) {
+ if (mKeyguardService != null) {
+ mKeyguardService.startKeyguardExitAnimation(fadeoutDuration);
+ }
+ }
+
private static final View createScrim(Context context) {
View view = new View(context);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
index 7cb48fa..f236ce7 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
@@ -190,6 +190,14 @@
}
}
+ public void startKeyguardExitAnimation(long fadeoutDuration) {
+ try {
+ mService.startKeyguardExitAnimation(fadeoutDuration);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
public void showAssistant() {
// Not used by PhoneWindowManager
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1e21e1c..5527528 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5606,16 +5606,23 @@
boolean isNewDefault = false;
if (DBG) log("handleConnectionValidated for "+newNetwork.name());
// check if any NetworkRequest wants this NetworkAgent
- // first check if it satisfies the NetworkCapabilities
ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
if (VDBG) log(" new Network has: " + newNetwork.networkCapabilities);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (newNetwork == currentNetwork) {
+ if (VDBG) log("Network " + newNetwork.name() + " was already satisfying" +
+ " request " + nri.request.requestId + ". No change.");
+ keep = true;
+ continue;
+ }
+
+ // check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
newNetwork.networkCapabilities)) {
// next check if it's better than any current network we're using for
// this request
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
if (VDBG) {
log("currentScore = " +
(currentNetwork != null ? currentNetwork.currentScore : 0) +
@@ -5744,12 +5751,19 @@
}
if (state == NetworkInfo.State.CONNECTED) {
- // TODO - check if we want it (optimization)
try {
+ // This is likely caused by the fact that this network already
+ // exists. An example is when a network goes from CONNECTED to
+ // CONNECTING and back (like wifi on DHCP renew).
+ // TODO: keep track of which networks we've created, or ask netd
+ // to tell us whether we've already created this network or not.
mNetd.createNetwork(networkAgent.network.netId);
} catch (Exception e) {
- loge("Error creating Network " + networkAgent.network.netId);
+ loge("Error creating network " + networkAgent.network.netId + ": "
+ + e.getMessage());
+ return;
}
+
updateLinkProperties(networkAgent, null);
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8f60b03..1804d03 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -30,10 +30,6 @@
import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY;
import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
@@ -1067,6 +1063,40 @@
}
}
+ /**
+ * Determine if home should be visible below the passed record.
+ * @param record activity we are querying for.
+ * @return true if home is visible below the passed activity, false otherwise.
+ */
+ boolean isActivityOverHome(ActivityRecord record) {
+ // Start at record and go down, look for either home or a visible fullscreen activity.
+ final TaskRecord recordTask = record.task;
+ for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
+ TaskRecord task = mTaskHistory.get(taskNdx);
+ final ArrayList<ActivityRecord> activities = task.mActivities;
+ final int startNdx =
+ task == recordTask ? activities.indexOf(record) : activities.size() - 1;
+ for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (r.isHomeActivity()) {
+ return true;
+ }
+ if (!r.finishing && r.fullscreen) {
+ // Passed activity is over a fullscreen activity.
+ return false;
+ }
+ }
+ if (task.mOnTopOfHome) {
+ // Got to the bottom of a task on top of home without finding a visible fullscreen
+ // activity. Home is visible.
+ return true;
+ }
+ }
+ // Got to the bottom of this stack and still don't know. If this is over the home stack
+ // then record is over home. May not work if we ever get more than two layers.
+ return mStackSupervisor.isFrontStack(this);
+ }
+
private void setVisibile(ActivityRecord r, boolean visible) {
r.visible = visible;
mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1096,8 +1126,7 @@
for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks();
for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) {
- final TaskRecord task = tasks.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
+ final ArrayList<ActivityRecord> activities = tasks.get(taskNdx).mActivities;
for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) {
final ActivityRecord r = activities.get(activityNdx);
@@ -1108,7 +1137,7 @@
// - Full Screen Activity OR
// - On top of Home and our stack is NOT home
if (!r.finishing && r.visible && (r.fullscreen ||
- (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()))) {
+ (!isHomeStack() && r.frontOfTask && tasks.get(taskNdx).mOnTopOfHome))) {
return false;
}
}
@@ -1236,7 +1265,7 @@
// At this point, nothing else needs to be shown
if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
behindFullscreen = true;
- } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+ } else if (!isHomeStack() && r.frontOfTask && task.mOnTopOfHome) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
behindFullscreen = true;
}
@@ -1390,7 +1419,6 @@
final boolean userLeaving = mStackSupervisor.mUserLeaving;
mStackSupervisor.mUserLeaving = false;
- final TaskRecord prevTask = prev != null ? prev.task : null;
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
@@ -1398,10 +1426,7 @@
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
// Only resume home if on home display
- final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
- HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
- return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
+ return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
}
next.delayedResume = false;
@@ -1420,24 +1445,22 @@
}
final TaskRecord nextTask = next.task;
+ final TaskRecord prevTask = prev != null ? prev.task : null;
if (prevTask != null && prevTask.stack == this &&
- prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
+ prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
prevTask.setFrontOfTask();
} else if (prevTask != topTask()) {
- // This task is going away but it was supposed to return to the home stack.
+ // This task is going away but it was supposed to return to the home task.
// Now the task above it has to return to the home task instead.
final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
- mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ mTaskHistory.get(taskNdx).mOnTopOfHome = true;
} else {
if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG,
"resumeTopActivityLocked: Launching home next");
// Only resume home if on home display
- final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
- HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
- return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
+ return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
}
}
@@ -1808,11 +1831,10 @@
ActivityStack lastStack = mStackSupervisor.getLastStack();
final boolean fromHome = lastStack.isHomeStack();
if (!isHomeStack() && (fromHome || topTask() != task)) {
- task.setTaskToReturnTo(fromHome ?
- lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
+ task.mOnTopOfHome = fromHome;
}
} else {
- task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+ task.mOnTopOfHome = false;
}
mTaskHistory.remove(task);
@@ -2357,8 +2379,8 @@
ActivityRecord next = topRunningActivityLocked(null);
if (next != r) {
final TaskRecord task = r.task;
- if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
+ if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) {
+ mStackSupervisor.moveHomeToTop();
}
}
ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
@@ -2842,9 +2864,8 @@
if (task != null && task.removeActivity(r)) {
if (DEBUG_STACK) Slog.i(TAG,
"removeActivityFromHistoryLocked: last activity removed from " + this);
- if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
- task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
+ if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) {
+ mStackSupervisor.moveHomeToTop();
}
removeTask(task);
}
@@ -3159,13 +3180,12 @@
}
}
- void moveHomeStackTaskToTop(int homeStackTaskType) {
+ void moveHomeTaskToTop() {
final int top = mTaskHistory.size() - 1;
for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.taskType == homeStackTaskType) {
- if (DEBUG_TASKS || DEBUG_STACK)
- Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task);
+ if (task.isHomeTask()) {
+ if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
mTaskHistory.remove(taskNdx);
mTaskHistory.add(top, task);
updateTaskMovement(task, true);
@@ -3277,12 +3297,12 @@
int numTasks = mTaskHistory.size();
for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.isOverHomeStack()) {
+ if (task.mOnTopOfHome) {
break;
}
if (taskNdx == 1) {
// Set the last task before tr to go to home.
- task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ task.mOnTopOfHome = true;
}
}
@@ -3303,10 +3323,9 @@
}
final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
- if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) {
- final int taskToReturnTo = tr.getTaskToReturnTo();
- tr.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
- return mStackSupervisor.resumeHomeStackTask(taskToReturnTo, null);
+ if (task == tr && tr.mOnTopOfHome || numTasks <= 1 && isOnHomeDisplay()) {
+ tr.mOnTopOfHome = false;
+ return mStackSupervisor.resumeHomeActivity(null);
}
mStackSupervisor.resumeTopActivitiesLocked();
@@ -3747,11 +3766,8 @@
final int taskNdx = mTaskHistory.indexOf(task);
final int topTaskNdx = mTaskHistory.size() - 1;
- if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
- final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
- if (!nextTask.isOverHomeStack()) {
- nextTask.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
- }
+ if (task.mOnTopOfHome && taskNdx < topTaskNdx) {
+ mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
}
mTaskHistory.remove(task);
updateTaskMovement(task, true);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 6d20a32..0b1c2b8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -31,9 +31,6 @@
import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.am.ActivityManagerService.TAG;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import android.app.Activity;
import android.app.ActivityManager;
@@ -322,27 +319,18 @@
}
}
- void moveHomeStackTaskToTop(int homeStackTaskType) {
- if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
- mWindowManager.showRecentApps();
- return;
- }
+ void moveHomeToTop() {
moveHomeStack(true);
- mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
+ mHomeStack.moveHomeTaskToTop();
}
- boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev) {
- if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
- mWindowManager.showRecentApps();
- return false;
- }
- moveHomeStackTaskToTop(homeStackTaskType);
+ boolean resumeHomeActivity(ActivityRecord prev) {
+ moveHomeToTop();
if (prev != null) {
- prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+ prev.task.mOnTopOfHome = false;
}
-
ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
- if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) {
+ if (r != null && r.isHomeActivity()) {
mService.setFocusedActivityLocked(r);
return resumeTopActivitiesLocked(mHomeStack, prev, null);
}
@@ -696,7 +684,7 @@
}
void startHomeActivity(Intent intent, ActivityInfo aInfo) {
- moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE);
+ moveHomeToTop();
startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
null, false, null, null);
}
@@ -1631,7 +1619,7 @@
(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity.
- intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ intentActivity.task.mOnTopOfHome = true;
}
options = null;
}
@@ -1816,11 +1804,6 @@
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
voiceSession, voiceInteractor, true), null, true);
- if (sourceRecord == null) {
- // Launched from a service or notification or task that is finishing.
- r.task.setTaskToReturnTo(isFrontStack(mHomeStack) ?
- mHomeStack.topTask().taskType : RECENTS_ACTIVITY_TYPE);
- }
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1832,7 +1815,7 @@
== (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity, so before starting
// their own activity we will bring home to the front.
- r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ r.task.mOnTopOfHome = r.task.stack.isOnHomeDisplay();
}
}
} else if (sourceRecord != null) {
@@ -2183,7 +2166,7 @@
if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
// Caller wants the home activity moved with it. To accomplish this,
// we'll just indicate that this task returns to the home task.
- task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ task.mOnTopOfHome = true;
}
task.stack.moveTaskToFrontLocked(task, null, options);
if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
@@ -2294,7 +2277,7 @@
}
mWindowManager.addTask(taskId, stackId, false);
}
- resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
+ resumeHomeActivity(null);
}
void moveTaskToStack(int taskId, int stackId, boolean toTop) {
@@ -2556,7 +2539,7 @@
}
} else {
// Stack was moved to another display while user was swapped out.
- resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
+ resumeHomeActivity(null);
}
return homeInFront;
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c07bc1e..ce83ae6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -17,9 +17,6 @@
package com.android.server.am;
import static com.android.server.am.ActivityManagerService.TAG;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
import android.app.Activity;
@@ -57,6 +54,7 @@
private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_TASKTYPE = "task_type";
+ private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
@@ -106,11 +104,9 @@
/** True if persistable, has changed, and has not yet been persisted */
boolean needsPersisting = false;
-
- /** Indication of what to run next when task exits. Use ActivityRecord types.
- * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the
- * task stack. */
- private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
+ /** Launch the home activity when leaving this task. Will be false for tasks that are not on
+ * Display.DEFAULT_DISPLAY. */
+ boolean mOnTopOfHome = false;
final ActivityManagerService mService;
@@ -127,8 +123,9 @@
TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
String _affinity, ComponentName _realActivity, ComponentName _origActivity,
- boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
- String _lastDescription, ArrayList<ActivityRecord> activities, long lastTimeMoved) {
+ boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
+ int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
+ long lastTimeMoved) {
mService = service;
taskId = _taskId;
intent = _intent;
@@ -141,7 +138,7 @@
rootWasReset = _rootWasReset;
askedCompatMode = _askedCompatMode;
taskType = _taskType;
- mTaskToReturnTo = HOME_ACTIVITY_TYPE;
+ mOnTopOfHome = _onTopOfHome;
userId = _userId;
lastDescription = _lastDescription;
mActivities = activities;
@@ -209,14 +206,6 @@
}
}
- void setTaskToReturnTo(int taskToReturnTo) {
- mTaskToReturnTo = taskToReturnTo;
- }
-
- int getTaskToReturnTo() {
- return mTaskToReturnTo;
- }
-
void disposeThumbnail() {
super.disposeThumbnail();
for (int i=mActivities.size()-1; i>=0; i--) {
@@ -488,15 +477,11 @@
}
boolean isHomeTask() {
- return taskType == HOME_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.HOME_ACTIVITY_TYPE;
}
boolean isApplicationTask() {
- return taskType == APPLICATION_ACTIVITY_TYPE;
- }
-
- boolean isOverHomeStack() {
- return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
}
public TaskAccessInfo getTaskAccessInfoLocked() {
@@ -638,6 +623,7 @@
out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
out.attribute(null, ATTR_USERID, String.valueOf(userId));
out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
+ out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
if (lastDescription != null) {
out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
@@ -683,6 +669,7 @@
boolean rootHasReset = false;
boolean askedCompatMode = false;
int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+ boolean onTopOfHome = true;
int userId = 0;
String lastDescription = null;
long lastTimeOnTop = 0;
@@ -710,6 +697,8 @@
userId = Integer.valueOf(attrValue);
} else if (ATTR_TASKTYPE.equals(attrName)) {
taskType = Integer.valueOf(attrValue);
+ } else if (ATTR_ONTOPOFHOME.equals(attrName)) {
+ onTopOfHome = Boolean.valueOf(attrValue);
} else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
lastDescription = attrValue;
} else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
@@ -747,7 +736,8 @@
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, realActivity, origActivity, rootHasReset,
- askedCompatMode, taskType, userId, lastDescription, activities, lastTimeOnTop);
+ askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
+ lastTimeOnTop);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
@@ -766,7 +756,7 @@
pw.print(" userId="); pw.print(userId);
pw.print(" taskType="); pw.print(taskType);
pw.print(" numFullscreen="); pw.print(numFullscreen);
- pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+ pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
}
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
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/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 266527d..6fdd535 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -36,6 +36,7 @@
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
+import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import com.android.server.wm.WindowManagerService.LayoutFields;
@@ -50,6 +51,9 @@
public class WindowAnimator {
private static final String TAG = "WindowAnimator";
+ /** How long to give statusbar to clear the private keyguard flag when animating out */
+ private static final long KEYGUARD_ANIM_TIMEOUT_MS = 1000;
+
final WindowManagerService mService;
final Context mContext;
final WindowManagerPolicy mPolicy;
@@ -82,6 +86,8 @@
boolean mInitialized = false;
+ boolean mKeyguardGoingAway;
+
// forceHiding states.
static final int KEYGUARD_NOT_SHOWN = 0;
static final int KEYGUARD_ANIMATING_IN = 1;
@@ -213,6 +219,29 @@
final WindowList windows = mService.getWindowListLocked(displayId);
ArrayList<WindowStateAnimator> unForceHiding = null;
boolean wallpaperInUnForceHiding = false;
+
+ if (mKeyguardGoingAway) {
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState win = windows.get(i);
+ if (!mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+ continue;
+ }
+ final WindowStateAnimator winAnimator = win.mWinAnimator;
+ if (mPolicy.doesForceHide(win.mAttrs)) {
+ if (!winAnimator.mAnimating) {
+ // Create a new animation to delay until keyguard is gone on its own.
+ winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f);
+ winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS);
+ winAnimator.mAnimationIsEntrance = false;
+ }
+ } else {
+ mKeyguardGoingAway = false;
+ winAnimator.clearAnimation();
+ }
+ break;
+ }
+ }
+
mForceHiding = KEYGUARD_NOT_SHOWN;
for (int i = windows.size() - 1; i >= 0; i--) {
@@ -239,7 +268,7 @@
}
}
- if (mPolicy.doesForceHide(win, win.mAttrs)) {
+ if (mPolicy.doesForceHide(win.mAttrs)) {
if (!wasAnimating && nowAnimating) {
if (WindowManagerService.DEBUG_ANIM ||
WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
@@ -252,6 +281,11 @@
getPendingLayoutChanges(displayId));
}
mService.mFocusMayChange = true;
+ } else if (mKeyguardGoingAway && !nowAnimating) {
+ // Timeout!!
+ Slog.e(TAG, "Timeout waiting for animation to startup");
+ mPolicy.startKeyguardExitAnimation(0);
+ mKeyguardGoingAway = false;
}
if (win.isReadyForDisplay()) {
if (nowAnimating) {
@@ -265,7 +299,7 @@
}
}
if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
- "Force hide " + mForceHiding
+ "Force hide " + forceHidingToString()
+ " hasSurface=" + win.mHasSurface
+ " policyVis=" + win.mPolicyVisibility
+ " destroying=" + win.mDestroying
@@ -349,12 +383,18 @@
// If we have windows that are being show due to them no longer
// being force-hidden, apply the appropriate animation to them.
if (unForceHiding != null) {
+ boolean startKeyguardExit = true;
for (int i=unForceHiding.size()-1; i>=0; i--) {
Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding);
if (a != null) {
final WindowStateAnimator winAnimator = unForceHiding.get(i);
winAnimator.setAnimation(a);
winAnimator.mAnimationIsEntrance = true;
+ if (startKeyguardExit) {
+ // Do one time only.
+ mPolicy.startKeyguardExitAnimation(a.getStartOffset());
+ startKeyguardExit = false;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 68fface..7382f4ca 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5139,6 +5139,18 @@
}
@Override
+ public void keyguardGoingAway() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+ }
+ synchronized (mWindowMap) {
+ mAnimator.mKeyguardGoingAway = true;
+ requestTraversalLocked();
+ }
+ }
+
+ @Override
public void closeSystemDialogs(String reason) {
synchronized(mWindowMap) {
final int numDisplays = mDisplayContents.size();
@@ -7161,9 +7173,7 @@
public static final int TAP_OUTSIDE_STACK = 31;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
- public static final int REMOVE_STARTING_TIMEOUT = 33;
-
- public static final int SHOW_DISPLAY_MASK = 34;
+ public static final int SHOW_DISPLAY_MASK = 33;
@Override
public void handleMessage(Message msg) {
@@ -10281,10 +10291,6 @@
mPolicy.lockNow(options);
}
- public void showRecentApps() {
- mPolicy.showRecentApps();
- }
-
@Override
public boolean isSafeModeEnabled() {
return mSafeMode;
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index cc621c4..105803e 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -826,6 +826,11 @@
return null;
}
+ @Override
+ public int[] extractThemeAttrs() {
+ return null;
+ }
+
/**
* Retrieve the raw TypedValue for the attribute at <var>index</var>.
*
@@ -912,4 +917,9 @@
public String toString() {
return Arrays.toString(mResourceData);
}
- }
+
+ static TypedArray obtain(Resources res, int len) {
+ return res instanceof BridgeResources ?
+ new BridgeTypedArray(((BridgeResources) res), null, len, true) : null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
index 5d89f83..faa8852 100644
--- a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
@@ -30,7 +30,6 @@
@LayoutlibDelegate
/*package*/ static TypedArray obtain(Resources res, int len) {
- // FIXME
- return null;
+ return BridgeTypedArray.obtain(res, len);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
index cdbbe46..610c867 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -79,13 +79,6 @@
return sManager.addNewDelegate(newDelegate);
}
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate(long native_shader, long native_bitmap,
- int shaderTileModeX, int shaderTileModeY) {
- // pass, not needed.
- return 0;
- }
-
// ---- Private delegate/helper methods ----
private BitmapShader_Delegate(java.awt.image.BufferedImage image,
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
index fae8aef..59ddcc6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -78,19 +78,6 @@
return sManager.addNewDelegate(newDelegate);
}
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate1(long native_shader, long native_skiaShaderA,
- long native_skiaShaderB, long native_mode) {
- // pass, not needed.
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate2(long native_shader, long native_skiaShaderA,
- long native_skiaShaderB, int porterDuffMode) {
- // pass, not needed.
- return 0;
- }
// ---- Private delegate/helper methods ----
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 5e7543a0..9ea4538 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -146,6 +146,8 @@
@LayoutlibDelegate
/*package*/ static void nUnrefFamily(long nativePtr) {
+ // Removing the java reference for the object doesn't mean that it's freed for garbage
+ // collection. Typeface_Delegate may still hold a reference for it.
sManager.removeJavaReferenceFor(nativePtr);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
index ac77377..55c4b98 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -71,22 +71,6 @@
tileMode);
}
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate1(LinearGradient thisGradient,
- long native_shader, float x0, float y0, float x1, float y1,
- int colors[], float positions[], int tileMode) {
- // nothing to be done here.
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate2(LinearGradient thisGradient,
- long native_shader, float x0, float y0, float x1, float y1,
- int color0, int color1, int tileMode) {
- // nothing to be done here.
- return 0;
- }
-
// ---- Private delegate/helper methods ----
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
index 4f16dcf..80179ee 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -68,20 +68,6 @@
tileMode);
}
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate1(long native_shader, float x, float y, float radius,
- int colors[], float positions[], int tileMode) {
- // nothing to be done here.
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate2(long native_shader, float x, float y, float radius,
- int color0, int color1, int tileMode) {
- // nothing to be done here.
- return 0;
- }
-
// ---- Private delegate/helper methods ----
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
index 70a0a43..14e9960 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
@@ -76,13 +76,12 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static void nativeDestructor(long native_shader, long native_skiaShader) {
+ /*package*/ static void nativeDestructor(long native_shader) {
sManager.removeJavaReferenceFor(native_shader);
}
@LayoutlibDelegate
- /*package*/ static void nativeSetLocalMatrix(long native_shader, long native_skiaShader,
- long matrix_instance) {
+ /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) {
// get the delegate from the native int.
Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader);
if (shaderDelegate == null) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
index f2b3e8d..95a57a9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -62,20 +62,6 @@
return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/);
}
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate1(long native_shader, float cx, float cy,
- int[] colors, float[] positions) {
- // nothing to be done here.
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativePostCreate2(long native_shader, float cx, float cy,
- int color0, int color1) {
- // nothing to be done here.
- return 0;
- }
-
// ---- Private delegate/helper methods ----
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index ed8f3b4..9746b48 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -54,7 +54,7 @@
// ---- delegate data ----
- private final long[] mFontFamilies; // the reference to FontFamily_Delegate.
+ private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate.
private int mStyle;
private static long sDefaultTypeface;
@@ -71,8 +71,7 @@
public List<Font> getFonts(boolean compact) {
List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
- for (long fontFamily : mFontFamilies) {
- FontFamily_Delegate ffd = FontFamily_Delegate.getDelegate(fontFamily);
+ for (FontFamily_Delegate ffd : mFontFamilies) {
if (ffd != null) {
Font font = ffd.getFont(mStyle, compact);
if (font != null) {
@@ -122,7 +121,11 @@
@LayoutlibDelegate
/*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) {
- Typeface_Delegate delegate = new Typeface_Delegate(familyArray, Typeface.NORMAL);
+ FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
+ for (int i = 0; i < familyArray.length; i++) {
+ fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]);
+ }
+ Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL);
return sManager.addNewDelegate(delegate);
}
@@ -153,9 +156,8 @@
// ---- Private delegate/helper methods ----
- private Typeface_Delegate(long[] fontFamilies, int style) {
+ private Typeface_Delegate(FontFamily_Delegate[] fontFamilies, int style) {
mFontFamilies = fontFamilies;
mStyle = style;
}
-
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 757cdd2..3bf2b20 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -72,7 +72,7 @@
@Override
public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
- boolean arg5, boolean arg6, int arg7, int arg8)
+ boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9)
throws RemoteException {
// TODO Auto-generated method stub
@@ -439,6 +439,10 @@
}
@Override
+ public void keyguardGoingAway() throws RemoteException {
+ }
+
+ @Override
public void lockNow(Bundle options) {
// TODO Auto-generated method stub
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
index 936ab4f..e59ccd7 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -171,7 +171,7 @@
// Set action bar to be split, if needed.
ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar);
mActionBarView.setSplitView(splitView);
- mActionBarView.setSplitActionBar(mSplit);
+ mActionBarView.setSplitToolbar(mSplit);
inflateMenus();
diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py
index ea3dccc..aea3241 100755
--- a/tools/layoutlib/rename_font/build_font.py
+++ b/tools/layoutlib/rename_font/build_font.py
@@ -15,10 +15,10 @@
# limitations under the License.
"""
-Rename the PS name of all fonts in the input directory and copy them to the
+Rename the PS name of all fonts in the input directories and copy them to the
output directory.
-Usage: build_font.py /path/to/input_fonts/ /path/to/output_fonts/
+Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/
"""
@@ -30,50 +30,86 @@
from lxml import etree
import shutil
import glob
+from multiprocessing import Pool
+
+# global variable
+dest_dir = '/tmp'
def main(argv):
- if len(argv) != 2:
- print "Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/"
- sys.exit(1)
- if not os.path.isdir(argv[0]):
- print argv[0] + "is not a valid directory"
- sys.exit(1)
- if not os.path.isdir(argv[1]):
- print argv[1] + "is not a valid directory"
- sys.exit(1)
+ if len(argv) < 2:
+ sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/')
+ for directory in argv:
+ if not os.path.isdir(directory):
+ sys.exit(directory + ' is not a valid directory')
+ global dest_dir
+ dest_dir = argv[-1]
+ src_dirs = argv[:-1]
cwd = os.getcwd()
- os.chdir(argv[1])
+ os.chdir(dest_dir)
files = glob.glob('*')
for filename in files:
os.remove(filename)
os.chdir(cwd)
- for filename in os.listdir(argv[0]):
- if not os.path.splitext(filename)[1].lower() == ".ttf":
- shutil.copy(os.path.join(argv[0], filename), argv[1])
- continue
- print os.path.join(argv[0], filename)
- old_ttf_path = os.path.join(argv[0], filename)
+ input_fonts = list()
+ for src_dir in src_dirs:
+ for dirname, dirnames, filenames in os.walk(src_dir):
+ for filename in filenames:
+ input_path = os.path.join(dirname, filename)
+ extension = os.path.splitext(filename)[1].lower()
+ if (extension == '.ttf'):
+ input_fonts.append(input_path)
+ elif (extension == '.xml'):
+ shutil.copy(input_path, dest_dir)
+ if '.git' in dirnames:
+ # don't go into any .git directories.
+ dirnames.remove('.git')
+ # Create as many threads as the number of CPUs
+ pool = Pool(processes=None)
+ pool.map(convert_font, input_fonts)
+
+
+class InvalidFontException(Exception):
+ pass
+
+def convert_font(input_path):
+ filename = os.path.basename(input_path)
+ print 'Converting font: ' + filename
+ # the path to the output file. The file name is the fontfilename.ttx
+ ttx_path = os.path.join(dest_dir, filename)
+ ttx_path = ttx_path[:-1] + 'x'
+ try:
# run ttx to generate an xml file in the output folder which represents all
# its info
- ttx_args = ["-d", argv[1], old_ttf_path]
+ ttx_args = ['-q', '-d', dest_dir, input_path]
ttx.main(ttx_args)
- # the path to the output file. The file name is the fontfilename.ttx
- ttx_path = os.path.join(argv[1], filename)
- ttx_path = ttx_path[:-1] + "x"
# now parse the xml file to change its PS name.
tree = etree.parse(ttx_path)
encoding = tree.docinfo.encoding
root = tree.getroot()
for name in root.iter('name'):
[old_ps_name, version] = get_font_info(name)
- new_ps_name = old_ps_name + version
- update_name(name, new_ps_name)
+ if old_ps_name is not None and version is not None:
+ new_ps_name = old_ps_name + version
+ update_name(name, new_ps_name)
tree.write(ttx_path, xml_declaration=True, encoding=encoding )
# generate the udpated font now.
- ttx_args = ["-d", argv[1], ttx_path]
+ ttx_args = ['-q', '-d', dest_dir, ttx_path]
ttx.main(ttx_args)
- # delete the temp ttx file.
+ except InvalidFontException:
+ # In case of invalid fonts, we exit.
+ print filename + ' is not a valid font'
+ raise
+ except Exception as e:
+ print 'Error converting font: ' + filename
+ print e
+ # Some fonts are too big to be handled by the ttx library.
+ # Just copy paste them.
+ shutil.copy(input_path, dest_dir)
+ try:
+ # delete the temp ttx file is it exists.
os.remove(ttx_path)
+ except OSError:
+ pass
def get_font_info(tag):
ps_name = None
@@ -85,19 +121,17 @@
if namerecord.attrib['nameID'] == '6':
if ps_name is not None:
if not sanitize(namerecord.text) == ps_name:
- sys.exit('found multiple possibilities of the font name')
+ raise InvalidFontException('found multiple possibilities of the font name')
else:
ps_name = sanitize(namerecord.text)
# nameID=5 means the font version
if namerecord.attrib['nameID'] == '5':
if ps_version is not None:
if not ps_version == get_version(namerecord.text):
- sys.exit('found multiple possibilities of the font version')
+ raise InvalidFontException('found multiple possibilities of the font version')
else:
ps_version = get_version(namerecord.text)
- if ps_name is not None and ps_version is not None:
- return [ps_name, ps_version]
- sys.exit('didn\'t find the font name or version')
+ return [ps_name, ps_version]
def update_name(tag, name):
@@ -110,11 +144,11 @@
return re.sub(r'[^\w-]+', '', string)
def get_version(string):
- # The string must begin with "Version n.nn "
+ # The string must begin with 'Version n.nn '
# to extract n.nn, we return the second entry in the split strings.
string = string.strip()
- if not string.startswith("Version "):
- sys.exit('mal-formed font version')
+ if not string.startswith('Version '):
+ raise InvalidFontException('mal-formed font version')
return sanitize(string.split()[1])
if __name__ == '__main__':