Merge "Add new media navigation keys."
diff --git a/api/current.txt b/api/current.txt
index 2401d7b..14babc8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22694,12 +22694,14 @@
public static class Build.VERSION {
ctor public Build.VERSION();
+ field public static final java.lang.String BASE_OS;
field public static final java.lang.String CODENAME;
field public static final java.lang.String INCREMENTAL;
field public static final int PREVIEW_SDK_INT;
field public static final java.lang.String RELEASE;
field public static final deprecated java.lang.String SDK;
field public static final int SDK_INT;
+ field public static final java.lang.String SECURITY_PATCH;
}
public static class Build.VERSION_CODES {
@@ -39717,6 +39719,7 @@
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet);
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int, int, android.content.res.Resources.Theme);
method public void clearListSelection();
method protected java.lang.CharSequence convertSelectionToString(java.lang.Object);
method public void dismissDropDown();
diff --git a/api/system-current.txt b/api/system-current.txt
index cb51032..908bf3c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24638,12 +24638,14 @@
public static class Build.VERSION {
ctor public Build.VERSION();
+ field public static final java.lang.String BASE_OS;
field public static final java.lang.String CODENAME;
field public static final java.lang.String INCREMENTAL;
field public static final int PREVIEW_SDK_INT;
field public static final java.lang.String RELEASE;
field public static final deprecated java.lang.String SDK;
field public static final int SDK_INT;
+ field public static final java.lang.String SECURITY_PATCH;
}
public static class Build.VERSION_CODES {
@@ -42363,6 +42365,7 @@
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet);
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public AutoCompleteTextView(android.content.Context, android.util.AttributeSet, int, int, android.content.res.Resources.Theme);
method public void clearListSelection();
method protected java.lang.CharSequence convertSelectionToString(java.lang.Object);
method public void dismissDropDown();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 84892a3..07fa52c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -31,7 +31,7 @@
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
-import android.view.Window.WindowStackCallback;
+import android.view.Window.WindowControllerCallback;
import android.widget.Toolbar;
import com.android.internal.app.IVoiceInteractor;
@@ -60,6 +60,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.graphics.Rect;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.net.Uri;
@@ -673,7 +674,7 @@
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
- Window.OnWindowDismissedCallback, WindowStackCallback {
+ Window.OnWindowDismissedCallback, WindowControllerCallback {
private static final String TAG = "Activity";
private static final boolean DEBUG_LIFECYCLE = false;
@@ -2712,7 +2713,8 @@
}
- /** Called to move the window and its activity/task to a different stack container.
+ /**
+ * Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
* {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
* {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
@@ -2734,6 +2736,31 @@
}
/**
+ * Returns the bounds of the task that contains this activity.
+ *
+ * @return Rect The bounds that contains the activity.
+ * @hide
+ */
+ @Override
+ public Rect getActivityBounds() throws RemoteException {
+ return ActivityManagerNative.getDefault().getActivityBounds(mToken);
+ }
+
+ /**
+ * Sets the bounds (size and position) of the task or stack that contains this
+ * activity.
+ * NOTE: The requested bounds might not the fully honored by the system depending
+ * on the window placement policy.
+ *
+ * @param newBounds The new target bounds of the activity in task or stack.
+ * @hide
+ */
+ @Override
+ public void setActivityBounds(Rect newBounds) throws RemoteException {
+ ActivityManagerNative.getDefault().setActivityBounds(mToken, newBounds);
+ }
+
+ /**
* Called to process key events. You can override this to intercept all
* key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
@@ -6211,7 +6238,7 @@
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
- mWindow.setWindowStackCallback(this);
+ mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 10c122a..5f33344 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,6 +752,24 @@
return true;
}
+ case GET_ACTIVITY_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Rect r = getActivityBounds(token);
+ reply.writeNoException();
+ r.writeToParcel(reply, 0);
+ return true;
+ }
+
+ case SET_ACTIVITY_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Rect r = Rect.CREATOR.createFromParcel(data);
+ setActivityBounds(token, r);
+ reply.writeNoException();
+ return true;
+ }
+
case POSITION_TASK_IN_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int taskId = data.readInt();
@@ -5903,6 +5921,35 @@
}
@Override
+ public void setActivityBounds(IBinder token, Rect r) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ r.writeToParcel(data, 0);
+ mRemote.transact(SET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public Rect getActivityBounds(IBinder token) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(GET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Rect rect = Rect.CREATOR.createFromParcel(reply);
+ data.recycle();
+ reply.recycle();
+ return rect;
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index ff1d595..d355219 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -490,6 +490,9 @@
throws RemoteException;
public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
public void resizeTask(int taskId, Rect bounds) throws RemoteException;
+ public void setActivityBounds(IBinder token, Rect bounds) throws RemoteException;
+ public Rect getActivityBounds(IBinder token) throws RemoteException;
+
public Rect getTaskBounds(int taskId) throws RemoteException;
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
@@ -887,4 +890,6 @@
int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343;
int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344;
int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
+ int GET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
+ int SET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a0162f7..2374899 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -157,6 +157,17 @@
public static final String RELEASE = getString("ro.build.version.release");
/**
+ * The base OS build the product is based on.
+ */
+ public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
+
+ /**
+ * The user-visible security patch level.
+ */
+ public static final String SECURITY_PATCH = SystemProperties.get(
+ "ro.build.version.security_patch", "");
+
+ /**
* The user-visible SDK version of the framework in its raw String
* representation; use {@link #SDK_INT} instead.
*
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index dc1d6f6..3d8ab1e 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -514,6 +514,12 @@
startA(mSpannableStringBuilder, attributes);
} else if (tag.equalsIgnoreCase("u")) {
start(mSpannableStringBuilder, new Underline());
+ } else if (tag.equalsIgnoreCase("del")) {
+ start(mSpannableStringBuilder, new Strikethrough());
+ } else if (tag.equalsIgnoreCase("s")) {
+ start(mSpannableStringBuilder, new Strikethrough());
+ } else if (tag.equalsIgnoreCase("strike")) {
+ start(mSpannableStringBuilder, new Strikethrough());
} else if (tag.equalsIgnoreCase("sup")) {
start(mSpannableStringBuilder, new Super());
} else if (tag.equalsIgnoreCase("sub")) {
@@ -565,6 +571,12 @@
endA(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("u")) {
end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
+ } else if (tag.equalsIgnoreCase("del")) {
+ end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
+ } else if (tag.equalsIgnoreCase("s")) {
+ end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
+ } else if (tag.equalsIgnoreCase("strike")) {
+ end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
} else if (tag.equalsIgnoreCase("sup")) {
end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
} else if (tag.equalsIgnoreCase("sub")) {
@@ -822,6 +834,7 @@
private static class Bold { }
private static class Italic { }
private static class Underline { }
+ private static class Strikethrough { }
private static class Big { }
private static class Small { }
private static class Monospace { }
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 992dc4d..40315ad 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
@@ -814,8 +815,9 @@
* a list of all the spans regardless of type.
*/
@SuppressWarnings("unchecked")
- public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
- if (kind == null || mSpanCount == 0) return ArrayUtils.emptyArray(kind);
+ public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
+ if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
+ if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
int count = countSpans(queryStart, queryEnd, kind, treeRoot());
if (count == 0) {
return ArrayUtils.emptyArray(kind);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index eecda89..1a9fb34 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -28,8 +28,9 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.media.session.MediaController;
import android.net.Uri;
import android.os.Bundle;
@@ -183,7 +184,7 @@
private TypedArray mWindowStyle;
private Callback mCallback;
private OnWindowDismissedCallback mOnWindowDismissedCallback;
- private WindowStackCallback mWindowStackCallback;
+ private WindowControllerCallback mWindowControllerCallback;
private WindowManager mWindowManager;
private IBinder mAppToken;
private String mAppName;
@@ -479,8 +480,9 @@
}
/** @hide */
- public interface WindowStackCallback {
- /** Called to move the window and its activity/task to a different stack container.
+ public interface WindowControllerCallback {
+ /**
+ * Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
* {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
* {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
@@ -491,6 +493,23 @@
/** Returns the current stack Id for the window. */
int getWindowStackId() throws RemoteException;
+
+ /**
+ * Returns the bounds of the task that contains this activity.
+ *
+ * @return Rect The bounds that contains the activity.
+ */
+ Rect getActivityBounds() throws RemoteException;
+
+ /**
+ * Sets the bounds (size and position) of the task or stack that contains this
+ * activity.
+ * NOTE: The requested bounds might not the fully honored by the system depending
+ * on the window placement policy.
+ *
+ * @param newBounds The new target bounds of the activity in task or stack.
+ */
+ void setActivityBounds(Rect newBounds) throws RemoteException;
}
public Window(Context context) {
@@ -682,13 +701,13 @@
}
/** @hide */
- public final void setWindowStackCallback(WindowStackCallback wscb) {
- mWindowStackCallback = wscb;
+ public final void setWindowControllerCallback(WindowControllerCallback wccb) {
+ mWindowControllerCallback = wccb;
}
/** @hide */
- public final WindowStackCallback getWindowStackCallback() {
- return mWindowStackCallback;
+ public final WindowControllerCallback getWindowControllerCallback() {
+ return mWindowControllerCallback;
}
/**
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 01767d5..7d57cb8 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -18,6 +18,7 @@
import android.annotation.DrawableRes;
import android.content.Context;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
@@ -28,10 +29,12 @@
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
@@ -94,6 +97,12 @@
static final int EXPAND_MAX = 3;
+ /** Context used to inflate the popup window or dialog. */
+ private final Context mPopupContext;
+
+ private final ListPopupWindow mPopup;
+ private final PassThroughClickListener mPassThroughClickListener;
+
private CharSequence mHintText;
private TextView mHintView;
private int mHintResource;
@@ -102,7 +111,6 @@
private Filter mFilter;
private int mThreshold;
- private ListPopupWindow mPopup;
private int mDropDownAnchorId;
private AdapterView.OnItemClickListener mItemClickListener;
@@ -122,71 +130,172 @@
// Set to false when the list is hidden to prevent asynchronous updates to popup the list again.
private boolean mPopupCanBeUpdated = true;
- private PassThroughClickListener mPassThroughClickListener;
private PopupDataSetObserver mObserver;
+ /**
+ * Constructs a new auto-complete text view with the given context's theme.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ */
public AutoCompleteTextView(Context context) {
this(context, null);
}
+ /**
+ * Constructs a new auto-complete text view with the given context's theme
+ * and the supplied attribute set.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
public AutoCompleteTextView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.autoCompleteTextViewStyle);
}
+ /**
+ * Constructs a new auto-complete text view with the given context's theme,
+ * the supplied attribute set, and default style attribute.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ */
public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
+ /**
+ * Constructs a new auto-complete text view with the given context's theme,
+ * the supplied attribute set, and default styles.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme.
+ * Can be 0 to not look for defaults.
+ */
public AutoCompleteTextView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
+ this(context, attrs, defStyleAttr, defStyleRes, null);
+ }
- mPopup = new ListPopupWindow(context, attrs, defStyleAttr, defStyleRes);
- mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
- mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
+ /**
+ * Constructs a new auto-complete text view with the given context, the
+ * supplied attribute set, default styles, and the theme against which the
+ * completion popup should be inflated.
+ *
+ * @param context The context against which the view is inflated, which
+ * provides access to the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme.
+ * Can be 0 to not look for defaults.
+ * @param popupTheme The theme against which the completion popup window
+ * should be inflated. May be {@code null} to use the
+ * view theme. If set, this will override any value
+ * specified by
+ * {@link android.R.styleable#AutoCompleteTextView_popupTheme}.
+ */
+ public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes, Theme popupTheme) {
+ super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
+ if (popupTheme != null) {
+ mPopupContext = new ContextThemeWrapper(context, popupTheme);
+ } else {
+ final int popupThemeResId = a.getResourceId(
+ R.styleable.AutoCompleteTextView_popupTheme, 0);
+ if (popupThemeResId != 0) {
+ mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
+ } else {
+ mPopupContext = context;
+ }
+ }
+
+ // Load attributes used within the popup against the popup context.
+ final TypedArray pa;
+ if (mPopupContext != context) {
+ pa = mPopupContext.obtainStyledAttributes(
+ attrs, R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
+ } else {
+ pa = a;
+ }
+
+ final Drawable popupListSelector = pa.getDrawable(
+ R.styleable.AutoCompleteTextView_dropDownSelector);
+ final int popupWidth = pa.getLayoutDimension(
+ R.styleable.AutoCompleteTextView_dropDownWidth, LayoutParams.WRAP_CONTENT);
+ final int popupHeight = pa.getLayoutDimension(
+ R.styleable.AutoCompleteTextView_dropDownHeight, LayoutParams.WRAP_CONTENT);
+ final int popupHintLayoutResId = pa.getResourceId(
+ R.styleable.AutoCompleteTextView_completionHintView, R.layout.simple_dropdown_hint);
+ final CharSequence popupHintText = pa.getText(
+ R.styleable.AutoCompleteTextView_completionHint);
+
+ if (pa != a) {
+ pa.recycle();
+ }
+
+ mPopup = new ListPopupWindow(mPopupContext, attrs, defStyleAttr, defStyleRes);
+ mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+ mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
+ mPopup.setListSelector(popupListSelector);
+ mPopup.setOnItemClickListener(new DropDownItemClickListener());
+
+ // For dropdown width, the developer can specify a specific width, or
+ // MATCH_PARENT (for full screen width), or WRAP_CONTENT (to match the
+ // width of the anchored view).
+ mPopup.setWidth(popupWidth);
+ mPopup.setHeight(popupHeight);
+
+ // Completion hint must be set after specifying hint layout.
+ mHintResource = popupHintLayoutResId;
+ setCompletionHint(popupHintText);
+
+ // Get the anchor's id now, but the view won't be ready, so wait to
+ // actually get the view and store it in mDropDownAnchorView lazily in
+ // getDropDownAnchorView later. Defaults to NO_ID, in which case the
+ // getDropDownAnchorView method will simply return this TextView, as a
+ // default anchoring point.
+ mDropDownAnchorId = a.getResourceId(
+ R.styleable.AutoCompleteTextView_dropDownAnchor, View.NO_ID);
+
mThreshold = a.getInt(R.styleable.AutoCompleteTextView_completionThreshold, 2);
- mPopup.setListSelector(a.getDrawable(R.styleable.AutoCompleteTextView_dropDownSelector));
-
- // Get the anchor's id now, but the view won't be ready, so wait to actually get the
- // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
- // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
- // this TextView, as a default anchoring point.
- mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
- View.NO_ID);
-
- // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
- // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
- mPopup.setWidth(a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mPopup.setHeight(a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight,
- ViewGroup.LayoutParams.WRAP_CONTENT));
-
- mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
- R.layout.simple_dropdown_hint);
-
- mPopup.setOnItemClickListener(new DropDownItemClickListener());
- setCompletionHint(a.getText(R.styleable.AutoCompleteTextView_completionHint));
+ a.recycle();
// Always turn on the auto complete input type flag, since it
// makes no sense to use this widget without it.
int inputType = getInputType();
- if ((inputType&EditorInfo.TYPE_MASK_CLASS)
- == EditorInfo.TYPE_CLASS_TEXT) {
+ if ((inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
setRawInputType(inputType);
}
- a.recycle();
-
setFocusable(true);
addTextChangedListener(new MyWatcher());
-
+
mPassThroughClickListener = new PassThroughClickListener();
super.setOnClickListener(mPassThroughClickListener);
}
@@ -222,8 +331,8 @@
mHintText = hint;
if (hint != null) {
if (mHintView == null) {
- final TextView hintView = (TextView) LayoutInflater.from(getContext()).inflate(
- mHintResource, null).findViewById(com.android.internal.R.id.text1);
+ final TextView hintView = (TextView) LayoutInflater.from(mPopupContext).inflate(
+ mHintResource, null).findViewById(R.id.text1);
hintView.setText(mHintText);
mHintView = hintView;
mPopup.setPromptView(hintView);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 6c37282..33595f2 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -5199,7 +5199,7 @@
**/
private int getWorkspaceId() {
int workspaceId = FULLSCREEN_WORKSPACE_STACK_ID;
- WindowStackCallback callback = getWindowStackCallback();
+ WindowControllerCallback callback = getWindowControllerCallback();
if (callback != null) {
try {
workspaceId = callback.getWindowStackId();
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index d8626cd..dd5c5d7 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import android.content.Context;
+import android.graphics.Rect;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.view.View;
@@ -243,7 +244,7 @@
* Maximize the window by moving it to the maximized workspace stack.
**/
private void maximizeWindow() {
- Window.WindowStackCallback callback = mOwner.getWindowStackCallback();
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
if (callback != null) {
try {
callback.changeWindowStack(
@@ -253,4 +254,40 @@
}
}
}
+
+ /**
+ * Returns the bounds of this activity.
+ * @return Returns bounds of the activity. It will return null if either the window is
+ * fullscreen or the bounds could not be retrieved.
+ */
+ private Rect getActivityBounds() {
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ return callback.getActivityBounds();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get the activity bounds.");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the bounds of this Activity on the stack.
+ * @param newBounds The bounds of the activity. Passing null is not allowed.
+ */
+ private void setActivityBounds(Rect newBounds) {
+ if (newBounds == null) {
+ Log.e(TAG, "Failed to set null bounds to the activity.");
+ return;
+ }
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ callback.setActivityBounds(newBounds);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to set the activity bounds.");
+ }
+ }
+ }
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6dab142..61af6c5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4485,6 +4485,8 @@
<enum name="wrap_content" value="-2" />
</attr>
<attr name="inputType" />
+ <!-- Theme to use for the completion popup window. -->
+ <attr name="popupTheme" />
</declare-styleable>
<declare-styleable name="PopupWindow">
<!-- The background to use for the popup window. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 398c584..01ab624 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1271,7 +1271,7 @@
If this string is empty or the specified package does not exist, then
the platform will search for an SMS app and use that (if there is one)-->
- <string name="default_sms_application" translatable="false"></string>
+ <string name="default_sms_application" translatable="false">com.android.messaging</string>
<!-- Default web browser. This is the package name of the application that will
be the default browser when the device first boots. Afterwards the user
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index 7b2bba1..fe4f1be 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -19,7 +19,6 @@
renderthread/RenderTask.cpp \
renderthread/RenderThread.cpp \
renderthread/TimeLord.cpp \
- renderthread/DirtyHistory.cpp \
thread/TaskManager.cpp \
utils/Blur.cpp \
utils/GLUtils.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 693b28e..f663f07 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -68,8 +68,6 @@
mRegionMesh = nullptr;
mProgram = nullptr;
- mFunctorsCount = 0;
-
patchCache.init();
mInitialized = true;
@@ -276,38 +274,6 @@
}
///////////////////////////////////////////////////////////////////////////////
-// Tiling
-///////////////////////////////////////////////////////////////////////////////
-
-void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard) {
- if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) {
- glStartTilingQCOM(x, y, width, height, (discard ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM));
- }
-}
-
-void Caches::endTiling() {
- if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) {
- glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
- }
-}
-
-bool Caches::hasRegisteredFunctors() {
- return mFunctorsCount > 0;
-}
-
-void Caches::registerFunctors(uint32_t functorCount) {
- mFunctorsCount += functorCount;
-}
-
-void Caches::unregisterFunctors(uint32_t functorCount) {
- if (functorCount > mFunctorsCount) {
- mFunctorsCount = 0;
- } else {
- mFunctorsCount -= functorCount;
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
// Regions
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 049d58b..a02e15d 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -124,10 +124,6 @@
*/
void deleteLayerDeferred(Layer* layer);
-
- void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard);
- void endTiling();
-
/**
* Returns the mesh used to draw regions. Calling this method will
* bind a VBO of type GL_ELEMENT_ARRAY_BUFFER that contains the
@@ -141,10 +137,6 @@
void dumpMemoryUsage();
void dumpMemoryUsage(String8& log);
- bool hasRegisteredFunctors();
- void registerFunctors(uint32_t functorCount);
- void unregisterFunctors(uint32_t functorCount);
-
// Misc
GLint maxTextureSize;
@@ -206,8 +198,6 @@
bool mInitialized;
- uint32_t mFunctorsCount;
-
// TODO: move below to RenderState
PixelBufferState* mPixelBufferState = nullptr;
TextureState* mTextureState = nullptr;
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 814bada..3d350c9 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -53,21 +53,10 @@
mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch");
mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer");
mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker");
- mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering");
mHas1BitStencil = hasGlExtension("GL_OES_stencil1");
mHas4BitStencil = hasGlExtension("GL_OES_stencil4");
mHasUnpackSubImage = hasGlExtension("GL_EXT_unpack_subimage");
- // Query EGL extensions
- findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList);
-
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, nullptr) > 0) {
- mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time");
- } else {
- mHasNvSystemTime = false;
- }
-
const char* version = (const char*) glGetString(GL_VERSION);
// Section 6.1.5 of the OpenGL ES specification indicates the GL version
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index a4eef0f..636b631 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -40,10 +40,8 @@
inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; }
inline bool hasDebugMarker() const { return mHasDebugMarker; }
- inline bool hasTiledRendering() const { return mHasTiledRendering; }
inline bool has1BitStencil() const { return mHas1BitStencil; }
inline bool has4BitStencil() const { return mHas4BitStencil; }
- inline bool hasNvSystemTime() const { return mHasNvSystemTime; }
inline bool hasUnpackRowLength() const { return mVersionMajor >= 3 || mHasUnpackSubImage; }
inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
@@ -67,10 +65,8 @@
bool mHasFramebufferFetch;
bool mHasDiscardFramebuffer;
bool mHasDebugMarker;
- bool mHasTiledRendering;
bool mHas1BitStencil;
bool mHas4BitStencil;
- bool mHasNvSystemTime;
bool mHasUnpackSubImage;
int mVersionMajor;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 8e04bdf..7a56d42 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -70,8 +70,6 @@
, mRenderState(renderState)
, mFrameStarted(false)
, mScissorOptimizationDisabled(false)
- , mSuppressTiling(false)
- , mFirstFrameAfterResize(true)
, mDirty(false)
, mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN})
, mLightRadius(FLT_MIN)
@@ -113,7 +111,6 @@
void OpenGLRenderer::onViewportInitialized() {
glDisable(GL_DITHER);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- mFirstFrameAfterResize = true;
}
void OpenGLRenderer::setupFrameState(float left, float top,
@@ -134,15 +131,6 @@
mRenderState.setViewport(mState.getWidth(), mState.getHeight());
- // Functors break the tiling extension in pretty spectacular ways
- // This ensures we don't use tiling when a functor is going to be
- // invoked during the frame
- mSuppressTiling = mCaches.hasRegisteredFunctors()
- || mFirstFrameAfterResize;
- mFirstFrameAfterResize = false;
-
- startTilingCurrentClip(true);
-
debugOverdraw(true, true);
clear(mTilingClip.left, mTilingClip.top,
@@ -192,46 +180,8 @@
mRenderState.scissor().reset();
}
-void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) {
- if (!mSuppressTiling) {
- const Snapshot* snapshot = currentSnapshot();
-
- const Rect* clip = &mTilingClip;
- if (snapshot->flags & Snapshot::kFlagFboTarget) {
- clip = &(snapshot->layer->clipRect);
- }
-
- startTiling(*clip, getViewportHeight(), opaque, expand);
- }
-}
-
-void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) {
- if (!mSuppressTiling) {
- if(expand) {
- // Expand the startTiling region by 1
- int leftNotZero = (clip.left > 0) ? 1 : 0;
- int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0;
-
- mCaches.startTiling(
- clip.left - leftNotZero,
- windowHeight - clip.bottom - topNotZero,
- clip.right - clip.left + leftNotZero + 1,
- clip.bottom - clip.top + topNotZero + 1,
- opaque);
- } else {
- mCaches.startTiling(clip.left, windowHeight - clip.bottom,
- clip.right - clip.left, clip.bottom - clip.top, opaque);
- }
- }
-}
-
-void OpenGLRenderer::endTiling() {
- if (!mSuppressTiling) mCaches.endTiling();
-}
-
bool OpenGLRenderer::finish() {
renderOverdraw();
- endTiling();
mTempPaths.clear();
// When finish() is invoked on FBO 0 we've reached the end
@@ -381,7 +331,6 @@
&& layer->renderNode.get() && layer->renderNode->isRenderable()) {
if (inFrame) {
- endTiling();
debugOverdraw(false, false);
}
@@ -393,7 +342,6 @@
if (inFrame) {
resumeAfterLayer();
- startTilingCurrentClip();
}
layer->debugDrawUpdate = Properties::debugLayersUpdates;
@@ -736,7 +684,6 @@
writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight());
writableSnapshot()->roundRectClipState = nullptr;
- endTiling();
debugOverdraw(false, false);
// Bind texture to FBO
mRenderState.bindFramebuffer(layer->getFbo());
@@ -751,9 +698,6 @@
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
layer->getTextureId(), 0);
- // Expand the startTiling region by 1
- startTilingCurrentClip(true, true);
-
// Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
mRenderState.scissor().setEnabled(true);
mRenderState.scissor().set(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f,
@@ -786,8 +730,6 @@
mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired);
if (fboLayer) {
- endTiling();
-
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
@@ -796,8 +738,6 @@
// Unbind current FBO and restore previous one
mRenderState.bindFramebuffer(restored.fbo);
debugOverdraw(true, false);
-
- startTilingCurrentClip();
}
if (!fboLayer && layer->getAlpha() < 255) {
@@ -1267,17 +1207,10 @@
void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
// The layer's FBO is already bound when we reach this stage
if (!layer->getStencilRenderBuffer()) {
- // GL_QCOM_tiled_rendering doesn't like it if a renderbuffer
- // is attached after we initiated tiling. We must turn it off,
- // attach the new render buffer then turn tiling back on
- endTiling();
-
RenderBuffer* buffer = mCaches.renderBufferCache.get(
Stencil::getLayerStencilFormat(),
layer->getWidth(), layer->getHeight());
layer->setStencilRenderBuffer(buffer);
-
- startTiling(layer->clipRect, layer->layer.getHeight());
}
}
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 390f620..4f75482 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -555,27 +555,6 @@
void discardFramebuffer(float left, float top, float right, float bottom);
/**
- * Tells the GPU what part of the screen is about to be redrawn.
- * This method will use the current layer space clip rect.
- * This method needs to be invoked every time getTargetFbo() is
- * bound again.
- */
- void startTilingCurrentClip(bool opaque = false, bool expand = false);
-
- /**
- * Tells the GPU what part of the screen is about to be redrawn.
- * This method needs to be invoked every time getTargetFbo() is
- * bound again.
- */
- void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false);
-
- /**
- * Tells the GPU that we are done drawing the frame or that we
- * are switching to another render target.
- */
- void endTiling();
-
- /**
* Sets the clipping rectangle using glScissor. The clip is defined by
* the current snapshot's clipRect member.
*/
@@ -862,10 +841,6 @@
// Properties.h
bool mScissorOptimizationDisabled;
- // No-ops start/endTiling when set
- bool mSuppressTiling;
- bool mFirstFrameAfterResize;
-
bool mSkipOutlineClip;
// True if anything has been drawn since the last call to
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 2e63793..b8f8585 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -30,6 +30,8 @@
bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
bool Properties::swapBuffersWithDamage = true;
+bool Properties::useBufferAge = true;
+bool Properties::enablePartialUpdates = true;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -105,6 +107,8 @@
skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true);
+ useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
+ enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1a70d7c..7602848 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -84,12 +84,6 @@
#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw"
/**
- * Used to enable/disable PerfHUD ES profiling. The accepted values
- * are "true" and "false". The default value is "false".
- */
-#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling"
-
-/**
* System property used to enable or disable hardware rendering profiling.
* The default value of this property is assumed to be false.
*
@@ -150,9 +144,25 @@
/**
* Setting this property will enable or disable usage of EGL_KHR_swap_buffers_with_damage
* See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt
+ * Default is "true"
*/
#define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage"
+/**
+ * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension
+ * to do partial invalidates. Setting this to "false" will fall back to
+ * using BUFFER_PRESERVED instead
+ * Default is "true"
+ */
+#define PROPERTY_USE_BUFFER_AGE "debug.hwui.use_buffer_age"
+
+/**
+ * Setting this to "false" will force HWUI to always do full-redraws of the surface.
+ * This will disable the use of EGL_EXT_buffer_age and BUFFER_PRESERVED.
+ * Default is "true"
+ */
+#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates"
+
///////////////////////////////////////////////////////////////////////////////
// Runtime configuration properties
///////////////////////////////////////////////////////////////////////////////
@@ -287,6 +297,8 @@
static bool skipEmptyFrames;
// TODO: Remove after stabilization period
static bool swapBuffersWithDamage;
+ static bool useBufferAge;
+ static bool enablePartialUpdates;
static DebugLevel debugLevel;
static OverdrawColorSet overdrawColorSet;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 6cf894e..fbfed12 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -303,10 +303,6 @@
// changes in isRenderable or, in the future, bounds
damageSelf(info);
deleteDisplayListData();
- // TODO: Remove this caches stuff
- if (mStagingDisplayListData && mStagingDisplayListData->functors.size()) {
- Caches::getInstance().registerFunctors(mStagingDisplayListData->functors.size());
- }
mDisplayListData = mStagingDisplayListData;
mStagingDisplayListData = nullptr;
if (mDisplayListData) {
@@ -323,9 +319,6 @@
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
}
- if (mDisplayListData->functors.size()) {
- Caches::getInstance().unregisterFunctors(mDisplayListData->functors.size());
- }
}
delete mDisplayListData;
mDisplayListData = nullptr;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ea73387..67c42f3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,6 +35,14 @@
#define TRIM_MEMORY_COMPLETE 80
#define TRIM_MEMORY_UI_HIDDEN 20
+#define LOG_FRAMETIME_MMA 0
+
+#if LOG_FRAMETIME_MMA
+static float sBenchMma = 0;
+static int sFrameCount = 0;
+static const float NANOS_PER_MILLIS_F = 1000000.0f;
+#endif
+
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -93,16 +101,6 @@
}
}
-void CanvasContext::swapBuffers(const SkRect& dirty, EGLint width, EGLint height) {
- if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface, dirty, width, height))) {
- setSurface(nullptr);
- }
- mHaveNewSurface = false;
- if (mEglManager.useBufferAgeExt()) {
- mDirtyHistory.prepend(Rect(dirty));
- }
-}
-
void CanvasContext::requireSurface() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"requireSurface() called but no surface set!");
@@ -230,8 +228,6 @@
"drawRenderNode called on a context with no canvas or surface!");
SkRect dirty;
- bool useBufferAgeExt = mEglManager.useBufferAgeExt();
- Rect patchedDirty;
mDamageAccumulator.finish(&dirty);
// TODO: Re-enable after figuring out cause of b/22592975
@@ -242,40 +238,59 @@
mCurrentFrameInfo->markIssueDrawCommandsStart();
- EGLint width, height, framebufferAge;
- mEglManager.beginFrame(mEglSurface, &width, &height, &framebufferAge);
-
- if (useBufferAgeExt && mHaveNewSurface) {
- mDirtyHistory.clear();
- }
-
- if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
- mCanvas->setViewport(width, height);
+ Frame frame = mEglManager.beginFrame(mEglSurface);
+ if (frame.width() != mCanvas->getViewportWidth()
+ || frame.height() != mCanvas->getViewportHeight()) {
+ mCanvas->setViewport(frame.width(), frame.height());
dirty.setEmpty();
- } else if (!mBufferPreserved || mHaveNewSurface) {
- mDirtyHistory.clear();
+ } else if (mHaveNewSurface || frame.bufferAge() == 0) {
+ // New surface needs a full draw
dirty.setEmpty();
} else {
- if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
+ if (!dirty.isEmpty() && !dirty.intersect(0, 0, frame.width(), frame.height())) {
ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
- SK_RECT_ARGS(dirty), width, height);
+ SK_RECT_ARGS(dirty), frame.width(), frame.height());
dirty.setEmpty();
}
profiler().unionDirty(&dirty);
}
- patchedDirty = dirty;
- if (useBufferAgeExt && !dirty.isEmpty()) {
- patchedDirty = mDirtyHistory.unionWith(Rect(dirty), framebufferAge-1);
+ if (dirty.isEmpty()) {
+ dirty.set(0, 0, frame.width(), frame.height());
}
- if (!patchedDirty.isEmpty()) {
- mCanvas->prepareDirty(patchedDirty.left, patchedDirty.top,
- patchedDirty.right, patchedDirty.bottom, mOpaque);
- } else {
- mCanvas->prepare(mOpaque);
+ // At this point dirty is the area of the screen to update. However,
+ // the area of the frame we need to repaint is potentially different, so
+ // stash the screen area for later
+ SkRect screenDirty(dirty);
+
+ // If the buffer age is 0 we do a full-screen repaint (handled above)
+ // If the buffer age is 1 the buffer contents are the same as they were
+ // last frame so there's nothing to union() against
+ // Therefore we only care about the > 1 case.
+ if (frame.bufferAge() > 1) {
+ if (frame.bufferAge() > (int) mDamageHistory.size()) {
+ // We don't have enough history to handle this old of a buffer
+ // Just do a full-draw
+ dirty.set(0, 0, frame.width(), frame.height());
+ } else {
+ // At this point we haven't yet added the latest frame
+ // to the damage history (happens below)
+ // So we need to damage
+ for (int i = mDamageHistory.size() - 1;
+ i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) {
+ dirty.join(mDamageHistory[i]);
+ }
+ }
}
+ // Add the screen damage to the ring buffer.
+ mDamageHistory.next() = screenDirty;
+
+ mEglManager.damageFrame(frame, dirty);
+ mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
+ dirty.fRight, dirty.fBottom, mOpaque);
+
Rect outBounds;
mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
@@ -288,11 +303,30 @@
mCurrentFrameInfo->markSwapBuffers();
if (drew) {
- swapBuffers(dirty, width, height);
+ if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
+ setSurface(nullptr);
+ }
+ mHaveNewSurface = false;
}
// TODO: Use a fence for real completion?
mCurrentFrameInfo->markFrameCompleted();
+
+#if LOG_FRAMETIME_MMA
+ float thisFrame = mCurrentFrameInfo->duration(
+ FrameInfoIndex::IssueDrawCommandsStart,
+ FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F;
+ if (sFrameCount) {
+ sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
+ } else {
+ sBenchMma = thisFrame;
+ }
+ if (++sFrameCount == 10) {
+ sFrameCount = 1;
+ ALOGD("Average frame time: %.4f", sBenchMma);
+ }
+#endif
+
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 59f9c3a..0ceb9f1 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -25,7 +25,6 @@
#include "utils/RingBuffer.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
-#include "renderthread/DirtyHistory.h"
#include <cutils/compiler.h>
#include <EGL/egl.h>
@@ -119,7 +118,6 @@
friend class android::uirenderer::RenderState;
void setSurface(ANativeWindow* window);
- void swapBuffers(const SkRect& dirty, EGLint width, EGLint height);
void requireSurface();
void freePrefetechedLayers();
@@ -130,6 +128,7 @@
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
+ RingBuffer<SkRect, 3> mDamageHistory;
bool mOpaque;
OpenGLRenderer* mCanvas = nullptr;
@@ -147,8 +146,6 @@
FrameInfoVisualizer mProfiler;
std::set<RenderNode*> mPrefetechedLayers;
-
- DirtyHistory mDirtyHistory;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DirtyHistory.cpp b/libs/hwui/renderthread/DirtyHistory.cpp
deleted file mode 100644
index 1419e84..0000000
--- a/libs/hwui/renderthread/DirtyHistory.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "DirtyHistory.h"
-
-namespace android {
-namespace uirenderer {
-namespace renderthread {
-
-DirtyHistory::DirtyHistory()
- : mBack(DIRTY_HISTORY_SIZE - 1) {
- clear();
-}
-
-void DirtyHistory::clear()
-{
- for (int i = 0; i < DIRTY_HISTORY_SIZE; i++) {
- mHistory[i].clear();
- }
-}
-
-Rect DirtyHistory::get(int index) {
- if (index >= DIRTY_HISTORY_SIZE || index < 0)
- return Rect();
- return mHistory[(1 + mBack + index) % DIRTY_HISTORY_SIZE];
-}
-
-Rect DirtyHistory::unionWith(Rect rect, int count) {
- if (rect.isEmpty() || count > DIRTY_HISTORY_SIZE || count < 0)
- return Rect();
-
- for (int i = 0; i < count; i++) {
- Rect ith = get(i);
- if (ith.isEmpty())
- return Rect();
-
- // rect union
- rect.left = fminf(rect.left, ith.left);
- rect.top = fminf(rect.top, ith.top);
- rect.right = fmaxf(rect.right, ith.right);
- rect.bottom = fmaxf(rect.bottom, ith.bottom);
- }
- return rect;
-}
-
-void DirtyHistory::prepend(Rect rect) {
- if (rect.isEmpty()) {
- mHistory[mBack].clear();
- } else {
- mHistory[mBack].set(rect);
- }
- mBack = (mBack + DIRTY_HISTORY_SIZE - 1) % DIRTY_HISTORY_SIZE;
-}
-
-} /* namespace renderthread */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/renderthread/DirtyHistory.h b/libs/hwui/renderthread/DirtyHistory.h
deleted file mode 100644
index d5ea597..0000000
--- a/libs/hwui/renderthread/DirtyHistory.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef DIRTYHISTORY_H
-#define DIRTYHISTORY_H
-
-#include <Rect.h>
-
-namespace android {
-namespace uirenderer {
-namespace renderthread {
-
-#define DIRTY_HISTORY_SIZE 4
-
-class DirtyHistory {
-public:
- DirtyHistory();
- ~DirtyHistory() {}
-
- Rect get(int index);
- Rect unionWith(Rect rect, int count);
- void prepend(Rect rect);
- void clear();
-private:
- Rect mHistory[DIRTY_HISTORY_SIZE];
- int mBack;
-};
-
-} /* namespace renderthread */
-} /* namespace uirenderer */
-} /* namespace android */
-
-#endif /* DIRTYHISTORY_H */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index ac36f53..d2ce49f 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -25,7 +25,8 @@
#include <cutils/properties.h>
#include <EGL/eglext.h>
-#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
+#include <string>
+
#define GLES_VERSION 2
#define WAIT_FOR_GPU_COMPLETION 0
@@ -63,10 +64,27 @@
return egl_error_str(eglGetError());
}
-static bool load_dirty_regions_property() {
- char buf[PROPERTY_VALUE_MAX];
- int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true");
- return !strncasecmp("true", buf, len);
+static struct {
+ bool bufferAge = false;
+ bool setDamage = false;
+} EglExtensions;
+
+void Frame::map(const SkRect& in, EGLint* out) const {
+ /* The rectangles are specified relative to the bottom-left of the surface
+ * and the x and y components of each rectangle specify the bottom-left
+ * position of that rectangle.
+ *
+ * HWUI does everything with 0,0 being top-left, so need to map
+ * the rect
+ */
+ SkIRect idirty;
+ in.roundOut(&idirty);
+ EGLint y = mHeight - (idirty.y() + idirty.height());
+ // layout: {x, y, width, height}
+ out[0] = idirty.x();
+ out[1] = y;
+ out[2] = idirty.width();
+ out[3] = idirty.height();
}
EglManager::EglManager(RenderThread& thread)
@@ -75,13 +93,9 @@
, mEglConfig(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
- , mAllowPreserveBuffer(load_dirty_regions_property())
- , mHasBufferAgeExt(false)
, mCurrentSurface(EGL_NO_SURFACE)
, mAtlasMap(nullptr)
, mAtlasMapSize(0) {
- mCanSetPreserveBuffer = mAllowPreserveBuffer;
- ALOGD("Use EGL_SWAP_BEHAVIOR_PRESERVED: %s", mAllowPreserveBuffer ? "true" : "false");
}
void EglManager::initialize() {
@@ -99,10 +113,18 @@
ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
- findExtensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS), mEglExtensionList);
- mHasBufferAgeExt = hasEglExtension("EGL_EXT_buffer_age");
+ initExtensions();
- loadConfig(mHasBufferAgeExt);
+ // Now that extensions are loaded, pick a swap behavior
+ if (Properties::enablePartialUpdates) {
+ if (Properties::useBufferAge && EglExtensions.bufferAge) {
+ mSwapBehavior = SwapBehavior::BufferAge;
+ } else {
+ mSwapBehavior = SwapBehavior::Preserved;
+ }
+ }
+
+ loadConfig();
createContext();
createPBufferSurface();
makeCurrent(mPBufferSurface);
@@ -110,17 +132,23 @@
initAtlas();
}
+void EglManager::initExtensions() {
+ std::string extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+ auto has = [&](const char* ext) {
+ return extensions.find(ext) != std::string::npos;
+ };
+ EglExtensions.bufferAge = has("EGL_EXT_buffer_age");
+ EglExtensions.setDamage = has("EGL_KHR_partial_update");
+}
+
bool EglManager::hasEglContext() {
return mEglDisplay != EGL_NO_DISPLAY;
}
-bool EglManager::hasEglExtension(const char* extension) const {
- const std::string s(extension);
- return mEglExtensionList.find(s) != mEglExtensionList.end();
-}
-
-void EglManager::loadConfig(bool useBufferAgeExt) {
- EGLint swapBehavior = (!useBufferAgeExt && mCanSetPreserveBuffer) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+void EglManager::loadConfig() {
+ ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
+ EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved)
+ ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
@@ -137,13 +165,13 @@
EGLint num_configs = 1;
if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
|| num_configs != 1) {
- // Failed to get a valid config
- if (mCanSetPreserveBuffer) {
- ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+ if (mSwapBehavior == SwapBehavior::Preserved) {
// Try again without dirty regions enabled
- mCanSetPreserveBuffer = false;
- loadConfig(useBufferAgeExt);
+ ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+ mSwapBehavior = SwapBehavior::Discard;
+ loadConfig();
} else {
+ // Failed to get a valid config
LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
}
}
@@ -247,24 +275,47 @@
return true;
}
-void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge) {
+EGLint EglManager::queryBufferAge(EGLSurface surface) {
+ switch (mSwapBehavior) {
+ case SwapBehavior::Discard:
+ return 0;
+ case SwapBehavior::Preserved:
+ return 1;
+ case SwapBehavior::BufferAge:
+ EGLint bufferAge;
+ eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, &bufferAge);
+ return bufferAge;
+ }
+ return 0;
+}
+
+Frame EglManager::beginFrame(EGLSurface surface) {
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Tried to beginFrame on EGL_NO_SURFACE!");
makeCurrent(surface);
- if (width) {
- eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
- }
- if (height) {
- eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
- }
- if (useBufferAgeExt()) {
- eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, framebufferAge);
- }
+ Frame frame;
+ frame.mSurface = surface;
+ eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth);
+ eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight);
+ frame.mBufferAge = queryBufferAge(surface);
eglBeginFrame(mEglDisplay, surface);
+ return frame;
}
-bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty,
- EGLint width, EGLint height) {
+void EglManager::damageFrame(const Frame& frame, const SkRect& dirty) {
+#ifdef EGL_KHR_partial_update
+ if (EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge) {
+ EGLint rects[4];
+ frame.map(dirty, rects);
+ if (!eglSetDamageRegionKHR(mEglDisplay, frame.mSurface, rects, 1)) {
+ LOG_ALWAYS_FATAL("Failed to set damage region on surface %p, error=%s",
+ (void*)frame.mSurface, egl_error_str());
+ }
+ }
+#endif
+}
+
+bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) {
#if WAIT_FOR_GPU_COMPLETION
{
@@ -275,28 +326,15 @@
#ifdef EGL_KHR_swap_buffers_with_damage
if (CC_LIKELY(Properties::swapBuffersWithDamage)) {
- SkIRect idirty;
- dirty.roundOut(&idirty);
- /*
- * EGL_KHR_swap_buffers_with_damage spec states:
- *
- * The rectangles are specified relative to the bottom-left of the surface
- * and the x and y components of each rectangle specify the bottom-left
- * position of that rectangle.
- *
- * HWUI does everything with 0,0 being top-left, so need to map
- * the rect
- */
- EGLint y = height - (idirty.y() + idirty.height());
- // layout: {x, y, width, height}
- EGLint rects[4] = { idirty.x(), y, idirty.width(), idirty.height() };
- EGLint numrects = dirty.isEmpty() ? 0 : 1;
- eglSwapBuffersWithDamageKHR(mEglDisplay, surface, rects, numrects);
+ EGLint rects[4];
+ frame.map(screenDirty, rects);
+ eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
+ screenDirty.isEmpty() ? 0 : 1);
} else {
- eglSwapBuffers(mEglDisplay, surface);
+ eglSwapBuffers(mEglDisplay, frame.mSurface);
}
#else
- eglSwapBuffers(mEglDisplay, surface);
+ eglSwapBuffers(mEglDisplay, frame.mSurface);
#endif
EGLint err = eglGetError();
@@ -307,7 +345,8 @@
// For some reason our surface was destroyed out from under us
// This really shouldn't happen, but if it does we can recover easily
// by just not trying to use the surface anymore
- ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", surface);
+ ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...",
+ frame.mSurface);
return false;
}
LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering",
@@ -316,10 +355,6 @@
return false;
}
-bool EglManager::useBufferAgeExt() {
- return mAllowPreserveBuffer && mHasBufferAgeExt;
-}
-
void EglManager::fence() {
EGLSyncKHR fence = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_FENCE_KHR, NULL);
eglClientWaitSyncKHR(mEglDisplay, fence,
@@ -328,21 +363,13 @@
}
bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) {
- if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false;
+ if (mSwapBehavior != SwapBehavior::Preserved) return false;
- // Use EGL_EXT_buffer_age instead if supported
- if (mHasBufferAgeExt) return true;
-
- bool preserved = false;
- if (mCanSetPreserveBuffer) {
- preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
- preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED);
- if (CC_UNLIKELY(!preserved)) {
- ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
- (void*) surface, egl_error_str());
- }
- }
- if (CC_UNLIKELY(!preserved)) {
+ bool preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
+ preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED);
+ if (!preserved) {
+ ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
+ (void*) surface, egl_error_str());
// Maybe it's already set?
EGLint swapBehavior;
if (eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &swapBehavior)) {
@@ -356,19 +383,6 @@
return preserved;
}
-void EglManager::findExtensions(const char* extensions, std::set<std::string>& list) const {
- const char* current = extensions;
- const char* head = current;
- do {
- head = strchr(current, ' ');
- std::string s(current, head ? head - current : strlen(current));
- if (s.length()) {
- list.insert(s);
- }
- current = head + 1;
- } while (head);
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index bb5d24b..62b5b99 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -21,13 +21,35 @@
#include <SkRect.h>
#include <ui/GraphicBuffer.h>
#include <utils/StrongPointer.h>
-#include <set>
namespace android {
namespace uirenderer {
namespace renderthread {
class RenderThread;
+class EglManager;
+
+class Frame {
+public:
+ EGLint width() const { return mWidth; }
+ EGLint height() const { return mHeight; }
+
+ // See: https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_buffer_age.txt
+ // for what this means
+ EGLint bufferAge() const { return mBufferAge; }
+
+private:
+ friend class EglManager;
+
+ EGLSurface mSurface;
+ EGLint mWidth;
+ EGLint mHeight;
+ EGLint mBufferAge;
+
+ // Maps from 0,0 in top-left to 0,0 in bottom-left
+ // If out is not an EGLint[4] you're going to have a bad time
+ void map(const SkRect& in, EGLint* out) const;
+};
// This class contains the shared global EGL objects, such as EGLDisplay
// and EGLConfig, which are re-used by CanvasContext
@@ -38,8 +60,6 @@
bool hasEglContext();
- bool hasEglExtension(const char* extension) const;
-
EGLSurface createSurface(EGLNativeWindowType window);
void destroySurface(EGLSurface surface);
@@ -48,14 +68,13 @@
bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
// Returns true if the current surface changed, false if it was already current
bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr);
- void beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge);
- bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height);
+ Frame beginFrame(EGLSurface surface);
+ void damageFrame(const Frame& frame, const SkRect& dirty);
+ bool swapBuffers(const Frame& frame, const SkRect& screenDirty);
// Returns true iff the surface is now preserving buffers.
bool setPreserveBuffer(EGLSurface surface, bool preserve);
- bool useBufferAgeExt();
-
void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
void fence();
@@ -67,12 +86,12 @@
// EglContext is never destroyed, method is purposely not implemented
~EglManager();
+ void initExtensions();
void createPBufferSurface();
- void loadConfig(bool useBufferAgeExt);
+ void loadConfig();
void createContext();
void initAtlas();
-
- void findExtensions(const char* extensions, std::set<std::string>& list) const;
+ EGLint queryBufferAge(EGLSurface surface);
RenderThread& mRenderThread;
@@ -81,18 +100,18 @@
EGLContext mEglContext;
EGLSurface mPBufferSurface;
- const bool mAllowPreserveBuffer;
- bool mCanSetPreserveBuffer;
-
- bool mHasBufferAgeExt;
-
EGLSurface mCurrentSurface;
sp<GraphicBuffer> mAtlasBuffer;
int64_t* mAtlasMap;
size_t mAtlasMapSize;
- std::set<std::string> mEglExtensionList;
+ enum class SwapBehavior {
+ Discard,
+ Preserved,
+ BufferAge,
+ };
+ SwapBehavior mSwapBehavior = SwapBehavior::Discard;
};
} /* namespace renderthread */
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 69e8225..483fb35 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -283,6 +283,69 @@
}
};
+class PartialInvalTest : public TreeContentAnimation {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, DisplayListCanvas* renderer) override {
+ static SkColor COLORS[] = {
+ 0xFFF44336,
+ 0xFF9C27B0,
+ 0xFF2196F3,
+ 0xFF4CAF50,
+ };
+
+ renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+ for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+ for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+ sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
+ COLORS[static_cast<int>((y / dp(116))) % 4]);
+ renderer->drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+ DisplayListCanvas* renderer = startRecording(cards[0].get());
+ renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
+ SkXfermode::kSrcOver_Mode);
+ endRecording(renderer, cards[0].get());
+ }
+
+ static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
+ int startA = (start >> 24) & 0xff;
+ int startR = (start >> 16) & 0xff;
+ int startG = (start >> 8) & 0xff;
+ int startB = start & 0xff;
+
+ int endA = (end >> 24) & 0xff;
+ int endR = (end >> 16) & 0xff;
+ int endG = (end >> 8) & 0xff;
+ int endB = end & 0xff;
+
+ return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+ (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+ (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+ (int)((startB + (int)(fraction * (endB - startB))));
+ }
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
+ sp<RenderNode> node = new RenderNode();
+ node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+ node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+ DisplayListCanvas* renderer = startRecording(node.get());
+ renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
+ endRecording(renderer, node.get());
+ return node;
+ }
+};
+
struct cstr_cmp {
bool operator()(const char *a, const char *b) const {
return std::strcmp(a, b) < 0;
@@ -296,6 +359,7 @@
{"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>},
{"rectgrid", TreeContentAnimation::run<RectGridAnimation> },
{"oval", TreeContentAnimation::run<OvalAnimation> },
+ {"partialinval", TreeContentAnimation::run<PartialInvalTest> },
};
int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/nullgles.cpp b/libs/hwui/tests/nullgles.cpp
index 8ca7598..f8e8c98 100644
--- a/libs/hwui/tests/nullgles.cpp
+++ b/libs/hwui/tests/nullgles.cpp
@@ -261,8 +261,6 @@
void glPushGroupMarkerEXT(GLsizei length, const GLchar *marker) {}
void glPopGroupMarkerEXT(void) {}
void glDiscardFramebufferEXT(GLenum target, GLsizei numAttachments, const GLenum *attachments) {}
-void glStartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask) {}
-void glEndTilingQCOM(GLbitfield preserveMask) {}
void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) {}
// GLES3
diff --git a/packages/Keyguard/res/values/strings.xml b/packages/Keyguard/res/values/strings.xml
index 41b1059..d1e84f5 100644
--- a/packages/Keyguard/res/values/strings.xml
+++ b/packages/Keyguard/res/values/strings.xml
@@ -316,6 +316,15 @@
<!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_restart_password">Password required when you restart device.</string>
+ <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_timeout_pattern">Pattern required for additional security.</string>
+
+ <!-- An explanation text that the pin needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_timeout_pin">PIN required for additional security.</string>
+
+ <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_timeout_password">Password required for additional security.</string>
+
<!-- An explanation text that the pattern needs to be solved since profiles have just been switched. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_switch_profiles_pattern">Pattern required when you switch profiles.</string>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index 32892cf..ec2a173 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -163,8 +163,9 @@
* Show a string explaining why the security view needs to be solved.
*
* @param reason a flag indicating which string should be shown, see
- * {@link KeyguardSecurityView#PROMPT_REASON_NONE}
- * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
+ * {@link KeyguardSecurityView#PROMPT_REASON_NONE},
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
*/
public void showPromptReason(int reason) {
mSecurityContainer.showPromptReason(reason);
@@ -213,9 +214,12 @@
/**
* Authentication has happened and it's time to dismiss keyguard. This function
* should clean up and inform KeyguardViewMediator.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
*/
@Override
- public void finish() {
+ public void finish(boolean strongAuth) {
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
@@ -226,9 +230,9 @@
}
if (mViewMediatorCallback != null) {
if (deferKeyguardDone) {
- mViewMediatorCallback.keyguardDonePending();
+ mViewMediatorCallback.keyguardDonePending(strongAuth);
} else {
- mViewMediatorCallback.keyguardDone(true);
+ mViewMediatorCallback.keyguardDone(strongAuth);
}
}
}
@@ -285,32 +289,6 @@
}
/**
- * Verify that the user can get past the keyguard securely. This is called,
- * for example, when the phone disables the keyguard but then wants to launch
- * something else that requires secure access.
- *
- * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)}
- */
- public void verifyUnlock() {
- SecurityMode securityMode = mSecurityContainer.getSecurityMode();
- if (securityMode == KeyguardSecurityModel.SecurityMode.None) {
- if (mViewMediatorCallback != null) {
- mViewMediatorCallback.keyguardDone(true);
- }
- } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
- && securityMode != KeyguardSecurityModel.SecurityMode.PIN
- && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
- // can only verify unlock when in pattern/password mode
- if (mViewMediatorCallback != null) {
- mViewMediatorCallback.keyguardDone(false);
- }
- } else {
- // otherwise, go to the unlock screen, see if they can verify it
- mSecurityContainer.verifyUnlock();
- }
- }
-
- /**
* Called before this view is being removed.
*/
public void cleanUp() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
index 2db87b3..3a7e6d0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
@@ -115,6 +115,8 @@
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_password;
+ case PROMPT_REASON_TIMEOUT:
+ return R.string.kg_prompt_reason_timeout_password;
default:
return 0;
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index a96c79f..9a91ca4 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -332,7 +332,12 @@
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern,
true /* important */);
break;
+ case PROMPT_REASON_TIMEOUT:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern,
+ true /* important */);
+ break;
default:
+ break;
}
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 07947b1..4cd4845 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -98,6 +98,8 @@
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_pin;
+ case PROMPT_REASON_TIMEOUT:
+ return R.string.kg_prompt_reason_timeout_pin;
default:
return 0;
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8fc3cde..77215a7 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -54,7 +54,12 @@
public boolean dismiss(boolean authenticated);
public void userActivity();
public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
- public void finish();
+
+ /**
+ * @param strongAuth wheher the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
+ */
+ public void finish(boolean strongAuth);
public void reset();
}
@@ -282,7 +287,7 @@
showWipeDialog(failedAttempts, userType);
}
}
- monitor.reportFailedUnlockAttempt();
+ monitor.reportFailedStrongAuthUnlockAttempt();
mLockPatternUtils.reportFailedPasswordAttempt(KeyguardUpdateMonitor.getCurrentUser());
if (timeoutMs > 0) {
showTimeoutDialog(timeoutMs);
@@ -308,6 +313,7 @@
boolean showNextSecurityScreenOrFinish(boolean authenticated) {
if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
boolean finish = false;
+ boolean strongAuth = false;
if (mUpdateMonitor.getUserCanSkipBouncer(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
@@ -323,6 +329,7 @@
case Pattern:
case Password:
case PIN:
+ strongAuth = true;
finish = true;
break;
@@ -346,7 +353,7 @@
}
}
if (finish) {
- mSecurityCallback.finish();
+ mSecurityCallback.finish(strongAuth);
}
return finish;
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java
index 7e82c63..38302fb 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java
@@ -22,9 +22,18 @@
static public final int VIEW_REVEALED = 2;
int PROMPT_REASON_NONE = 0;
+
+ /**
+ * Strong auth is required because the device has just booted.
+ */
int PROMPT_REASON_RESTART = 1;
/**
+ * Strong auth is required because the user hasn't used strong auth since a while.
+ */
+ int PROMPT_REASON_TIMEOUT = 2;
+
+ /**
* Interface back to keyguard to tell it when security
* @param callback
*/
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0ee68fd..3232f65 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -64,6 +64,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -103,6 +104,12 @@
= "com.android.facelock.FACE_UNLOCK_STOPPED";
private static final String FINGERPRINT_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+ private static final String ACTION_STRONG_AUTH_TIMEOUT =
+ "com.android.systemui.ACTION_STRONG_AUTH_TIMEOUT";
+ private static final String USER_ID = "com.android.systemui.USER_ID";
+
+ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
/**
* Mode in which we don't need to wake up the device when we get a fingerprint.
*/
@@ -126,6 +133,12 @@
* */
private static final int FP_ONLY_WAKE = 3;
+ /**
+ * Milliseconds after unlocking with fingerprint times out, i.e. the user has to use a
+ * strong auth method like password, PIN or pattern.
+ */
+ private static final long FINGERPRINT_UNLOCK_TIMEOUT_MS = 72 * 60 * 60 * 1000;
+
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
@@ -173,7 +186,8 @@
// Password attempts
private SparseIntArray mFailedAttempts = new SparseIntArray();
- private boolean mClockVisible;
+ /** Tracks whether strong authentication hasn't been used since quite some time per user. */
+ private ArraySet<Integer> mStrongAuthTimedOut = new ArraySet<>();
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
@@ -184,6 +198,7 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private SubscriptionManager mSubscriptionManager;
+ private AlarmManager mAlarmManager;
private List<SubscriptionInfo> mSubscriptionInfo;
private boolean mFingerprintDetectionRunning;
private TrustManager mTrustManager;
@@ -549,7 +564,39 @@
}
public boolean isUnlockingWithFingerprintAllowed() {
- return mUserHasAuthenticatedSinceBoot;
+ return mUserHasAuthenticatedSinceBoot && !hasFingerprintUnlockTimedOut(sCurrentUser);
+ }
+
+ /**
+ * @return true if the user hasn't use strong authentication (pattern, PIN, password) since a
+ * while and thus can't unlock with fingerprint, false otherwise
+ */
+ public boolean hasFingerprintUnlockTimedOut(int userId) {
+ return mStrongAuthTimedOut.contains(userId);
+ }
+
+ public void reportSuccessfulStrongAuthUnlockAttempt() {
+ mStrongAuthTimedOut.remove(sCurrentUser);
+ scheduleStrongAuthTimeout();
+ }
+
+ private void scheduleStrongAuthTimeout() {
+ long when = SystemClock.elapsedRealtime() + FINGERPRINT_UNLOCK_TIMEOUT_MS;
+ Intent intent = new Intent(ACTION_STRONG_AUTH_TIMEOUT);
+ intent.putExtra(USER_ID, sCurrentUser);
+ PendingIntent sender = PendingIntent.getBroadcast(mContext,
+ sCurrentUser, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, sender);
+ notifyStrongAuthTimedOutChanged(sCurrentUser);
+ }
+
+ private void notifyStrongAuthTimedOutChanged(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onStrongAuthTimeoutExpiredChanged(userId);
+ }
+ }
}
static class DisplayClientState {
@@ -639,6 +686,17 @@
}
};
+ private final BroadcastReceiver mStrongAuthTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_STRONG_AUTH_TIMEOUT.equals(intent.getAction())) {
+ int userId = intent.getIntExtra(USER_ID, -1);
+ mStrongAuthTimedOut.add(userId);
+ notifyStrongAuthTimedOutChanged(userId);
+ }
+ }
+ };
+
private FingerprintManager.AuthenticationCallback mAuthenticationCallback
= new AuthenticationCallback() {
@@ -871,7 +929,9 @@
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mPowerManager = context.getSystemService(PowerManager.class);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
+
// Since device can't be un-provisioned, we only need to register a content observer
// to update mDeviceProvisioned when we are...
if (!mDeviceProvisioned) {
@@ -932,6 +992,10 @@
e.printStackTrace();
}
+ IntentFilter strongAuthTimeoutFilter = new IntentFilter();
+ strongAuthTimeoutFilter.addAction(ACTION_STRONG_AUTH_TIMEOUT);
+ context.registerReceiver(mStrongAuthTimeoutReceiver, strongAuthTimeoutFilter,
+ PERMISSION_SELF, null /* handler */);
mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
mTrustManager.registerTrustListener(this);
@@ -955,7 +1019,7 @@
private void startListeningForFingerprint() {
if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
int userId = ActivityManager.getCurrentUser();
- if (isUnlockWithFingerPrintPossible(userId)) {
+ if (isUnlockWithFingerprintPossible(userId)) {
mUserHasAuthenticatedSinceBoot = mTrustManager.hasUserAuthenticatedSinceBoot(
ActivityManager.getCurrentUser());
if (mFingerprintCancelSignal != null) {
@@ -967,7 +1031,7 @@
}
}
- public boolean isUnlockWithFingerPrintPossible(int userId) {
+ public boolean isUnlockWithFingerprintPossible(int userId) {
return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
&& mFpm.getEnrolledFingerprints(userId).size() > 0;
}
@@ -1433,7 +1497,7 @@
return mFailedAttempts.get(sCurrentUser, 0);
}
- public void reportFailedUnlockAttempt() {
+ public void reportFailedStrongAuthUnlockAttempt() {
mFailedAttempts.put(sCurrentUser, getFailedUnlockAttempts() + 1);
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6cda2b7..52412f7 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -217,4 +217,10 @@
* Called when the fingerprint running state changed.
*/
public void onFingerprintRunningStateChanged(boolean running) { }
+
+ /**
+ * Called when the state that the user hasn't used strong authentication since quite some time
+ * has changed.
+ */
+ public void onStrongAuthTimeoutExpiredChanged(int userId) { }
}
diff --git a/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java b/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java
index ff463c6..8ab3011 100644
--- a/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java
@@ -28,12 +28,11 @@
/**
* Report that the keyguard is done.
- * @param authenticated Whether the user securely got past the keyguard.
- * the only reason for this to be false is if the keyguard was instructed
- * to appear temporarily to verify the user is supposed to get past the
- * keyguard, and the user fails to do so.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
*/
- void keyguardDone(boolean authenticated);
+ void keyguardDone(boolean strongAuth);
/**
* Report that the keyguard is done drawing.
@@ -48,8 +47,11 @@
/**
* Report that the keyguard is dismissable, pending the next keyguardDone call.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
*/
- void keyguardDonePending();
+ void keyguardDonePending(boolean strongAuth);
/**
* Report when keyguard is actually gone
@@ -85,8 +87,9 @@
/**
* @return one of the reasons why the bouncer needs to be shown right now and the user can't use
* his normal unlock method like fingerprint or trust agents. See
- * {@link KeyguardSecurityView#PROMPT_REASON_NONE}
- * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}.
+ * {@link KeyguardSecurityView#PROMPT_REASON_NONE},
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
*/
int getBouncerPromptReason();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
new file mode 100644
index 0000000..2353205
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Process;
+import android.provider.DocumentsContract;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedList;
+
+class DocumentLoader {
+ static final int NUM_INITIAL_ENTRIES = 10;
+ static final int NUM_LOADING_ENTRIES = 20;
+ static final int NOTIFY_PERIOD_MS = 500;
+
+ private final MtpManager mMtpManager;
+ private final ContentResolver mResolver;
+ private final LinkedList<LoaderTask> mTasks = new LinkedList<LoaderTask>();
+ private boolean mHasBackgroundThread = false;
+
+ DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
+ mMtpManager = mtpManager;
+ mResolver = resolver;
+ }
+
+ private static MtpDocument[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
+ throws IOException {
+ final MtpDocument[] documents = new MtpDocument[handles.length];
+ for (int i = 0; i < handles.length; i++) {
+ documents[i] = manager.getDocument(deviceId, handles[i]);
+ }
+ return documents;
+ }
+
+ synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent)
+ throws IOException {
+ LoaderTask task = findTask(parent);
+ if (task == null) {
+ int parentHandle = parent.mObjectHandle;
+ // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
+ // getObjectHandles if we would like to obtain children under the root.
+ if (parentHandle == MtpDocument.DUMMY_HANDLE_FOR_ROOT) {
+ parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
+ }
+ task = new LoaderTask(parent, mMtpManager.getObjectHandles(
+ parent.mDeviceId, parent.mStorageId, parentHandle));
+ task.fillDocuments(loadDocuments(
+ mMtpManager,
+ parent.mDeviceId,
+ task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES)));
+ }
+
+ // Move this task to the head of the list to prioritize it.
+ mTasks.remove(task);
+ mTasks.addFirst(task);
+ if (!task.completed() && !mHasBackgroundThread) {
+ mHasBackgroundThread = true;
+ new BackgroundLoaderThread().start();
+ }
+
+ return task.createCursor(mResolver, columnNames);
+ }
+
+ synchronized void clearCache(int deviceId) {
+ int i = 0;
+ while (i < mTasks.size()) {
+ if (mTasks.get(i).mIdentifier.mDeviceId == deviceId) {
+ mTasks.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+
+ synchronized void clearCache() {
+ int i = 0;
+ while (i < mTasks.size()) {
+ if (mTasks.get(i).completed()) {
+ mTasks.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+
+ private LoaderTask findTask(Identifier parent) {
+ for (int i = 0; i < mTasks.size(); i++) {
+ if (mTasks.get(i).mIdentifier.equals(parent))
+ return mTasks.get(i);
+ }
+ return null;
+ }
+
+ private LoaderTask findUncompletedTask() {
+ for (int i = 0; i < mTasks.size(); i++) {
+ if (!mTasks.get(i).completed())
+ return mTasks.get(i);
+ }
+ return null;
+ }
+
+ private class BackgroundLoaderThread extends Thread {
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ while (true) {
+ LoaderTask task;
+ int deviceId;
+ int[] handles;
+ synchronized (DocumentLoader.this) {
+ task = findUncompletedTask();
+ if (task == null) {
+ mHasBackgroundThread = false;
+ return;
+ }
+ deviceId = task.mIdentifier.mDeviceId;
+ handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
+ }
+ MtpDocument[] documents;
+ try {
+ documents = loadDocuments(mMtpManager, deviceId, handles);
+ } catch (IOException exception) {
+ documents = null;
+ Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
+ }
+ synchronized (DocumentLoader.this) {
+ if (documents != null) {
+ task.fillDocuments(documents);
+ final boolean shouldNotify =
+ task.mLastNotified.getTime() <
+ new Date().getTime() - NOTIFY_PERIOD_MS ||
+ task.completed();
+ if (shouldNotify) {
+ task.notify(mResolver);
+ }
+ } else {
+ mTasks.remove(task);
+ }
+ }
+ }
+ }
+ }
+
+ private static class LoaderTask {
+ final Identifier mIdentifier;
+ final int[] mObjectHandles;
+ final MtpDocument[] mDocuments;
+ Date mLastNotified;
+ int mNumLoaded;
+
+ LoaderTask(Identifier identifier, int[] objectHandles) {
+ mIdentifier = identifier;
+ mObjectHandles = objectHandles;
+ mDocuments = new MtpDocument[mObjectHandles.length];
+ mNumLoaded = 0;
+ mLastNotified = new Date();
+ }
+
+ Cursor createCursor(ContentResolver resolver, String[] columnNames) {
+ final MatrixCursor cursor = new MatrixCursor(columnNames);
+ final Identifier rootIdentifier = new Identifier(
+ mIdentifier.mDeviceId, mIdentifier.mStorageId);
+ for (int i = 0; i < mNumLoaded; i++) {
+ mDocuments[i].addToCursor(rootIdentifier, cursor.newRow());
+ }
+ final Bundle extras = new Bundle();
+ extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
+ cursor.setNotificationUri(resolver, createUri());
+ cursor.respond(extras);
+ return cursor;
+ }
+
+ boolean completed() {
+ return mNumLoaded == mDocuments.length;
+ }
+
+ int[] getUnloadedObjectHandles(int count) {
+ return Arrays.copyOfRange(
+ mObjectHandles,
+ mNumLoaded,
+ Math.min(mNumLoaded + count, mObjectHandles.length));
+ }
+
+ void notify(ContentResolver resolver) {
+ resolver.notifyChange(createUri(), null, false);
+ mLastNotified = new Date();
+ }
+
+ void fillDocuments(MtpDocument[] documents) {
+ for (int i = 0; i < documents.length; i++) {
+ mDocuments[mNumLoaded++] = documents[i];
+ }
+ }
+
+ private Uri createUri() {
+ return DocumentsContract.buildChildDocumentsUri(
+ MtpDocumentsProvider.AUTHORITY, mIdentifier.toDocumentId());
+ }
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 32882c8..bcdfb71 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -40,12 +40,12 @@
public class MtpDocumentsProvider extends DocumentsProvider {
static final String AUTHORITY = "com.android.mtp.documents";
static final String TAG = "MtpDocumentsProvider";
- private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+ static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,
};
- private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+ static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
@@ -56,6 +56,7 @@
private MtpManager mMtpManager;
private ContentResolver mResolver;
private PipeManager mPipeManager;
+ private DocumentLoader mDocumentLoader;
/**
* Provides singleton instance to MtpDocumentsService.
@@ -70,14 +71,15 @@
mMtpManager = new MtpManager(getContext());
mResolver = getContext().getContentResolver();
mPipeManager = new PipeManager();
-
+ mDocumentLoader = new DocumentLoader(mMtpManager, mResolver);
return true;
}
@VisibleForTesting
void onCreateForTesting(MtpManager mtpManager, ContentResolver resolver) {
- this.mMtpManager = mtpManager;
- this.mResolver = resolver;
+ mMtpManager = mtpManager;
+ mResolver = resolver;
+ mDocumentLoader = new DocumentLoader(mMtpManager, mResolver);
}
@Override
@@ -152,7 +154,6 @@
return cursor;
}
- // TODO: Support background loading for large number of files.
@Override
public Cursor queryChildDocuments(String parentDocumentId,
String[] projection, String sortOrder) throws FileNotFoundException {
@@ -160,29 +161,8 @@
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId);
- int parentHandle = parentIdentifier.mObjectHandle;
- // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
- // getObjectHandles if we would like to obtain children under the root.
- if (parentHandle == MtpDocument.DUMMY_HANDLE_FOR_ROOT) {
- parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
- }
try {
- final MatrixCursor cursor = new MatrixCursor(projection);
- final Identifier rootIdentifier = new Identifier(
- parentIdentifier.mDeviceId, parentIdentifier.mStorageId);
- final int[] objectHandles = mMtpManager.getObjectHandles(
- parentIdentifier.mDeviceId, parentIdentifier.mStorageId, parentHandle);
- for (int i = 0; i < objectHandles.length; i++) {
- try {
- final MtpDocument document = mMtpManager.getDocument(
- parentIdentifier.mDeviceId, objectHandles[i]);
- document.addToCursor(rootIdentifier, cursor.newRow());
- } catch (IOException error) {
- cursor.close();
- throw new FileNotFoundException(error.getMessage());
- }
- }
- return cursor;
+ return mDocumentLoader.queryChildDocuments(projection, parentIdentifier);
} catch (IOException exception) {
throw new FileNotFoundException(exception.getMessage());
}
@@ -234,6 +214,11 @@
}
}
+ @Override
+ public void onTrimMemory(int level) {
+ mDocumentLoader.clearCache();
+ }
+
void openDevice(int deviceId) throws IOException {
mMtpManager.openDevice(deviceId);
notifyRootsChange();
@@ -241,6 +226,7 @@
void closeDevice(int deviceId) throws IOException {
mMtpManager.closeDevice(deviceId);
+ mDocumentLoader.clearCache(deviceId);
notifyRootsChange();
}
@@ -249,6 +235,7 @@
for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
try {
mMtpManager.closeDevice(deviceId);
+ mDocumentLoader.clearCache(deviceId);
closed = true;
} catch (IOException d) {
Log.d(TAG, "Failed to close the MTP device: " + deviceId);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
new file mode 100644
index 0000000..49fcddd
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+public class DocumentLoaderTest extends AndroidTestCase {
+ private BlockableTestMtpMaanger mManager;
+ private TestContentResolver mResolver;
+ private DocumentLoader mLoader;
+ final private Identifier mParentIdentifier = new Identifier(0, 0, 0);
+
+ @Override
+ public void setUp() {
+ mManager = new BlockableTestMtpMaanger(getContext());
+ mResolver = new TestContentResolver();
+ mLoader = new DocumentLoader(mManager, mResolver);
+ }
+
+ public void testBasic() throws IOException, InterruptedException {
+ final Uri uri = DocumentsContract.buildChildDocumentsUri(
+ MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId());
+ setUpDocument(mManager, 40);
+ mManager.blockDocument(0, 15);
+ mManager.blockDocument(0, 35);
+
+ {
+ final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier);
+ assertEquals(DocumentLoader.NUM_INITIAL_ENTRIES, cursor.getCount());
+ }
+
+ Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS);
+ mManager.unblockDocument(0, 15);
+ mResolver.waitForNotification(uri, 1);
+
+ {
+ final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier);
+ assertEquals(
+ DocumentLoader.NUM_INITIAL_ENTRIES + DocumentLoader.NUM_LOADING_ENTRIES,
+ cursor.getCount());
+ }
+
+ mManager.unblockDocument(0, 35);
+ mResolver.waitForNotification(uri, 2);
+
+ {
+ final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier);
+ assertEquals(40, cursor.getCount());
+ }
+
+ assertEquals(2, mResolver.getChangeCount(uri));
+ }
+
+ private void setUpDocument(TestMtpManager manager, int count) {
+ int[] childDocuments = new int[count];
+ for (int i = 0; i < childDocuments.length; i++) {
+ final int objectHandle = i + 1;
+ childDocuments[i] = objectHandle;
+ manager.setDocument(0, objectHandle, new MtpDocument(
+ objectHandle,
+ 0 /* format */,
+ "file" + objectHandle,
+ new Date(),
+ 1024,
+ 0 /* thumbnail size */));
+ }
+ manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
+ }
+
+ private static class BlockableTestMtpMaanger extends TestMtpManager {
+ final private Map<String, CountDownLatch> blockedDocuments = new HashMap<>();
+
+ BlockableTestMtpMaanger(Context context) {
+ super(context);
+ }
+
+ void blockDocument(int deviceId, int objectHandle) {
+ blockedDocuments.put(pack(deviceId, objectHandle), new CountDownLatch(1));
+ }
+
+ void unblockDocument(int deviceId, int objectHandle) {
+ blockedDocuments.get(pack(deviceId, objectHandle)).countDown();
+ }
+
+ @Override
+ MtpDocument getDocument(int deviceId, int objectHandle) throws IOException {
+ final CountDownLatch latch = blockedDocuments.get(pack(deviceId, objectHandle));
+ if (latch != null) {
+ try {
+ latch.await();
+ } catch(InterruptedException e) {
+ fail();
+ }
+ }
+ return super.getDocument(deviceId, objectHandle);
+ }
+ }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 1031d8a..e0e3ce6 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -16,30 +16,26 @@
package com.android.mtp;
-import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.test.AndroidTestCase;
-import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
@SmallTest
public class MtpDocumentsProviderTest extends AndroidTestCase {
- private ContentResolver mResolver;
+ private TestContentResolver mResolver;
private MtpDocumentsProvider mProvider;
private TestMtpManager mMtpManager;
@Override
public void setUp() {
- mResolver = new ContentResolver();
+ mResolver = new TestContentResolver();
mMtpManager = new TestMtpManager(getContext());
mProvider = new MtpDocumentsProvider();
mProvider.onCreateForTesting(mMtpManager, mResolver);
@@ -291,21 +287,4 @@
DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, "0_0_2")));
}
-
- private static class ContentResolver extends MockContentResolver {
- final Map<Uri, Integer> mChangeCounts = new HashMap<Uri, Integer>();
-
- @Override
- public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
- mChangeCounts.put(uri, getChangeCount(uri) + 1);
- }
-
- int getChangeCount(Uri uri) {
- if (mChangeCounts.containsKey(uri)) {
- return mChangeCounts.get(uri);
- } else {
- return 0;
- }
- }
- }
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestContentResolver.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestContentResolver.java
new file mode 100644
index 0000000..33e559f
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestContentResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Phaser;
+
+class TestContentResolver extends MockContentResolver {
+ final private Map<Uri, Phaser> mPhasers = new HashMap<>();
+
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ getPhaser(uri).arrive();
+ }
+
+ void waitForNotification(Uri uri, int count) {
+ Assert.assertEquals(count, getPhaser(uri).awaitAdvance(count - 1));
+ }
+
+ int getChangeCount(Uri uri) {
+ if (mPhasers.containsKey(uri)) {
+ return mPhasers.get(uri).getPhase();
+ } else {
+ return 0;
+ }
+ }
+
+ private synchronized Phaser getPhaser(Uri uri) {
+ Phaser phaser = mPhasers.get(uri);
+ if (phaser == null) {
+ phaser = new Phaser(1);
+ mPhasers.put(uri, phaser);
+ }
+ return phaser;
+ }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 7f81686..40de7b4 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -28,7 +28,7 @@
import java.util.TreeSet;
public class TestMtpManager extends MtpManager {
- private static String pack(int... args) {
+ protected static String pack(int... args) {
return Arrays.toString(args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index d78800f..d2c60ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -80,7 +80,8 @@
@Override // Binder interface
public void keyguardDone(boolean authenticated, boolean wakeup) {
checkPermission();
- mKeyguardViewMediator.keyguardDone(authenticated, wakeup);
+ // TODO: Remove wakeup
+ mKeyguardViewMediator.keyguardDone(authenticated);
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e44d109..428ceca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -465,14 +465,15 @@
mUpdateMonitor.isUnlockingWithFingerprintAllowed();
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
if (unlockingWithFingerprintAllowed) {
- mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated();
+ mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(
+ false /* strongAuth */);
}
} else {
if (wakeAndUnlocking && mShowing && unlockingWithFingerprintAllowed) {
mWakeAndUnlocking = true;
mStatusBarKeyguardViewManager.setWakeAndUnlocking();
- keyguardDone(true, true);
- } else if (mShowing && mDeviceInteractive) {
+ keyguardDone(true);
+ } else if (mShowing) {
if (wakeAndUnlocking) {
mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
}
@@ -490,9 +491,12 @@
KeyguardViewMediator.this.userActivity();
}
- public void keyguardDone(boolean authenticated) {
+ public void keyguardDone(boolean strongAuth) {
if (!mKeyguardDonePending) {
- KeyguardViewMediator.this.keyguardDone(authenticated, true);
+ KeyguardViewMediator.this.keyguardDone(true /* authenticated */);
+ }
+ if (strongAuth) {
+ mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
}
}
@@ -506,12 +510,15 @@
}
@Override
- public void keyguardDonePending() {
+ public void keyguardDonePending(boolean strongAuth) {
mKeyguardDonePending = true;
mHideAnimationRun = true;
mStatusBarKeyguardViewManager.startPreHideAnimation(null /* finishRunnable */);
mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_PENDING_TIMEOUT,
KEYGUARD_DONE_PENDING_TIMEOUT_MS);
+ if (strongAuth) {
+ mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
+ }
}
@Override
@@ -524,7 +531,7 @@
if (mKeyguardDonePending) {
// Somebody has called keyguardDonePending before, which means that we are
// authenticated
- KeyguardViewMediator.this.keyguardDone(true /* authenticated */, true /* wakeUp */);
+ KeyguardViewMediator.this.keyguardDone(true /* authenticated */);
}
}
@@ -552,9 +559,12 @@
public int getBouncerPromptReason() {
int currentUser = ActivityManager.getCurrentUser();
if ((mUpdateMonitor.getUserTrustIsManaged(currentUser)
- || mUpdateMonitor.isUnlockWithFingerPrintPossible(currentUser))
+ || mUpdateMonitor.isUnlockWithFingerprintPossible(currentUser))
&& !mTrustManager.hasUserAuthenticatedSinceBoot(currentUser)) {
return KeyguardSecurityView.PROMPT_REASON_RESTART;
+ } else if (mUpdateMonitor.isUnlockWithFingerprintPossible(currentUser)
+ && mUpdateMonitor.hasFingerprintUnlockTimedOut(currentUser)) {
+ return KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
@@ -1194,10 +1204,10 @@
}
};
- public void keyguardDone(boolean authenticated, boolean wakeup) {
- if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
+ public void keyguardDone(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated +")");
EventLog.writeEvent(70000, 2);
- Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0, wakeup ? 1 : 0);
+ Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0);
mHandler.sendMessage(msg);
}
@@ -1240,14 +1250,11 @@
handleNotifyStartedWakingUp();
break;
case KEYGUARD_DONE:
- handleKeyguardDone(msg.arg1 != 0, msg.arg2 != 0);
+ handleKeyguardDone(msg.arg1 != 0);
break;
case KEYGUARD_DONE_DRAWING:
handleKeyguardDoneDrawing();
break;
- case KEYGUARD_DONE_AUTHENTICATING:
- keyguardDone(true, true);
- break;
case SET_OCCLUDED:
handleSetOccluded(msg.arg1 != 0);
break;
@@ -1277,7 +1284,7 @@
* @see #keyguardDone
* @see #KEYGUARD_DONE
*/
- private void handleKeyguardDone(boolean authenticated, boolean wakeup) {
+ private void handleKeyguardDone(boolean authenticated) {
if (DEBUG) Log.d(TAG, "handleKeyguardDone");
synchronized (this) {
resetKeyguardDonePendingLocked();
@@ -1599,6 +1606,7 @@
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff");
mStatusBarKeyguardViewManager.onScreenTurnedOff();
+ mWakeAndUnlocking = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index f36019b..e64f6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -21,7 +21,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import com.android.systemui.Prefs;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b47fb304..d0876fa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -33,7 +33,6 @@
import android.view.ViewStub;
import android.widget.Toast;
-import com.android.internal.logging.MetricsConstants;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index afa32cbdf..89aeabc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -20,10 +20,8 @@
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
-import android.app.IActivityContainer;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
-import android.app.SearchManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -54,15 +52,12 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -71,7 +66,6 @@
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsAppWidgetHost;
import com.android.systemui.recents.RecentsConfiguration;
import java.io.IOException;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 4878cd92..f1b8873 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -647,6 +647,11 @@
public void onFingerprintRunningStateChanged(boolean running) {
mLockIcon.update();
}
+
+ @Override
+ public void onStrongAuthTimeoutExpiredChanged(int userId) {
+ mLockIcon.update();
+ }
};
public void setKeyguardIndicationController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 37f563e..8b96e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -26,6 +26,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.KeyguardSecurityView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.R;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
@@ -47,6 +49,13 @@
private ViewGroup mRoot;
private boolean mShowingSoon;
private int mBouncerPromptReason;
+ private KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onStrongAuthTimeoutExpiredChanged(int userId) {
+ mBouncerPromptReason = mCallback.getBouncerPromptReason();
+ }
+ };
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
@@ -56,6 +65,7 @@
mLockPatternUtils = lockPatternUtils;
mContainer = container;
mWindowManager = windowManager;
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
}
public void show(boolean resetSecuritySelection) {
@@ -247,8 +257,8 @@
return mKeyguardView.interceptMediaKey(event);
}
- public void notifyKeyguardAuthenticated() {
+ public void notifyKeyguardAuthenticated(boolean strongAuth) {
ensureView();
- mKeyguardView.finish();
+ mKeyguardView.finish(strongAuth);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 1bdcf03..3c1272d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -480,8 +480,8 @@
* Notifies that the user has authenticated by other means than using the bouncer, for example,
* fingerprint.
*/
- public void notifyKeyguardAuthenticated() {
- mBouncer.notifyKeyguardAuthenticated();
+ public void notifyKeyguardAuthenticated(boolean strongAuth) {
+ mBouncer.notifyKeyguardAuthenticated(strongAuth);
}
public void setWakeAndUnlocking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index bd537f7..d646d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -143,6 +143,11 @@
public void onFaceUnlockStateChanged(boolean running, int userId) {
update(false /* updateAlways */);
}
+
+ @Override
+ public void onStrongAuthTimeoutExpiredChanged(int userId) {
+ update(false /* updateAlways */);
+ }
};
public boolean isTrustManaged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index daa84ad..a04edf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -164,11 +164,18 @@
// Our current device is still valid.
return;
}
+ mLastDevice = null;
for (CachedBluetoothDevice device : getDevices()) {
if (device.isConnected()) {
mLastDevice = device;
}
}
+ if (mLastDevice == null && mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
+ // If somehow we think we are connected, but have no connected devices, we aren't
+ // connected.
+ mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2be3bf7..19ad930 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8639,6 +8639,54 @@
}
@Override
+ public void setActivityBounds(IBinder token, Rect bounds) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ Slog.w(TAG, "setActivityBounds: token=" + token + " not found");
+ return;
+ }
+ final TaskRecord task = r.task;
+ if (task == null) {
+ Slog.e(TAG, "setActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
+ return;
+ }
+ mStackSupervisor.resizeTaskLocked(task, bounds);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public Rect getActivityBounds(IBinder token) {
+ long ident = Binder.clearCallingIdentity();
+ Rect rect = null;
+ try {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ Slog.w(TAG, "getActivityBounds: token=" + token + " not found");
+ return rect;
+ }
+ final TaskRecord task = r.task;
+ if (task == null) {
+ Slog.e(TAG, "getActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
+ return rect;
+ }
+ if (task.mBounds != null) {
+ rect = new Rect(task.mBounds);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return rect;
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) {
if (!FileUtils.isValidExtFilename(filename)
|| !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 2c9d82b..ffad69d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -589,10 +589,14 @@
result = true; // client not listening
}
if (fpId == 0) {
- FingerprintUtils.vibrateFingerprintError(getContext());
+ if (receiver != null) {
+ FingerprintUtils.vibrateFingerprintError(getContext());
+ }
result |= handleFailedAttempt(this);
} else {
- FingerprintUtils.vibrateFingerprintSuccess(getContext());
+ if (receiver != null) {
+ FingerprintUtils.vibrateFingerprintSuccess(getContext());
+ }
result |= true; // we have a valid fingerprint
mLockoutReset.run();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 426578d..45bbf37 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1699,8 +1699,7 @@
* navigation bar and touch exploration is not enabled
*/
private boolean canHideNavigationBar() {
- return mHasNavigationBar
- && !mAccessibilityManager.isTouchExplorationEnabled();
+ return mHasNavigationBar;
}
@Override
@@ -5522,10 +5521,6 @@
// may happen in a future call to goToSleep.
synchronized (mLock) {
mAwake = true;
- if (mKeyguardDelegate != null) {
- mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
- mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, 1000);
- }
updateWakeGestureListenerLp();
updateOrientationListenerLp();
@@ -5615,6 +5610,8 @@
mScreenOnListener = screenOnListener;
if (mKeyguardDelegate != null) {
+ mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, 1000);
mKeyguardDelegate.onScreenTurningOn(mKeyguardDrawnCallback);
} else {
if (DEBUG_WAKEUP) Slog.d(TAG,
@@ -6780,8 +6777,7 @@
* R.boolean.config_enableTranslucentDecor is false.
*/
private boolean areTranslucentBarsAllowed() {
- return mTranslucentDecorEnabled
- && !mAccessibilityManager.isTouchExplorationEnabled();
+ return mTranslucentDecorEnabled;
}
// Use this instead of checking config_showNavigationBar so that it can be consistently