Merge "Synchronize thumbnail header in recents to freeform animation."
diff --git a/api/current.txt b/api/current.txt
index 24cddd6..5048788 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4867,6 +4867,7 @@
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -36283,6 +36284,7 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
+ method public final void cancelDragAndDrop();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
@@ -36763,7 +36765,8 @@
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
method public void startAnimation(android.view.animation.Animation);
- method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
method public boolean startNestedScroll(int);
method public void stopNestedScroll();
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
diff --git a/api/system-current.txt b/api/system-current.txt
index 00d5745..ae0ac0c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4985,6 +4985,7 @@
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -38606,6 +38607,7 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
+ method public final void cancelDragAndDrop();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
@@ -39086,7 +39088,8 @@
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
method public void startAnimation(android.view.animation.Animation);
- method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
method public boolean startNestedScroll(int);
method public void stopNestedScroll();
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 84ddd9f..c1d5b19 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,11 +47,11 @@
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
- void setPackagePriority(String pkg, int uid, int priority);
- int getPackagePriority(String pkg, int uid);
-
- void setPackageVisibilityOverride(String pkg, int uid, int visibility);
- int getPackageVisibilityOverride(String pkg, int uid);
+ ParceledListSlice getTopics(String pkg, int uid);
+ void setTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
+ int getTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic);
+ void setTopicPriority(String pkg, int uid, in Notification.Topic topic, int priority);
+ int getTopicPriority(String pkg, int uid, in Notification.Topic topic);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 9d8129c..3f1d113 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,3 +17,4 @@
package android.app;
parcelable Notification;
+parcelable Notification.Topic;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4e6548b..848b33f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -1433,6 +1434,9 @@
};
}
+ @SystemApi
+ public static final String TOPIC_DEFAULT = "system_default_topic";
+
private Topic topic;
public Topic getTopic() {
@@ -3419,6 +3423,10 @@
mN.extras.putStringArray(EXTRA_PEOPLE,
mPersonList.toArray(new String[mPersonList.size()]));
}
+ if (mN.topic == null) {
+ mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
+ R.string.default_notification_topic_label));
+ }
return mN;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 94b434c..89e974e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3616,6 +3616,48 @@
}
/**
+ * Called by a device owner to get the list of apps to keep around as APKs even if no user has
+ * currently installed it.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ *
+ * @return List of package names to keep cached.
+ * @hide
+ */
+ public List<String> getKeepUninstalledPackages(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getKeepUninstalledPackages(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by a device owner to set a list of apps to keep around as APKs even if no user has
+ * currently installed it.
+ *
+ * <p>Please note that setting this policy does not imply that specified apps will be
+ * automatically pre-cached.</p>
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageNames List of package names to keep cached.
+ * @hide
+ */
+ public void setKeepUninstalledPackages(@NonNull ComponentName admin,
+ @NonNull List<String> packageNames) {
+ if (mService != null) {
+ try {
+ mService.setKeepUninstalledPackages(admin, packageNames);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
* Called by a device owner to create a user with the specified name. The UserHandle returned
* by this method should not be persisted as user handles are recycled as users are removed and
* created. If you need to persist an identifier for this user, use
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 93a5503..c43fa9a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -231,4 +231,6 @@
String permission, int grantState);
int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
boolean isProvisioningAllowed(String action);
+ void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
+ List<String> getKeepUninstalledPackages(in ComponentName admin);
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index bf70d6c..905ac5e 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.NonNull;
+import java.util.List;
/**
* Package manager local system service interface.
@@ -115,4 +115,11 @@
*/
public abstract void grantDefaultPermissionsToDefaultSimCallManager(String packageName,
int userId);
+
+ /**
+ * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has
+ * currently installed it. The apps are not preloaded.
+ * @param packageList List of package names to keep cached.
+ */
+ public abstract void setKeepUninstalledPackages(List<String> packageList);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9034cc9..f560f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7758,6 +7758,7 @@
AUTO_TIME_ZONE,
POWER_SOUNDS_ENABLED,
DOCK_SOUNDS_ENABLED,
+ CHARGING_SOUNDS_ENABLED,
USB_MASS_STORAGE_ENABLED,
ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3fc70cc..b3cd8c11 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,6 +186,11 @@
void reportDropResult(IWindow window, boolean consumed);
/**
+ * Cancel the current drag operation.
+ */
+ void cancelDragAndDrop(IBinder dragToken);
+
+ /**
* Tell the OS that we've just dragged into a View that is willing to accept the drop
*/
void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 227d8f2..66381f9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3696,7 +3696,7 @@
/**
* Flag indicating that a drag can cross window boundaries. When
- * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
* with this flag set, all visible applications will be able to participate
* in the drag operation and receive the dragged content.
*
@@ -3741,7 +3741,7 @@
/**
* Flag indicating that the drag shadow will be opaque. When
- * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
* with this flag set, the drag shadow will be opaque, otherwise, it will be semitransparent.
*/
public static final int DRAG_FLAG_OPAQUE = 1 << 9;
@@ -19910,6 +19910,15 @@
}
/**
+ * @deprecated Use {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)
+ * startDragAndDrop()} for newer platform versions.
+ */
+ public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+ Object myLocalState, int flags) {
+ return startDragAndDrop(data, shadowBuilder, myLocalState, flags);
+ }
+
+ /**
* Starts a drag and drop operation. When your application calls this method, it passes a
* {@link android.view.View.DragShadowBuilder} object to the system. The
* system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
@@ -19926,9 +19935,10 @@
* {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
* </p>
* <p>
- * Your application can invoke startDrag() on any attached View object. The View object does not
- * need to be the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to
- * be related to the View the user selected for dragging.
+ * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
+ * int) startDragAndDrop()} on any attached View object. The View object does not need to be
+ * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
+ * to the View the user selected for dragging.
* </p>
* @param data A {@link android.content.ClipData} object pointing to the data to be
* transferred by the drag and drop operation.
@@ -19948,10 +19958,10 @@
* {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
* do a drag, and so no drag operation is in progress.
*/
- public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+ public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
Object myLocalState, int flags) {
if (ViewDebug.DEBUG_DRAG) {
- Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " flags=" + flags);
+ Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
}
boolean okay = false;
@@ -19970,11 +19980,11 @@
}
Surface surface = new Surface();
try {
- IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+ mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
flags, shadowSize.x, shadowSize.y, surface);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
- + " surface=" + surface);
- if (token != null) {
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
+ + mAttachInfo.mDragToken + " surface=" + surface);
+ if (mAttachInfo.mDragToken != null) {
Canvas canvas = surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19991,7 +20001,7 @@
// repurpose 'shadowSize' for the last touch point
root.getLastTouchPoint(shadowSize);
- okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+ okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -20009,6 +20019,39 @@
}
/**
+ * Cancels an ongoing drag and drop operation.
+ * <p>
+ * A {@link android.view.DragEvent} object with
+ * {@link android.view.DragEvent#getAction()} value of
+ * {@link android.view.DragEvent#ACTION_DRAG_ENDED} and
+ * {@link android.view.DragEvent#getResult()} value of {@code false}
+ * will be sent to every
+ * View that received {@link android.view.DragEvent#ACTION_DRAG_STARTED}
+ * even if they are not currently visible.
+ * </p>
+ * <p>
+ * This method can be called on any View in the same window as the View on which
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int) startDragAndDrop}
+ * was called.
+ * </p>
+ */
+ public final void cancelDragAndDrop() {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "cancelDragAndDrop");
+ }
+ if (mAttachInfo.mDragToken != null) {
+ try {
+ mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken);
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+ }
+ mAttachInfo.mDragToken = null;
+ } else {
+ Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+ }
+ }
+
+ /**
* Starts a move from {startX, startY}, the amount of the movement will be the offset
* between {startX, startY} and the new cursor positon.
* @param startX horizontal coordinate where the move started.
@@ -20030,7 +20073,8 @@
/**
* Handles drag events sent by the system following a call to
- * {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}.
+ * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
*<p>
* When the system calls this method, it passes a
* {@link android.view.DragEvent} object. A call to
@@ -22293,6 +22337,11 @@
final List<View> mPartialLayoutViews = new ArrayList<View>();
/**
+ * Used to track the identity of the current drag operation.
+ */
+ IBinder mDragToken;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6812fd1..11df9a3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -224,7 +224,7 @@
* NOTE: If you change the flags below make sure to reflect the changes
* the DisplayList class
*/
-
+
// When set, ViewGroup invalidates only the child's rectangle
// Set by default
static final int FLAG_CLIP_CHILDREN = 0x1;
@@ -269,7 +269,7 @@
/**
* When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
* to get the index of the child to draw for that iteration.
- *
+ *
* @hide
*/
protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
@@ -1327,7 +1327,7 @@
children[i].dispatchConfigurationChanged(newConfig);
}
}
-
+
/**
* {@inheritDoc}
*/
@@ -2214,7 +2214,7 @@
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
- final ArrayList<View> preorderedList = buildOrderedChildList();
+ final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
@@ -2347,6 +2347,18 @@
}
/**
+ * Provide custom ordering of views in which the touch will be dispatched.
+ *
+ * This is called within a tight loop, so you are not allowed to allocate objects, including
+ * the return array. Instead, you should return a pre-allocated list that will be cleared
+ * after the dispatch is finished.
+ * @hide
+ */
+ public ArrayList<View> buildTouchDispatchChildList() {
+ return buildOrderedChildList();
+ }
+
+ /**
* Finds the child which has accessibility focus.
*
* @return The child that has focus.
@@ -2787,7 +2799,7 @@
* @see #FOCUS_BEFORE_DESCENDANTS
* @see #FOCUS_AFTER_DESCENDANTS
* @see #FOCUS_BLOCK_DESCENDANTS
- * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
+ * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
*/
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
@@ -4104,7 +4116,7 @@
/**
* <p>Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.</p>
- *
+ *
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4120,7 +4132,7 @@
/**
* Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.
- *
+ *
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4560,7 +4572,7 @@
/**
* {@inheritDoc}
- *
+ *
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4579,7 +4591,7 @@
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
- *
+ *
* @param view the view to remove from the group
*/
public void removeViewInLayout(View view) {
@@ -4607,7 +4619,7 @@
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
- *
+ *
* @param index the position in the group of the view to remove
*/
public void removeViewAt(int index) {
@@ -4796,7 +4808,7 @@
/**
* Call this method to remove all child views from the
* ViewGroup.
- *
+ *
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5bbfc3f..2c0cd7a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5372,10 +5372,10 @@
}
}
- // When the drag operation ends, release any local state object
- // that may have been in use
+ // When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
setLocalDragState(null);
+ mAttachInfo.mDragToken = null;
}
}
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 47df4e8..41f1ce7 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -937,10 +937,11 @@
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mMenu.close();
mOverflowPopup = null;
+
+ super.onDismiss();
}
}
@@ -959,10 +960,11 @@
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mActionButtonPopup = null;
mOpenSubMenuId = 0;
+
+ super.onDismiss();
}
}
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index a53df88..027f874 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -19,7 +19,6 @@
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
-import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.ShowableListMenu;
import android.annotation.MenuRes;
@@ -45,7 +44,7 @@
private final MenuPopupHelper mPopup;
private OnMenuItemClickListener mMenuItemClickListener;
- private OnDismissListener mDismissListener;
+ private OnDismissListener mOnDismissListener;
private OnTouchListener mDragListener;
/**
@@ -114,20 +113,13 @@
mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
- mPopup.setCallback(new MenuPresenter.Callback() {
+ mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss(PopupMenu.this);
+ public void onDismiss() {
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss(PopupMenu.this);
}
}
-
- @Override
- public boolean onOpenSubMenu(MenuBuilder subMenu) {
- // The menu presenter will handle opening the submenu itself.
- // Nothing to do here.
- return false;
- }
});
}
@@ -259,7 +251,7 @@
* @param listener the listener to notify
*/
public void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
+ mOnDismissListener = listener;
}
/**
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 08c7935..c992c70 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -33,6 +33,7 @@
public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
public static final int ACTION_ZEN_ALLOW_PEEK = 261;
public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
+ public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index e674ecc..59d5f94 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -18,82 +18,104 @@
import com.android.internal.view.menu.MenuPresenter.Callback;
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
-import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
/**
* Presents a menu as a small, simple popup anchored to another view.
- *
- * @hide
*/
-public class MenuPopupHelper implements PopupWindow.OnDismissListener {
+public class MenuPopupHelper {
private final Context mContext;
+
+ // Immutable cached popup menu properties.
private final MenuBuilder mMenu;
private final boolean mOverflowOnly;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
+ // Mutable cached popup menu properties.
private View mAnchorView;
- private MenuPopup mPopup;
-
- private int mDropDownGravity = Gravity.NO_GRAVITY;
+ private int mDropDownGravity = Gravity.START;
private boolean mForceShowIcon;
- private boolean mShowTitle;
private Callback mPresenterCallback;
- private int mInitXOffset;
- private int mInitYOffset;
- public MenuPopupHelper(Context context, MenuBuilder menu) {
+ private MenuPopup mPopup;
+ private OnDismissListener mOnDismissListener;
+
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView) {
this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView,
+ boolean overflowOnly, @AttrRes int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+ @StyleRes int popupStyleRes) {
mContext = context;
mMenu = menu;
+ mAnchorView = anchorView;
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
- mAnchorView = anchorView;
- mPopup = createMenuPopup();
}
- private MenuPopup createMenuPopup() {
- if (mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableCascadingSubmenus)) {
- return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
- mOverflowOnly);
- }
- return new StandardMenuPopup(
- mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
+ mOnDismissListener = listener;
}
- public void setAnchorView(View anchor) {
+ /**
+ * Sets the view to which the popup window is anchored.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param anchor the view to which the popup window should be anchored
+ */
+ public void setAnchorView(@NonNull View anchor) {
mAnchorView = anchor;
- mPopup.setAnchorView(anchor);
}
- public void setForceShowIcon(boolean forceShow) {
- mForceShowIcon = forceShow;
- mPopup.setForceShowIcon(forceShow);
+ /**
+ * Sets whether the popup menu's adapter is forced to show icons in the
+ * menu item views.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param forceShowIcon {@code true} to force icons to be shown, or
+ * {@code false} for icons to be optionally shown
+ */
+ public void setForceShowIcon(boolean forceShowIcon) {
+ mForceShowIcon = forceShowIcon;
}
+ /**
+ * Sets the alignment of the popup window relative to the anchor view.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param gravity alignment of the popup relative to the anchor
+ */
public void setGravity(int gravity) {
mDropDownGravity = gravity;
- mPopup.setGravity(gravity);
}
+ /**
+ * @return alignment of the popup relative to the anchor
+ */
public int getGravity() {
return mDropDownGravity;
}
@@ -110,7 +132,11 @@
}
}
- public ShowableListMenu getPopup() {
+ @NonNull
+ public MenuPopup getPopup() {
+ if (mPopup == null) {
+ mPopup = createPopup();
+ }
return mPopup;
}
@@ -129,14 +155,28 @@
return false;
}
- mInitXOffset = 0;
- mInitYOffset = 0;
- mShowTitle = false;
-
- showPopup();
+ showPopup(0, 0, false, false);
return true;
}
+ /**
+ * Shows the popup menu and makes a best-effort to anchor it to the
+ * specified (x,y) coordinate relative to the anchor view.
+ * <p>
+ * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+ * display the popup with its top-left corner at (x,y) relative to the
+ * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+ * popup's top-right corner will be at (x,y).
+ * <p>
+ * If the popup cannot be displayed fully on-screen, this method will
+ * attempt to scroll the anchor view's ancestors and/or offset the popup
+ * such that it may be displayed fully on-screen.
+ *
+ * @param x x coordinate relative to the anchor view
+ * @param y y coordinate relative to the anchor view
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow(int x, int y) {
if (isShowing()) {
return true;
@@ -146,51 +186,104 @@
return false;
}
- mInitXOffset = x;
- mInitYOffset = y;
- mShowTitle = true;
-
- showPopup();
+ showPopup(x, y, true, true);
return true;
}
- private void showPopup() {
- mPopup = createMenuPopup();
- mPopup.setAnchorView(mAnchorView);
- mPopup.setCallback(mPresenterCallback);
- mPopup.setForceShowIcon(mForceShowIcon);
- mPopup.setGravity(mDropDownGravity);
- mPopup.setHorizontalOffset(mInitXOffset);
- mPopup.setShowTitle(mShowTitle);
- mPopup.setVerticalOffset(mInitYOffset);
+ /**
+ * Creates the popup and assigns cached properties.
+ *
+ * @return an initialized popup
+ */
+ @NonNull
+ private MenuPopup createPopup() {
+ final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableCascadingSubmenus);
- // In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface,
- // we must set the listener to this outer Helper rather than to the inner MenuPopup.
- // Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done
- // its own handling.
- mPopup.setOnDismissListener(this);
+ final MenuPopup popup;
+ if (enableCascadingSubmenus) {
+ popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ } else {
+ popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ }
- mPopup.addMenu(mMenu);
- mPopup.show();
+ // Assign immutable properties.
+ popup.addMenu(mMenu);
+ popup.setOnDismissListener(mInternalOnDismissListener);
+
+ // Assign mutable properties. These may be reassigned later.
+ popup.setAnchorView(mAnchorView);
+ popup.setCallback(mPresenterCallback);
+ popup.setForceShowIcon(mForceShowIcon);
+ popup.setGravity(mDropDownGravity);
+
+ return popup;
}
+ private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
+ if (resolveOffsets) {
+ // If the resolved drop-down gravity is RIGHT, the popup's right
+ // edge will be aligned with the anchor view. Adjust by the anchor
+ // width such that the top-right corner is at the X offset.
+ final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+ mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ xOffset -= mAnchorView.getWidth();
+ }
+ }
+
+ final MenuPopup popup = getPopup();
+ popup.setHorizontalOffset(xOffset);
+ popup.setVerticalOffset(yOffset);
+ popup.setShowTitle(showTitle);
+ popup.show();
+ }
+
+ /**
+ * Dismisses the popup, if showing.
+ */
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
- @Override
- public void onDismiss() {
+ /**
+ * Called after the popup has been dismissed.
+ * <p>
+ * <strong>Note:</strong> Subclasses should call the super implementation
+ * last to ensure that any necessary tear down has occurred before the
+ * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+ * is called.
+ */
+ protected void onDismiss() {
mPopup = null;
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
}
public boolean isShowing() {
return mPopup != null && mPopup.isShowing();
}
- public void setCallback(MenuPresenter.Callback cb) {
+ public void setCallback(@Nullable MenuPresenter.Callback cb) {
mPresenterCallback = cb;
- mPopup.setCallback(cb);
+ if (mPopup != null) {
+ mPopup.setCallback(cb);
+ }
}
+
+ /**
+ * Listener used to proxy dismiss callbacks to the helper's owner.
+ */
+ private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ MenuPopupHelper.this.onDismiss();
+ }
+ };
}
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 3b7bce4..a694aca 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,6 +30,10 @@
* orientation when it can't fit its child views horizontally.
*/
public class ButtonBarLayout extends LinearLayout {
+ // Whether to allow vertically stacked button bars. This is disabled for
+ // configurations with a small (e.g. less than 320dp) screen height. -->
+ private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
+
/** Whether the current configuration allows stacking. */
private boolean mAllowStacking;
@@ -38,8 +42,12 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ final boolean allowStackingDefault =
+ context.getResources().getConfiguration().screenHeightDp
+ >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+ allowStackingDefault);
ta.recycle();
}
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index 16e8296..d747686 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -19,18 +19,24 @@
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.os.RemoteException;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.Window;
-import android.util.Log;
import com.android.internal.R;
import com.android.internal.policy.PhoneWindow;
+import java.util.ArrayList;
+
/**
* This class represents the special screen elements to control a window on freeform
* environment.
@@ -38,8 +44,8 @@
* <ul>
* <li>The caption, containing the system buttons like maximize, close and such as well as
* allowing the user to drag the window around.</li>
- * After creating the view, the function
- * {@link #setPhoneWindow} needs to be called to make
+ * </ul>
+ * After creating the view, the function {@link #setPhoneWindow} needs to be called to make
* the connection to it's owning PhoneWindow.
* Note: At this time the application can change various attributes of the DecorView which
* will break things (in settle/unexpected ways):
@@ -48,9 +54,29 @@
* <li>setSurfaceFormat</li>
* <li>..</li>
* </ul>
+ *
+ * Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
+ * overlaying caption on the content and drawing.
+ *
+ * First, no matter where the content View gets added, it will always be the first child and the
+ * caption will be the second. This way the caption will always be drawn on top of the content when
+ * overlaying is enabled.
+ *
+ * Second, the touch dispatch is customized to handle overlaying. This is what happens when touch
+ * is dispatched on the caption area while overlaying it on content:
+ * <ul>
+ * <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
+ * down action is performed on top close or maximize buttons; the reason for that is we want these
+ * buttons to always work.</li>
+ * <li>The content View will receive the touch event. Mind that content is actually underneath the
+ * caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
+ * {@link #buildTouchDispatchChildList()}.</li>
+ * <li>If the touch event is not consumed by the content View, it will go to the caption View
+ * and the dragging logic will be executed.</li>
+ * </ul>
*/
-public class DecorCaptionView extends ViewGroup
- implements View.OnClickListener, View.OnTouchListener {
+public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
+ GestureDetector.OnGestureListener {
private final static String TAG = "DecorCaptionView";
private PhoneWindow mOwner = null;
private boolean mShow = false;
@@ -65,17 +91,42 @@
private View mCaption;
private View mContent;
+ private View mMaximize;
+ private View mClose;
+
+ // Fields for detecting drag events.
+ private int mTouchDownX;
+ private int mTouchDownY;
+ private boolean mCheckForDragging;
+ private int mDragSlop;
+
+ // Fields for detecting and intercepting click events on close/maximize.
+ private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
+ // We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
+ // with existing click detection.
+ private GestureDetector mGestureDetector;
+ private final Rect mCloseRect = new Rect();
+ private final Rect mMaximizeRect = new Rect();
+ private View mClickTarget;
public DecorCaptionView(Context context) {
super(context);
+ init(context);
}
public DecorCaptionView(Context context, AttributeSet attrs) {
super(context, attrs);
+ init(context);
}
public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mGestureDetector = new GestureDetector(context, this);
}
@Override
@@ -88,13 +139,47 @@
mOwner = owner;
mShow = show;
mOverlayWithAppContent = owner.getOverlayDecorCaption();
+ if (mOverlayWithAppContent) {
+ // The caption is covering the content, so we make its background transparent to make
+ // the content visible.
+ mCaption.setBackgroundColor(Color.TRANSPARENT);
+ }
updateCaptionVisibility();
// By changing the outline provider to BOUNDS, the window can remove its
// background without removing the shadow.
mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+ mMaximize = findViewById(R.id.maximize_window);
+ mClose = findViewById(R.id.close_window);
+ }
- findViewById(R.id.maximize_window).setOnClickListener(this);
- findViewById(R.id.close_window).setOnClickListener(this);
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // If the user starts touch on the maximize/close buttons, we immediately intercept, so
+ // that these buttons are always clickable.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ if (mMaximizeRect.contains(x, y)) {
+ mClickTarget = mMaximize;
+ }
+ if (mCloseRect.contains(x, y)) {
+ mClickTarget = mClose;
+ }
+ }
+ return mClickTarget != null;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mClickTarget != null) {
+ mGestureDetector.onTouchEvent(event);
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mClickTarget = null;
+ }
+ return true;
+ }
+ return false;
}
@Override
@@ -102,25 +187,31 @@
// Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
// the old input device events get cancelled first. So no need to remember the kind of
// input device we are listening to.
+ final int x = (int) e.getX();
+ final int y = (int) e.getY();
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (!mShow) {
// When there is no caption we should not react to anything.
return false;
}
- // A drag action is started if we aren't dragging already and the starting event is
- // either a left mouse button or any other input device.
- if (!mDragging &&
- (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
- mDragging = true;
- mLeftMouseButtonReleased = false;
- startMovingTask(e.getRawX(), e.getRawY());
+ // Checking for a drag action is started if we aren't dragging already and the
+ // starting event is either a left mouse button or any other input device.
+ if (((e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0))) {
+ mCheckForDragging = true;
+ mTouchDownX = x;
+ mTouchDownY = y;
}
break;
case MotionEvent.ACTION_MOVE:
- if (mDragging && !mLeftMouseButtonReleased) {
+ if (!mDragging && mCheckForDragging && passedSlop(x, y)) {
+ mCheckForDragging = false;
+ mDragging = true;
+ mLeftMouseButtonReleased = false;
+ startMovingTask(e.getRawX(), e.getRawY());
+ } else if (mDragging && !mLeftMouseButtonReleased) {
if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
(e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
// There is no separate mouse button up call and if the user mixes mouse
@@ -138,9 +229,25 @@
}
// Abort the ongoing dragging.
mDragging = false;
- return true;
+ return !mCheckForDragging;
}
- return mDragging;
+ return mDragging || mCheckForDragging;
+ }
+
+ @Override
+ public ArrayList<View> buildTouchDispatchChildList() {
+ mTouchDispatchList.ensureCapacity(3);
+ if (mCaption != null) {
+ mTouchDispatchList.add(mCaption);
+ }
+ if (mContent != null) {
+ mTouchDispatchList.add(mContent);
+ }
+ return mTouchDispatchList;
+ }
+
+ private boolean passedSlop(int x, int y) {
+ return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
}
/**
@@ -153,15 +260,6 @@
}
@Override
- public void onClick(View view) {
- if (view.getId() == R.id.maximize_window) {
- maximizeWindow();
- } else if (view.getId() == R.id.close_window) {
- mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
- }
- }
-
- @Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!(params instanceof MarginLayoutParams)) {
throw new IllegalArgumentException(
@@ -205,8 +303,12 @@
if (mCaption.getVisibility() != View.GONE) {
mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
captionHeight = mCaption.getBottom() - mCaption.getTop();
+ mMaximize.getHitRect(mMaximizeRect);
+ mClose.getHitRect(mCloseRect);
} else {
captionHeight = 0;
+ mMaximizeRect.setEmpty();
+ mCloseRect.setEmpty();
}
if (mContent != null) {
@@ -291,4 +393,39 @@
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ if (mClickTarget == mMaximize) {
+ maximizeWindow();
+ } else if (mClickTarget == mClose) {
+ mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return false;
+ }
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 3d96fab..e4e73a4 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -544,39 +544,6 @@
float totalAdvance;
};
-// Same values used by Skia
-#define kStdStrikeThru_Offset (-6.0f / 21.0f)
-#define kStdUnderline_Offset (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void drawTextDecorations(Canvas* canvas, float x, float y, float length, const SkPaint& paint) {
- uint32_t flags;
- SkDrawFilter* drawFilter = canvas->getDrawFilter();
- if (drawFilter) {
- SkPaint paintCopy(paint);
- drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
- flags = paintCopy.getFlags();
- } else {
- flags = paint.getFlags();
- }
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- SkScalar left = x;
- SkScalar right = x + length;
- float textSize = paint.getTextSize();
- float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
- if (flags & SkPaint::kUnderlineText_Flag) {
- SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
- }
- if (flags & SkPaint::kStrikeThruText_Flag) {
- SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
- }
- }
-}
-
void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount,
float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) {
// minikin may modify the original paint
@@ -586,8 +553,8 @@
MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
size_t nGlyphs = layout.nGlyphs();
- uint16_t* glyphs = new uint16_t[nGlyphs];
- float* pos = new float[nGlyphs * 2];
+ std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
+ std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
@@ -597,13 +564,9 @@
bounds.offset(x, y);
}
- DrawTextFunctor f(layout, canvas, glyphs, pos, paint, x, y, bounds, layout.getAdvance());
+ DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(),
+ paint, x, y, bounds, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
-
- drawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
-
- delete[] glyphs;
- delete[] pos;
}
static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
diff --git a/core/res/res/layout/alert_dialog_button_bar_material.xml b/core/res/res/layout/alert_dialog_button_bar_material.xml
index 6e102f3..f7974a5 100644
--- a/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -27,7 +27,6 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="bottom"
- android:allowStacking="@bool/allow_stacked_button_bar"
style="?attr/buttonBarStyle">
<Button
diff --git a/core/res/res/values-h320dp/bools.xml b/core/res/res/values-h320dp/bools.xml
deleted file mode 100644
index 3bbfe96..0000000
--- a/core/res/res/values-h320dp/bools.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<resources>
- <bool name="allow_stacked_button_bar">true</bool>
-</resources>
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 7c63950..457131a 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -25,8 +25,4 @@
<bool name="show_ongoing_ime_switcher">true</bool>
<bool name="action_bar_expanded_action_views_exclusive">true</bool>
<bool name="target_honeycomb_needs_options_menu">true</bool>
-
- <!-- Whether to allow vertically stacked button bars. This is disabled for
- configurations with a small (e.g. less than 320dp) screen height. -->
- <bool name="allow_stacked_button_bar">false</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 057790a..539baa5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,9 +1946,9 @@
See {@link com.android.server.notification.NotificationSignalExtractor} -->
<string-array name="config_notificationSignalExtractors">
<item>com.android.server.notification.ValidateNotificationPeople</item>
- <item>com.android.server.notification.PackagePriorityExtractor</item>
+ <item>com.android.server.notification.TopicPriorityExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
- <item>com.android.server.notification.PackageVisibilityExtractor</item>
+ <item>com.android.server.notification.TopicVisibilityExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 00c0fe8..1964bec 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4072,4 +4072,6 @@
<item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
<item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
</plurals>
+
+ <string name="default_notification_topic_label">Miscellaneous</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1e325b1..edd3555 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2321,7 +2321,6 @@
<java-symbol type="string" name="lockscreen_access_pattern_area" />
- <java-symbol type="bool" name="allow_stacked_button_bar" />
<java-symbol type="bool" name="config_eap_sim_based_auth_supported" />
<java-symbol type="array" name="config_cell_retries_per_error_code" />
@@ -2337,4 +2336,5 @@
<java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
<java-symbol type="string" name="config_packagedKeyboardName" />
+ <java-symbol type="string" name="default_notification_topic_label" />
</resources>
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 58bf654..4186007 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -81,7 +81,7 @@
key 39 SEMICOLON
key 40 APOSTROPHE
key 14 DEL
-
+
key 44 Z
key 45 X
key 46 C
@@ -93,7 +93,7 @@
key 52 PERIOD
key 53 SLASH
key 28 ENTER
-
+
key 56 ALT_LEFT
key 100 ALT_RIGHT
key 42 SHIFT_LEFT
@@ -101,7 +101,7 @@
key 15 TAB
key 57 SPACE
key 150 EXPLORER
-key 155 ENVELOPE
+key 155 ENVELOPE
key 12 MINUS
key 13 EQUALS
@@ -110,3 +110,16 @@
# On an AT keyboard: ESC, F10
key 1 BACK
key 68 MENU
+
+# App switch = Overview key
+key 580 APP_SWITCH
+
+# Media control keys
+key 160 MEDIA_CLOSE
+key 161 MEDIA_EJECT
+key 163 MEDIA_NEXT
+key 164 MEDIA_PLAY_PAUSE
+key 165 MEDIA_PREVIOUS
+key 166 MEDIA_STOP
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0a57d50..cc68fb2 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -37,6 +37,7 @@
AnimatorManager.cpp \
AssetAtlas.cpp \
Caches.cpp \
+ Canvas.cpp \
CanvasState.cpp \
ClipArea.cpp \
DamageAccumulator.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d2d3285..d13d7ef 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -24,6 +24,9 @@
#include "utils/GLUtils.h"
#include "VertexBuffer.h"
+#include <algorithm>
+#include <math.h>
+
namespace android {
namespace uirenderer {
@@ -183,6 +186,10 @@
renderer.renderGlop(state, glop);
}
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("todo");
+}
+
void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -270,6 +277,91 @@
renderer.renderGlop(state, glop);
}
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, (const char*) op.glyphs,
+ op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+
+ // TODO: Implement better clipping for scaled/rotated text
+ const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool hasActiveLayer = false; // TODO
+ fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+ op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
OffscreenBuffer* buffer = *op.layerHandle;
diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp
new file mode 100644
index 0000000..bc88c81
--- /dev/null
+++ b/libs/hwui/Canvas.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "Canvas.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+ uint32_t flags;
+ SkDrawFilter* drawFilter = getDrawFilter();
+ if (drawFilter) {
+ SkPaint paintCopy(paint);
+ drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ flags = paintCopy.getFlags();
+ } else {
+ flags = paint.getFlags();
+ }
+ if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ // Same values used by Skia
+ const float kStdStrikeThru_Offset = (-6.0f / 21.0f);
+ const float kStdUnderline_Offset = (1.0f / 9.0f);
+ const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+ SkScalar left = x;
+ SkScalar right = x + length;
+ float textSize = paint.getTextSize();
+ float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ }
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 4bd4ac8..b585a27 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -149,16 +149,12 @@
// Text
/**
* drawText: count is of glyphs
- * totalAdvance is ignored in software renderering, used by hardware renderer for
- * text decorations (underlines, strikethroughs).
+ * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) = 0;
- /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) = 0;
/** drawTextOnPath: count is of glyphs */
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) = 0;
@@ -171,6 +167,9 @@
* to be added to each glyph's position to get its absolute position.
*/
virtual bool drawTextAbsolutePos() const = 0;
+
+protected:
+ void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
};
}; // namespace android
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index f5e5735..759c12a 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -423,18 +423,6 @@
addDrawOp(op);
}
-void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions,
- int count, int posCount, const SkPaint& paint) {
- if (!text || count <= 0) return;
-
- int bytesCount = 2 * count;
- positions = refBuffer<float>(positions, count * 2);
-
- DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount),
- bytesCount, count, positions, refPaint(&paint));
- addDrawOp(op);
-}
-
void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
int count, const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
@@ -450,6 +438,7 @@
DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
x, y, positions, refPaint(&paint), totalAdvance, bounds);
addDrawOp(op);
+ drawTextDecorations(x, y, totalAdvance, paint);
}
void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 609103b..bf98f79 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -212,8 +212,6 @@
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 772aa72..977b53c 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1278,24 +1278,6 @@
float mVOffset;
};
-class DrawPosTextOp : public DrawSomeTextOp {
-public:
- DrawPosTextOp(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint)
- : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
- /* TODO: inherit from DrawBounded and init mLocalBounds */
- }
-
- virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
- renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint);
- }
-
- virtual const char* name() override { return "DrawPosText"; }
-
-private:
- const float* mPositions;
-};
-
class DrawTextOp : public DrawStrokableOp {
public:
DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index ccf0b48..5f33cae 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,13 +21,20 @@
#include "Extensions.h"
#include "Glop.h"
#include "GlopBuilder.h"
-#include "OpenGLRenderer.h"
#include "PixelBuffer.h"
#include "Rect.h"
#include "renderstate/RenderState.h"
#include "utils/Blur.h"
#include "utils/Timing.h"
+
+#if HWUI_NEW_OPS
+#include "BakedOpState.h"
+#include "BakedOpRenderer.h"
+#else
+#include "OpenGLRenderer.h"
+#endif
+
#include <algorithm>
#include <cutils/properties.h>
#include <SkGlyph.h>
@@ -59,14 +66,25 @@
int transformFlags = pureTranslate
? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
Glop glop;
+#if HWUI_NEW_OPS
+ GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
+ .setRoundRectClipState(bakedState->roundRectClipState)
+ .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
+ .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+ .setTransform(bakedState->computedState.transform, transformFlags)
+ .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+ .build();
+ renderer->renderGlop(*bakedState, glop);
+#else
GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
+ .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
.setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
.setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha)
.setTransform(*(renderer->currentSnapshot()), transformFlags)
.setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
- .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
.build();
renderer->renderGlop(glop);
+#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -539,7 +557,7 @@
}
FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
+ int numGlyphs, float radius, const float* positions) {
checkInit();
DropShadow image;
@@ -558,7 +576,7 @@
mBounds = nullptr;
Rect bounds;
- mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
+ mCurrentFont->measure(paint, text, numGlyphs, &bounds, positions);
uint32_t intRadius = Blur::convertRadiusToInt(radius);
uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
@@ -590,7 +608,7 @@
// text has non-whitespace, so draw and blur to create the shadow
// NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
// TODO: don't draw pure whitespace in the first place, and avoid needing this check
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
+ mCurrentFont->render(paint, text, numGlyphs, penX, penY,
Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
// Unbind any PBO we might have used
@@ -635,15 +653,15 @@
}
bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
- const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
+ mCurrentFont->render(paint, text, numGlyphs, x, y, positions);
if (forceFinish) {
finishRender();
@@ -653,15 +671,15 @@
}
bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) {
+ int numGlyphs, const SkPath* path, float hOffset, float vOffset,
+ Rect* bounds, TextDrawFunctor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+ mCurrentFont->render(paint, text, numGlyphs, path, hOffset, vOffset);
finishRender();
return mDrawn;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 8172312..87cfe7f 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -44,13 +44,28 @@
namespace android {
namespace uirenderer {
+#if HWUI_NEW_OPS
+class BakedOpState;
+class BakedOpRenderer;
+#else
class OpenGLRenderer;
+#endif
class TextDrawFunctor {
public:
- TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+ TextDrawFunctor(
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer,
+ const BakedOpState* bakedState,
+#else
+ OpenGLRenderer* renderer,
+#endif
+ float x, float y, bool pureTranslate,
int alpha, SkXfermode::Mode mode, const SkPaint* paint)
: renderer(renderer)
+#if HWUI_NEW_OPS
+ , bakedState(bakedState)
+#endif
, x(x)
, y(y)
, pureTranslate(pureTranslate)
@@ -61,7 +76,12 @@
void draw(CacheTexture& texture, bool linearFiltering);
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer;
+ const BakedOpState* bakedState;
+#else
OpenGLRenderer* renderer;
+#endif
float x;
float y;
bool pureTranslate;
@@ -83,15 +103,13 @@
void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
void endPrecaching();
- // bounds is an out parameter
bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
- Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true);
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true);
- // bounds is an out parameter
bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor);
+ int numGlyphs, const SkPath* path,
+ float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor);
struct DropShadow {
uint32_t width;
@@ -103,8 +121,8 @@
// After renderDropShadow returns, the called owns the memory in DropShadow.image
// and is responsible for releasing it when it's done with it
- DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, float radius, const float* positions);
+ DropShadow renderDropShadow(const SkPaint* paint, const char *text, int numGlyphs,
+ float radius, const float* positions);
void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 96cac7e..5e954ae 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -671,6 +671,13 @@
currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
}
+void OpReorderer::onLinesOp(const LinesOp& op) {
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
+ if (!bakedStateOp) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+
+}
+
void OpReorderer::onRectOp(const RectOp& op) {
BakedOpState* bakedStateOp = tryBakeOpState(op);
if (!bakedStateOp) return; // quick rejected
@@ -683,6 +690,17 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
+void OpReorderer::onTextOp(const TextOp& op) {
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
+ if (!bakedStateOp) return; // quick rejected
+
+ // TODO: better handling of shader (since we won't care about color then)
+ batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
+ ? OpBatchType::Text : OpBatchType::ColorText;
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+ currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
+}
+
void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
float contentTranslateX, float contentTranslateY,
const Rect& repaintRect,
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 12c4607..e386b1c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1950,7 +1950,7 @@
}
void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
- int bytesCount, int count, const float* positions,
+ int count, const float* positions,
FontRenderer& fontRenderer, int alpha, float x, float y) {
mCaches.textureState().activateTexture(0);
@@ -1963,7 +1963,7 @@
// if shader-based correction is enabled
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
ShadowTexture* texture = mCaches.dropShadowCache.get(
- paint, text, bytesCount, count, textShadow.radius, positions);
+ paint, text, count, textShadow.radius, positions);
// If the drop shadow exceeds the max texture size or couldn't be
// allocated, skip drawing
if (!texture) return;
@@ -1991,57 +1991,6 @@
&& PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
-void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint) {
- if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
- return;
- }
-
- // NOTE: Skia does not support perspective transform on drawPosText yet
- if (!currentTransform()->isSimple()) {
- return;
- }
-
- mRenderState.scissor().setEnabled(true);
-
- float x = 0.0f;
- float y = 0.0f;
- const bool pureTranslate = currentTransform()->isPureTranslate();
- if (pureTranslate) {
- x = floorf(x + currentTransform()->getTranslateX() + 0.5f);
- y = floorf(y + currentTransform()->getTranslateY() + 0.5f);
- }
-
- FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
- fontRenderer.setFont(paint, SkMatrix::I());
-
- int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
- alpha, 0.0f, 0.0f);
- }
-
- // Pick the appropriate texture filtering
- bool linearFilter = currentTransform()->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
- }
- fontRenderer.setTextureFiltering(linearFilter);
-
- const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
- Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
- if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
- positions, hasLayer() ? &bounds : nullptr, &functor)) {
- dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
- mDirty = true;
- }
-
-}
-
bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
if (CC_LIKELY(transform.isPureTranslate())) {
outMatrix->setIdentity();
@@ -2166,7 +2115,7 @@
if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
fontRenderer.setFont(paint, SkMatrix::I());
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ drawTextShadow(paint, text, count, positions, fontRenderer,
alpha, oldX, oldY);
}
@@ -2195,17 +2144,22 @@
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
+#endif
// don't call issuedrawcommand, do it at end of batch
bool forceFinish = (drawOpMode != DrawOpMode::kDefer);
if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(&paintCopy, clip, text, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
} else {
- status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(paint, clip, text, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
}
@@ -2216,8 +2170,6 @@
dirtyLayerUnchecked(layerBounds, getRegion());
}
- drawTextDecorations(totalAdvance, oldX, oldY, paint);
-
mDirty = true;
}
@@ -2236,12 +2188,17 @@
int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
+#endif
const Rect* clip = &writableSnapshot()->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
- if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+ if (fontRenderer.renderTextOnPath(paint, clip, text, count, path,
hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) {
dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
mDirty = true;
@@ -2375,56 +2332,6 @@
renderGlop(glop);
}
-// Same values used by Skia
-#define kStdStrikeThru_Offset (-6.0f / 21.0f)
-#define kStdUnderline_Offset (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
- const SkPaint* paint) {
- // Handle underline and strike-through
- uint32_t flags = paint->getFlags();
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- SkPaint paintCopy(*paint);
-
- if (CC_LIKELY(underlineWidth > 0.0f)) {
- const float textSize = paintCopy.getTextSize();
- const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f);
-
- const float left = x;
- float top = 0.0f;
-
- int linesCount = 0;
- if (flags & SkPaint::kUnderlineText_Flag) linesCount++;
- if (flags & SkPaint::kStrikeThruText_Flag) linesCount++;
-
- const int pointsCount = 4 * linesCount;
- float points[pointsCount];
- int currentPoint = 0;
-
- if (flags & SkPaint::kUnderlineText_Flag) {
- top = y + textSize * kStdUnderline_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- if (flags & SkPaint::kStrikeThruText_Flag) {
- top = y + textSize * kStdStrikeThru_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- paintCopy.setStrokeWidth(strokeWidth);
-
- drawLines(&points[0], pointsCount, &paintCopy);
- }
- }
-}
-
void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
if (mState.currentlyIgnored()) {
return;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 400c225..84bc9b0 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -193,8 +193,6 @@
void drawPoints(const float* points, int count, const SkPaint* paint);
void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
float hOffset, float vOffset, const SkPaint* paint);
- void drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint);
void drawText(const char* text, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode = DrawOpMode::kImmediate);
@@ -637,24 +635,11 @@
*/
void drawConvexPath(const SkPath& path, const SkPaint* paint);
- /**
- * Draws text underline and strike-through if needed.
- *
- * @param text The text to decor
- * @param bytesCount The number of bytes in the text
- * @param totalAdvance The total advance in pixels, defines underline/strikethrough length
- * @param x The x coordinate where the text will be drawn
- * @param y The y coordinate where the text will be drawn
- * @param paint The paint to draw the text with
- */
- void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
-
/**
* Draws shadow layer on text (with optional positions).
*
* @param paint The paint to draw the shadow with
* @param text The text to draw
- * @param bytesCount The number of bytes in the text
* @param count The number of glyphs in the text
* @param positions The x, y positions of individual glyphs (or NULL)
* @param fontRenderer The font renderer object
@@ -662,7 +647,7 @@
* @param x The x coordinate where the shadow will be drawn
* @param y The y coordinate where the shadow will be drawn
*/
- void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+ void drawTextShadow(const SkPaint* paint, const char* text, int count,
const float* positions, FontRenderer& fontRenderer, int alpha,
float x, float y);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index ef05367..127dca5 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
+#include "font/FontUtil.h"
#include "Matrix.h"
#include "Rect.h"
#include "RenderNode.h"
@@ -42,10 +43,12 @@
*/
#define MAP_OPS(OP_FN) \
OP_FN(BitmapOp) \
+ OP_FN(LinesOp) \
OP_FN(RectOp) \
OP_FN(RenderNodeOp) \
OP_FN(ShadowOp) \
OP_FN(SimpleRectsOp) \
+ OP_FN(TextOp) \
OP_FN(BeginLayerOp) \
OP_FN(EndLayerOp) \
OP_FN(LayerOp)
@@ -98,6 +101,10 @@
bool skipInOrderDraw = false;
};
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Standard Ops
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
struct BitmapOp : RecordedOp {
BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
: SUPER(BitmapOp)
@@ -106,6 +113,15 @@
// TODO: asset atlas/texture id lookup?
};
+struct LinesOp : RecordedOp {
+ LinesOp(BASE_PARAMS, const float* points, const int floatCount)
+ : SUPER(LinesOp)
+ , points(points)
+ , floatCount(floatCount) {}
+ const float* points;
+ const int floatCount;
+};
+
struct RectOp : RecordedOp {
RectOp(BASE_PARAMS)
: SUPER(RectOp) {}
@@ -148,6 +164,27 @@
const size_t vertexCount;
};
+struct TextOp : RecordedOp {
+ TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount,
+ float x, float y)
+ : SUPER(TextOp)
+ , glyphs(glyphs)
+ , positions(positions)
+ , glyphCount(glyphCount)
+ , x(x)
+ , y(y) {}
+ const glyph_t* glyphs;
+ const float* positions;
+ const int glyphCount;
+ const float x;
+ const float y;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Layers
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
/**
* Stateful operation! denotes the creation of an off-screen layer,
* and that commands following will render into it.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 6ab253c..61fa384 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -230,12 +230,9 @@
void RecordingCanvas::drawPaint(const SkPaint& paint) {
// TODO: more efficient recording?
- Matrix4 identity;
- identity.loadIdentity();
-
addOp(new (alloc()) RectOp(
mState.getRenderTargetClipBounds(),
- identity,
+ Matrix4::identity(),
mState.getRenderTargetClipBounds(),
refPaint(&paint)));
}
@@ -244,9 +241,30 @@
void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
-void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
- LOG_ALWAYS_FATAL("TODO!");
+
+void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
+ if (floatCount < 4) return;
+ floatCount &= ~0x3; // round down to nearest four
+
+ Rect unmappedBounds(points[0], points[1], points[0], points[1]);
+ for (int i = 2; i < floatCount; i += 2) {
+ unmappedBounds.left = std::min(unmappedBounds.left, points[i]);
+ unmappedBounds.right = std::max(unmappedBounds.right, points[i]);
+ unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]);
+ unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]);
+ }
+
+ // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
+ // 1.0 stroke, treat 1.0 as minimum.
+ unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f);
+
+ addOp(new (alloc()) LinesOp(
+ unmappedBounds,
+ *mState.currentSnapshot()->transform,
+ mState.getRenderTargetClipBounds(),
+ refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
+
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) RectOp(
Rect(left, top, right, bottom),
@@ -388,17 +406,24 @@
}
// Text
-void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
+void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) {
- LOG_ALWAYS_FATAL("TODO!");
+ if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+ positions = refBuffer<float>(positions, glyphCount * 2);
+
+ addOp(new (alloc()) TextOp(
+ Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
+ *(mState.currentSnapshot()->transform),
+ mState.getRenderTargetClipBounds(),
+ refPaint(&paint), glyphs, positions, glyphCount, x, y));
+ drawTextDecorations(x, y, totalAdvance, paint);
}
-void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) {
- LOG_ALWAYS_FATAL("TODO!");
-}
+
void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
+ // NOTE: can't use refPaint() directly, since it forces left alignment
LOG_ALWAYS_FATAL("TODO!");
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index f26b0c8..736cc9e 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -178,8 +178,6 @@
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
@@ -221,6 +219,15 @@
return cachedPath;
}
+ /**
+ * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
+ * based on paint generation ID)
+ *
+ * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
+ * since alignment offsetting has been done at a higher level. This is done to essentially all
+ * copied paints, since the deduping can mean a paint is shared by drawText commands and other
+ * types (which wouldn't care about alignment).
+ */
inline const SkPaint* refPaint(const SkPaint* paint) {
if (!paint) return nullptr;
@@ -239,10 +246,11 @@
// In the unlikely event that 2 unique paints have the same hash we do a
// object equality check to ensure we don't erroneously dedup them.
if (cachedPaint == nullptr || *cachedPaint != *paint) {
- cachedPaint = new SkPaint(*paint);
- std::unique_ptr<const SkPaint> copy(cachedPaint);
- mDisplayList->paints.push_back(std::move(copy));
+ SkPaint* copy = new SkPaint(*paint);
+ copy->setTextAlign(SkPaint::kLeft_Align);
+ cachedPaint = copy;
+ mDisplayList->paints.emplace_back(copy);
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 0736a10..472aad7 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -260,13 +260,6 @@
bottom = std::max(bottom, y);
}
- void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
- left = std::min(left, otherLeft);
- top = std::min(top, otherTop);
- right = std::max(right, otherRight);
- bottom = std::max(bottom, otherBottom);
- }
-
SkRect toSkRect() const {
return SkRect::MakeLTRB(left, top, right, bottom);
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 6d3dfac..96c1a7c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -131,8 +131,6 @@
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
@@ -152,7 +150,6 @@
void drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode);
- void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
SkAutoTUnref<SkCanvas> mCanvas;
std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
@@ -712,22 +709,7 @@
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
-}
-
-void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount,
- const SkPaint& paint) {
- SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
- int indx;
- for (indx = 0; indx < posCount; indx++) {
- posPtr[indx].fX = positions[indx << 1];
- posPtr[indx].fY = positions[(indx << 1) + 1];
- }
-
- SkPaint paintCopy(paint);
- paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
- mCanvas->drawPosText(text, count, posPtr, paintCopy);
-
- delete[] posPtr;
+ drawTextDecorations(x, y, totalAdvance, paint);
}
void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index b7a76ba..996ac8e 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -30,8 +30,7 @@
///////////////////////////////////////////////////////////////////////////////
hash_t ShadowText::hash() const {
- uint32_t charCount = len / sizeof(char16_t);
- uint32_t hash = JenkinsHashMix(0, len);
+ uint32_t hash = JenkinsHashMix(0, glyphCount);
hash = JenkinsHashMix(hash, android::hash_type(radius));
hash = JenkinsHashMix(hash, android::hash_type(textSize));
hash = JenkinsHashMix(hash, android::hash_type(typeface));
@@ -40,10 +39,10 @@
hash = JenkinsHashMix(hash, android::hash_type(scaleX));
if (text) {
hash = JenkinsHashMixShorts(
- hash, reinterpret_cast<const uint16_t*>(text), charCount);
+ hash, reinterpret_cast<const uint16_t*>(text), glyphCount);
}
if (positions) {
- for (uint32_t i = 0; i < charCount * 2; i++) {
+ for (uint32_t i = 0; i < glyphCount * 2; i++) {
hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
}
}
@@ -51,7 +50,7 @@
}
int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
- int deltaInt = int(lhs.len) - int(rhs.len);
+ int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
if (deltaInt != 0) return deltaInt;
deltaInt = lhs.flags - rhs.flags;
@@ -76,7 +75,7 @@
if (!lhs.text) return -1;
if (!rhs.text) return +1;
- deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+ deltaInt = memcmp(lhs.text, rhs.text, lhs.glyphCount * sizeof(glyph_t));
if (deltaInt != 0) return deltaInt;
}
@@ -84,7 +83,7 @@
if (!lhs.positions) return -1;
if (!rhs.positions) return +1;
- return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+ return memcmp(lhs.positions, rhs.positions, lhs.glyphCount << 1);
}
return 0;
@@ -168,16 +167,16 @@
mCache.clear();
}
-ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
- int numGlyphs, float radius, const float* positions) {
- ShadowText entry(paint, radius, len, text, positions);
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* glyphs, int numGlyphs,
+ float radius, const float* positions) {
+ ShadowText entry(paint, radius, numGlyphs * 2, glyphs, positions);
ShadowTexture* texture = mCache.get(entry);
if (!texture) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0,
- len, numGlyphs, radius, positions);
+ FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
+ radius, positions);
if (!shadow.image) {
return nullptr;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index caf089f..c4f3c5d 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -34,14 +34,14 @@
class FontRenderer;
struct ShadowText {
- ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
+ ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) {
}
// len is the number of bytes in text
- ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
+ ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText,
const float* positions):
- len(len), radius(radius), positions(positions) {
+ glyphCount(glyphCount), radius(radius), positions(positions) {
// TODO: Propagate this through the API, we should not cast here
text = (const char16_t*) srcText;
@@ -73,17 +73,16 @@
}
void copyTextLocally() {
- uint32_t charCount = len / sizeof(char16_t);
- str.setTo((const char16_t*) text, charCount);
+ str.setTo((const char16_t*) text, glyphCount);
text = str.string();
if (positions != nullptr) {
positionsCopy.clear();
- positionsCopy.appendArray(positions, charCount * 2);
+ positionsCopy.appendArray(positions, glyphCount * 2);
positions = positionsCopy.array();
}
}
- uint32_t len;
+ uint32_t glyphCount;
float radius;
float textSize;
SkTypeface* typeface;
@@ -136,7 +135,7 @@
*/
void operator()(ShadowText& text, ShadowTexture*& texture) override;
- ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
+ ShadowTexture* get(const SkPaint* paint, const char* text,
int numGlyphs, float radius, const float* positions);
/**
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index d680f99..dc82041 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -291,20 +291,18 @@
return cachedGlyph;
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char *text,
int numGlyphs, int x, int y, const float* positions) {
- render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr,
+ render(paint, text, numGlyphs, x, y, FRAMEBUFFER, nullptr,
0, 0, nullptr, positions);
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+void Font::render(const SkPaint* paint, const char *text, int numGlyphs,
+ const SkPath* path, float hOffset, float vOffset) {
+ if (numGlyphs == 0 || text == nullptr) {
return;
}
- text += start;
-
int glyphsCount = 0;
SkFixed prevRsbDelta = 0;
@@ -317,7 +315,7 @@
float pathLength = SkScalarToFloat(measure.getLength());
if (paint->getTextAlign() != SkPaint::kLeft_Align) {
- float textWidth = SkScalarToFloat(paint->measureText(text, len));
+ float textWidth = SkScalarToFloat(paint->measureText(text, numGlyphs * 2));
float pathOffset = pathLength;
if (paint->getTextAlign() == SkPaint::kCenter_Align) {
textWidth *= 0.5f;
@@ -347,14 +345,14 @@
}
}
-void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const char* text,
int numGlyphs, Rect *bounds, const float* positions) {
if (bounds == nullptr) {
ALOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
- render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
+ render(paint, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
}
void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
@@ -378,10 +376,10 @@
}
}
-void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char* text,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+ if (numGlyphs == 0 || text == nullptr) {
return;
}
@@ -395,7 +393,6 @@
};
RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
- text += start;
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 3119d73..59518a1 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -82,10 +82,10 @@
~Font();
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char* text,
int numGlyphs, int x, int y, const float* positions);
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char* text,
int numGlyphs, const SkPath* path, float hOffset, float vOffset);
const Font::FontDescription& getDescription() const {
@@ -113,11 +113,11 @@
void precache(const SkPaint* paint, const char* text, int numGlyphs);
- void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char *text,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
- void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void measure(const SkPaint* paint, const char* text,
int numGlyphs, Rect *bounds, const float* positions);
void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ec8048d..d76086c 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -136,14 +136,14 @@
}
TEST(OpReorderer, simpleBatching) {
- static int SIMPLE_BATCHING_LOOPS = 5;
+ const int LOOPS = 5;
class SimpleBatchingTestRenderer : public TestRendererBase {
public:
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
+ EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
}
};
@@ -153,7 +153,7 @@
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
+ for (int i = 0; i < LOOPS; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, SkPaint());
canvas.drawBitmap(bitmap, 5, 0, nullptr);
@@ -164,7 +164,35 @@
OpReorderer reorderer(200, 200, *dl, sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+}
+
+TEST(OpReorderer, textStrikethroughBatching) {
+ const int LOOPS = 5;
+ class TextStrikethroughTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
+ }
+ void onTextOp(const TextOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
+ }
+ };
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+ SkPaint textPaint;
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(20);
+ textPaint.setStrikeThruText(true);
+ for (int i = 0; i < LOOPS; i++) {
+ TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+ }
+ });
+ OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+ TextStrikethroughTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count"; // TODO: force no merging
}
TEST(OpReorderer, renderNode) {
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 22190f5..c23d47e 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -41,21 +41,110 @@
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
-TEST(RecordingCanvas, testSimpleRectRecord) {
+TEST(RecordingCanvas, drawLines) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setStrokeWidth(20);
+ float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
+ canvas.drawLines(&points[0], 7, paint);
+ });
+
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = dl->getOps()[0];
+ ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
+ EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
+ << "float count must be rounded down to closest multiple of 4";
+ EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds)
+ << "unmapped bounds must be size of line, outset by 1/2 stroke width";
+}
+
+TEST(RecordingCanvas, drawRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(10, 20, 90, 180, SkPaint());
});
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = *(dl->getOps()[0]);
+ ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+ EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawText) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
int count = 0;
playbackOps(*dl, [&count](const RecordedOp& op) {
count++;
- ASSERT_EQ(RecordedOpId::RectOp, op.opId);
- ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
- ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
+ << "Op expected to be 25+ pixels wide, 10+ pixels tall";
});
ASSERT_EQ(1, count);
}
+TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ paint.setUnderlineText(i != 0);
+ paint.setStrikeThruText(j != 0);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ }
+ }
+ });
+
+ auto ops = dl->getOps();
+ ASSERT_EQ(8u, ops.size());
+
+ int index = 0;
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
+}
+
+TEST(RecordingCanvas, drawText_forceAlignLeft) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextAlign(SkPaint::kLeft_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kCenter_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kRight_Align);
+ TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
+ << "recorded drawText commands must force kLeft_Align on their paint";
+ EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+ });
+ ASSERT_EQ(3, count);
+}
+
TEST(RecordingCanvas, backgroundAndImage) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap;
@@ -109,7 +198,7 @@
ASSERT_EQ(2, count);
}
-TEST(RecordingCanvas, saveLayerSimple) {
+TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
canvas.drawRect(10, 20, 190, 180, SkPaint());
@@ -143,7 +232,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerViewportCrop) {
+TEST(RecordingCanvas, saveLayer_viewportCrop) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// shouldn't matter, since saveLayer will clip to its bounds
canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
@@ -167,7 +256,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(100, 100);
@@ -193,7 +282,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayerRotateClipped) {
+TEST(RecordingCanvas, saveLayer_rotateClipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(100, 100);
@@ -224,7 +313,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, testReorderBarrier) {
+TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.insertReorderBarrier(true);
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
index 84230a7..dd6fc36 100644
--- a/libs/hwui/utils/TestUtils.cpp
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -36,5 +36,46 @@
| (int)((startB + (int)(fraction * (endB - startB))));
}
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& inPaint, float x, float y) {
+ // copy to force TextEncoding (which JNI layer would have done)
+ SkPaint paint(inPaint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkMatrix identity;
+ identity.setIdentity();
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+
+ float totalAdvance = 0;
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ Rect bounds;
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+ autoCache.getCache()->unicharToGlyph(unichar);
+
+ // push glyph and its relative position
+ glyphs.push_back(glyph);
+ positions.push_back(totalAdvance);
+ positions.push_back(0);
+
+ // compute bounds
+ SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+ Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+ glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+ bounds.unionWith(glyphBounds);
+
+ // advance next character
+ SkScalar skWidth;
+ paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+ totalAdvance += skWidth;
+ }
+ bounds.translate(x, y);
+ canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
+ bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h
index f7f4f2d..f9fa242 100644
--- a/libs/hwui/utils/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -196,6 +196,9 @@
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+ static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+ const SkPaint& inPaint, float x, float y);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8b1caf9..3971706 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -904,12 +904,12 @@
}
}
- // Enforce what the calling package can mutate the system settings.
- enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, runAsUserId);
-
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+ // Enforce what the calling package can mutate the system settings.
+ enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, callingUserId);
+
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index fa9c4bb..83edc96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -383,6 +383,12 @@
@Override
public void onUserSwitching(int newUserId, IRemoteCallback reply) {
mUserInfoController.reloadUserInfo();
+ if (reply != null) {
+ try {
+ reply.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9f1dc0a..3d358ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,21 +64,17 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Pools.Pool;
-import android.util.Pools.SimplePool;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
import android.view.InputDevice;
-import android.view.InputEventConsistencyVerifier;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -146,8 +142,6 @@
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
- private static final int MAX_POOL_SIZE = 10;
-
private static final int WINDOW_ID_UNKNOWN = -1;
private static int sIdCounter = 0;
@@ -158,9 +152,6 @@
private final Object mLock = new Object();
- private final Pool<PendingEvent> mPendingEventPool =
- new SimplePool<>(MAX_POOL_SIZE);
-
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@@ -193,6 +184,8 @@
private boolean mHasInputFilter;
+ private KeyEventDispatcher mKeyEventDispatcher;
+
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -756,12 +749,11 @@
boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
synchronized (mLock) {
- KeyEvent localClone = KeyEvent.obtain(event);
- boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
- if (!handled) {
- handled = notifyKeyEventLocked(localClone, policyFlags, true);
+ List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
+ if (boundServices.isEmpty()) {
+ return false;
}
- return handled;
+ return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices);
}
}
@@ -935,31 +927,6 @@
return false;
}
- private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
- // TODO: Now we are giving the key events to the last enabled
- // service that can handle them Ideally, the user should
- // make the call which service handles key events. However,
- // only one service should handle key events to avoid user
- // frustration when different behavior is observed from
- // different combinations of enabled accessibility services.
- UserState state = getCurrentUserStateLocked();
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- Service service = state.mBoundServices.get(i);
- // Key events are handled only by services that declared
- // this capability and requested to filter key events.
- if (!service.mRequestFilterKeyEvents ||
- (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
- .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
- continue;
- }
- if (service.mIsDefault == isDefault) {
- service.notifyKeyEvent(event, policyFlags);
- return true;
- }
- }
- return false;
- }
-
private void notifyClearAccessibilityCacheLocked() {
UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
@@ -1754,6 +1721,14 @@
return null;
}
+ private KeyEventDispatcher getKeyEventDispatcher() {
+ if (mKeyEventDispatcher == null) {
+ mKeyEventDispatcher = new KeyEventDispatcher(
+ mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock);
+ }
+ return mKeyEventDispatcher;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
@@ -1954,22 +1929,6 @@
}
}
- private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
- PendingEvent pendingEvent = mPendingEventPool.acquire();
- if (pendingEvent == null) {
- pendingEvent = new PendingEvent();
- }
- pendingEvent.event = event;
- pendingEvent.policyFlags = policyFlags;
- pendingEvent.sequence = sequence;
- return pendingEvent;
- }
-
- private void recyclePendingEventLocked(PendingEvent pendingEvent) {
- pendingEvent.clear();
- mPendingEventPool.release(pendingEvent);
- }
-
private int findWindowIdLocked(IBinder token) {
final int globalIndex = mGlobalWindowTokens.indexOfValue(token);
if (globalIndex >= 0) {
@@ -2082,8 +2041,6 @@
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<>();
- final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
-
boolean mWasConnectedAndDied;
// Handler only for dispatching accessibility events since we use event
@@ -2195,7 +2152,7 @@
return false;
}
UserState userState = getUserStateLocked(mUserId);
- mKeyEventDispatcher.flush();
+ getKeyEventDispatcher().flush(this);
if (!mIsAutomation) {
mContext.unbindService(this);
} else {
@@ -2212,7 +2169,7 @@
@Override
public void setOnKeyEventResult(boolean handled, int sequence) {
- mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+ getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
}
@Override
@@ -2926,7 +2883,7 @@
return;
}
mWasConnectedAndDied = true;
- mKeyEventDispatcher.flush();
+ getKeyEventDispatcher().flush(this);
UserState userState = getUserStateLocked(mUserId);
// The death recipient is unregistered in removeServiceLocked
removeServiceLocked(this, userState);
@@ -3035,11 +2992,6 @@
gestureId, 0).sendToTarget();
}
- public void notifyKeyEvent(KeyEvent event, int policyFlags) {
- mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
- policyFlags, 0, event).sendToTarget();
- }
-
public void notifyClearAccessibilityNodeInfoCache() {
mInvocationHandler.sendEmptyMessage(
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
@@ -3084,10 +3036,6 @@
}
}
- private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
- mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
- }
-
private void notifyClearAccessibilityCacheInternal() {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -3205,9 +3153,7 @@
private final class InvocationHandler extends Handler {
public static final int MSG_ON_GESTURE = 1;
- public static final int MSG_ON_KEY_EVENT = 2;
- public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
- public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+ public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
@@ -3226,21 +3172,10 @@
notifyGestureInternal(gestureId);
} break;
- case MSG_ON_KEY_EVENT: {
- KeyEvent event = (KeyEvent) message.obj;
- final int policyFlags = message.arg1;
- notifyKeyEventInternal(event, policyFlags);
- } break;
-
case MSG_CLEAR_ACCESSIBILITY_CACHE: {
notifyClearAccessibilityCacheInternal();
} break;
- case MSG_ON_KEY_EVENT_TIMEOUT: {
- PendingEvent eventState = (PendingEvent) message.obj;
- setOnKeyEventResult(false, eventState.sequence);
- } break;
-
case MSG_ON_MAGNIFICATION_CHANGED: {
final SomeArgs args = (SomeArgs) message.obj;
final Region region = (Region) args.arg1;
@@ -3278,140 +3213,6 @@
}
}
- private final class KeyEventDispatcher {
-
- private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
-
- private PendingEvent mPendingEvents;
-
- private final InputEventConsistencyVerifier mSentEventsVerifier =
- InputEventConsistencyVerifier.isInstrumentationEnabled()
- ? new InputEventConsistencyVerifier(
- this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
-
- public void notifyKeyEvent(KeyEvent event, int policyFlags) {
- final PendingEvent pendingEvent;
-
- synchronized (mLock) {
- pendingEvent = addPendingEventLocked(event, policyFlags);
- }
-
- Message message = mInvocationHandler.obtainMessage(
- InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
- mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
-
- try {
- // Accessibility services are exclusively not in the system
- // process, therefore no need to clone the motion event to
- // prevent tampering. It will be cloned in the IPC call.
- mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
- } catch (RemoteException re) {
- setOnKeyEventResult(false, pendingEvent.sequence);
- }
- }
-
- public void setOnKeyEventResult(boolean handled, int sequence) {
- synchronized (mLock) {
- PendingEvent pendingEvent = removePendingEventLocked(sequence);
- if (pendingEvent != null) {
- mInvocationHandler.removeMessages(
- InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
- pendingEvent);
- pendingEvent.handled = handled;
- finishPendingEventLocked(pendingEvent);
- }
- }
- }
-
- public void flush() {
- synchronized (mLock) {
- cancelAllPendingEventsLocked();
- if (mSentEventsVerifier != null) {
- mSentEventsVerifier.reset();
- }
- }
- }
-
- private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
- final int sequence = event.getSequenceNumber();
- PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
- pendingEvent.next = mPendingEvents;
- mPendingEvents = pendingEvent;
- return pendingEvent;
- }
-
- private PendingEvent removePendingEventLocked(int sequence) {
- PendingEvent previous = null;
- PendingEvent current = mPendingEvents;
-
- while (current != null) {
- if (current.sequence == sequence) {
- if (previous != null) {
- previous.next = current.next;
- } else {
- mPendingEvents = current.next;
- }
- current.next = null;
- return current;
- }
- previous = current;
- current = current.next;
- }
- return null;
- }
-
- private void finishPendingEventLocked(PendingEvent pendingEvent) {
- if (!pendingEvent.handled) {
- sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
- }
- // Nullify the event since we do not want it to be
- // recycled yet. It will be sent to the input filter.
- pendingEvent.event = null;
- recyclePendingEventLocked(pendingEvent);
- }
-
- private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Injecting event: " + event);
- }
- if (mSentEventsVerifier != null) {
- mSentEventsVerifier.onKeyEvent(event, 0);
- }
- policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
- policyFlags, 0, event).sendToTarget();
- }
-
- private void cancelAllPendingEventsLocked() {
- while (mPendingEvents != null) {
- PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
- pendingEvent.handled = false;
- mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
- pendingEvent);
- finishPendingEventLocked(pendingEvent);
- }
- }
- }
- }
-
- private static final class PendingEvent {
- PendingEvent next;
-
- KeyEvent event;
- int policyFlags;
- int sequence;
- boolean handled;
-
- public void clear() {
- if (event != null) {
- event.recycle();
- event = null;
- }
- next = null;
- policyFlags = 0;
- sequence = 0;
- handled = false;
- }
}
final class WindowsForAccessibilityCallback implements
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
new file mode 100644
index 0000000..3469565
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
@@ -0,0 +1,285 @@
+/*
+ ** Copyright 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.server.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Pools;
+import android.util.Pools.Pool;
+import android.util.Slog;
+import android.view.InputEventConsistencyVerifier;
+import android.view.KeyEvent;
+import android.view.WindowManagerPolicy;
+
+import com.android.server.accessibility.AccessibilityManagerService.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dispatcher to send KeyEvents to all accessibility services that are able to process them.
+ * Events that are handled by one or more services are consumed. Events that are not processed
+ * by any service (or time out before a service reports them as handled) are passed along to
+ * the rest of the system.
+ *
+ * The class assumes that services report their return values in order, which is valid because
+ * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so
+ * don't see the N+1th event until they have processed the Nth event.
+ */
+public class KeyEventDispatcher {
+ // Debugging
+ private static final String LOG_TAG = "KeyEventDispatcher";
+ private static final boolean DEBUG = false;
+ /* KeyEvents must be processed in this time interval */
+ private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+ private static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
+ private static final int MAX_POOL_SIZE = 10;
+
+ private final Pool<PendingKeyEvent> mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE);
+ private final Object mLock;
+
+ /*
+ * Track events sent to each service. If a KeyEvent is to be sent to at least one service,
+ * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in
+ * the list for each service its KeyEvent is sent to. It is removed from the list when
+ * the service calls setOnKeyEventResult, or when we time out waiting for the service to
+ * respond.
+ */
+ private final Map<Service, ArrayList<PendingKeyEvent>> mPendingEventsMap = new ArrayMap<>();
+
+ private final InputEventConsistencyVerifier mSentEventsVerifier;
+ private final Handler mHandlerToSendKeyEventsToInputFilter;
+ private final int mMessageTypeForSendKeyEvent;
+ private final Handler mKeyEventTimeoutHandler;
+
+ /**
+ * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s
+ * that have not been handled by any accessibility service.
+ * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the
+ * message that carries a {@code KeyEvent} to be sent to the input filter
+ * @param lock The lock used for all synchronization in this package. This lock must be held
+ * when calling {@code notifyKeyEventLocked}
+ */
+ public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
+ int messageTypeForSendKeyEvent, Object lock) {
+ if (InputEventConsistencyVerifier.isInstrumentationEnabled()) {
+ mSentEventsVerifier = new InputEventConsistencyVerifier(
+ this, 0, KeyEventDispatcher.class.getSimpleName());
+ } else {
+ mSentEventsVerifier = null;
+ }
+ mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter;
+ mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent;
+ mKeyEventTimeoutHandler =
+ new Handler(mHandlerToSendKeyEventsToInputFilter.getLooper(), new Callback());
+ mLock = lock;
+ }
+
+ /**
+ * Notify that a new KeyEvent is available to accessibility services. Must be called with the
+ * lock used to construct this object held. The boundServices list must also be protected
+ * by a lock.
+ *
+ * @param event The new key event
+ * @param policyFlags Flags for the event
+ * @param boundServices A list of currently bound AccessibilityServices
+ *
+ * @return {@code true} if the event was passed to at least one AccessibilityService,
+ * {@code false} otherwise.
+ */
+ // TODO: The locking policy for boundServices needs some thought.
+ public boolean notifyKeyEventLocked(
+ KeyEvent event, int policyFlags, List<Service> boundServices) {
+ PendingKeyEvent pendingKeyEvent = null;
+ KeyEvent localClone = KeyEvent.obtain(event);
+ for (int i = 0; i < boundServices.size(); i++) {
+ Service service = boundServices.get(i);
+ // Key events are handled only by services that declared
+ // this capability and requested to filter key events.
+ if (!service.mRequestFilterKeyEvents) {
+ continue;
+ }
+ int filterKeyEventBit = service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+ if (filterKeyEventBit == 0) {
+ continue;
+ }
+
+ try {
+ // The event will be cloned in the IPC call, so it doesn't need to be here.
+ service.mServiceInterface.onKeyEvent(localClone, localClone.getSequenceNumber());
+ } catch (RemoteException re) {
+ continue;
+ }
+
+ if (pendingKeyEvent == null) {
+ pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
+ }
+ ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(service);
+ if (pendingEventList == null) {
+ pendingEventList = new ArrayList<>();
+ mPendingEventsMap.put(service, pendingEventList);
+ }
+ pendingEventList.add(pendingKeyEvent);
+ pendingKeyEvent.referenceCount++;
+ }
+
+ if (pendingKeyEvent == null) {
+ localClone.recycle();
+ return false;
+ }
+
+ Message message = mKeyEventTimeoutHandler.obtainMessage(
+ MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
+ mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+ return true;
+ }
+
+ /**
+ * Set the result from onKeyEvent from one service.
+ *
+ * @param service The service setting the result
+ * @param handled {@code true} if the service handled the {@code KeyEvent}
+ * @param sequence The sequence number of the {@code KeyEvent}
+ */
+ public void setOnKeyEventResult(Service service, boolean handled, int sequence) {
+ synchronized (mLock) {
+ PendingKeyEvent pendingEvent =
+ removeEventFromListLocked(mPendingEventsMap.get(service), sequence);
+ if (pendingEvent != null) {
+ pendingEvent.handled |= handled;
+ removeReferenceToPendingEventLocked(pendingEvent);
+ }
+ }
+ }
+
+ /**
+ * Flush all pending key events for a service, treating all of them as unhandled
+ *
+ * @param service The service for which to flush events
+ */
+ public void flush(Service service) {
+ synchronized (mLock) {
+ List<PendingKeyEvent> pendingEvents = mPendingEventsMap.get(service);
+ if (pendingEvents != null) {
+ for (int i = 0; i < pendingEvents.size(); i++) {
+ PendingKeyEvent pendingEvent = pendingEvents.get(i);
+ removeReferenceToPendingEventLocked(pendingEvent);
+ }
+ mPendingEventsMap.remove(service);
+ }
+ }
+ }
+
+ private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) {
+ PendingKeyEvent pendingEvent = mPendingEventPool.acquire();
+ if (pendingEvent == null) {
+ pendingEvent = new PendingKeyEvent();
+ }
+ pendingEvent.event = event;
+ pendingEvent.policyFlags = policyFlags;
+ pendingEvent.referenceCount = 0;
+ pendingEvent.handled = false;
+ return pendingEvent;
+ }
+
+ private static PendingKeyEvent removeEventFromListLocked(
+ List<PendingKeyEvent> listOfEvents, int sequence) {
+ /* In normal operation, the event should be first */
+ for (int i = 0; i < listOfEvents.size(); i++) {
+ PendingKeyEvent pendingKeyEvent = listOfEvents.get(i);
+ if (pendingKeyEvent.event.getSequenceNumber() == sequence) {
+ /*
+ * Removing the first element of the ArrayList can be slow if there are a lot
+ * of events backed up, but for a handful of events it's better than incurring
+ * the fixed overhead of LinkedList. An ArrayList optimized for removing the
+ * first element (by treating the underlying array as a circular buffer) would
+ * be ideal.
+ */
+ listOfEvents.remove(pendingKeyEvent);
+ return pendingKeyEvent;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param pendingEvent The event whose reference count should be decreased
+ * @return {@code true} if the event was release, {@code false} if not.
+ */
+ private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
+ if (--pendingEvent.referenceCount > 0) {
+ return false;
+ }
+ mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+ if (!pendingEvent.handled) {
+ /* Pass event to input filter */
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event);
+ }
+ if (mSentEventsVerifier != null) {
+ mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
+ }
+ int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER;
+ mHandlerToSendKeyEventsToInputFilter
+ .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event)
+ .sendToTarget();
+ } else {
+ pendingEvent.event.recycle();
+ }
+ mPendingEventPool.release(pendingEvent);
+ return true;
+ }
+
+ private static final class PendingKeyEvent {
+ /* Event and policyFlag provided in notifyKeyEventLocked */
+ KeyEvent event;
+ int policyFlags;
+ /*
+ * The referenceCount optimizes the process of determining the number of services
+ * still holding a KeyEvent. It must be equal to the number of times the PendingEvent
+ * appears in mPendingEventsMap, or PendingEvents will leak.
+ */
+ int referenceCount;
+ /* Whether or not at least one service had handled this event */
+ boolean handled;
+ }
+
+ private class Callback implements Handler.Callback {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
+ throw new IllegalArgumentException("Unknown message: " + message.what);
+ }
+ PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
+ synchronized (mLock) {
+ for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
+ if (listForService.remove(pendingKeyEvent)) {
+ if(removeReferenceToPendingEventLocked(pendingKeyEvent)) {
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 504a7ef..c5ce2fd 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2769,7 +2769,9 @@
}
@Override public void onUidIdle(int uid) throws RemoteException {
- removeForStoppedLocked(uid);
+ synchronized (mLock) {
+ removeForStoppedLocked(uid);
+ }
}
};
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 3fc6846..02cf87a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1277,7 +1277,7 @@
}
final Configuration serviceConfig = mService.mConfiguration;
- mOverrideConfig = new Configuration(serviceConfig);
+ mOverrideConfig = new Configuration(Configuration.EMPTY);
// TODO(multidisplay): Update Dp to that of display stack is on.
final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
mOverrideConfig.screenWidthDp =
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6d8bd1c..4b2a55f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -674,7 +674,7 @@
void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
synchronized (mService) {
- Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+ Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
}
}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index ec7c1c4..103ed0a 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1139,6 +1139,12 @@
public void onUserSwitching(int newUserId, IRemoteCallback reply) {
mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
.sendToTarget();
+ if (reply != null) {
+ try {
+ reply.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
}
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4424838..946fbb1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1211,29 +1211,36 @@
}
@Override
- public void setPackagePriority(String pkg, int uid, int priority) {
+ public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
checkCallerIsSystem();
- mRankingHelper.setPackagePriority(pkg, uid, priority);
+ return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
+ }
+
+ @Override
+ public void setTopicPriority(String pkg, int uid, Notification.Topic topic, int priority) {
+ checkCallerIsSystem();
+ mRankingHelper.setTopicPriority(pkg, uid, topic, priority);
savePolicyFile();
}
@Override
- public int getPackagePriority(String pkg, int uid) {
+ public int getTopicPriority(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
- return mRankingHelper.getPackagePriority(pkg, uid);
+ return mRankingHelper.getTopicPriority(pkg, uid, topic);
}
@Override
- public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
+ public void setTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic,
+ int visibility) {
checkCallerIsSystem();
- mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
+ mRankingHelper.setTopicVisibilityOverride(pkg, uid, topic, visibility);
savePolicyFile();
}
@Override
- public int getPackageVisibilityOverride(String pkg, int uid) {
+ public int getTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
- return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
+ return mRankingHelper.getTopicVisibilityOverride(pkg, uid, topic);
}
/**
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..7ee29e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -15,12 +15,20 @@
*/
package com.android.server.notification;
+import android.app.Notification;
+
+import java.util.List;
+
public interface RankingConfig {
- int getPackagePriority(String packageName, int uid);
- void setPackagePriority(String packageName, int uid, int priority);
+ List<Notification.Topic> getTopics(String packageName, int uid);
- int getPackageVisibilityOverride(String packageName, int uid);
+ int getTopicPriority(String packageName, int uid, Notification.Topic topic);
- void setPackageVisibilityOverride(String packageName, int uid, int visibility);
+ void setTopicPriority(String packageName, int uid, Notification.Topic topic, int priority);
+
+ int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic);
+
+ void setTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic,
+ int visibility);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index f8b661f..4d33248 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,8 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -35,6 +37,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
@@ -45,11 +49,14 @@
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String ATT_VERSION = "version";
+ private static final String TAG_TOPIC = "topic";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_TOPIC_ID = "id";
+ private static final String ATT_TOPIC_LABEL = "label";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY =
@@ -161,12 +168,14 @@
} else {
r = getOrCreateRecord(name, uid);
}
- if (priority != DEFAULT_PRIORITY) {
- r.priority = priority;
- }
- if (vis != DEFAULT_VISIBILITY) {
- r.visibility = vis;
- }
+
+ // Migrate package level settings to the default topic.
+ // Might be overwritten by parseTopics.
+ Topic defaultTopic = r.topics.get(Notification.TOPIC_DEFAULT);
+ defaultTopic.priority = priority;
+ defaultTopic.visibility = vis;
+
+ parseTopics(r, parser);
}
}
}
@@ -174,6 +183,38 @@
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ public void parseTopics(Record r, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (TAG_TOPIC.equals(tagName)) {
+ int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+ String id = parser.getAttributeValue(null, ATT_TOPIC_ID);
+ CharSequence label = parser.getAttributeValue(null, ATT_TOPIC_LABEL);
+
+ if (!TextUtils.isEmpty(id)) {
+ Topic topic = new Topic(new Notification.Topic(id, label));
+
+ if (priority != DEFAULT_PRIORITY) {
+ topic.priority = priority;
+ }
+ if (vis != DEFAULT_VISIBILITY) {
+ topic.visibility = vis;
+ }
+ r.topics.put(id, topic);
+ }
+ }
+ }
+ }
+
private static String recordKey(String pkg, int uid) {
return pkg + "|" + uid;
}
@@ -185,21 +226,14 @@
r = new Record();
r.pkg = pkg;
r.uid = uid;
+ r.topics.put(Notification.TOPIC_DEFAULT,
+ new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
+ mContext.getString(R.string.default_notification_topic_label))));
mRecords.put(key, r);
}
return r;
}
- private void removeDefaultRecords() {
- final int N = mRecords.size();
- for (int i = N - 1; i >= 0; i--) {
- final Record r = mRecords.valueAt(i);
- if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) {
- mRecords.remove(i);
- }
- }
- }
-
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -213,20 +247,32 @@
}
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
- if (r.priority != DEFAULT_PRIORITY) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
- }
+
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
+
+ writeTopicsXml(out, r);
out.endTag(null, TAG_PACKAGE);
}
out.endTag(null, TAG_RANKING);
}
+ public void writeTopicsXml(XmlSerializer out, Record r) throws IOException {
+ for (Topic t : r.topics.values()) {
+ out.startTag(null, TAG_TOPIC);
+ out.attribute(null, ATT_TOPIC_ID, t.topic.getId());
+ out.attribute(null, ATT_TOPIC_LABEL, t.topic.getLabel().toString());
+ if (t.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(t.priority));
+ }
+ if (t.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(t.visibility));
+ }
+ out.endTag(null, TAG_TOPIC);
+ }
+ }
+
private void updateConfig() {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
@@ -322,37 +368,54 @@
}
@Override
- public int getPackagePriority(String packageName, int uid) {
- final Record r = mRecords.get(recordKey(packageName, uid));
- return r != null ? r.priority : DEFAULT_PRIORITY;
+ public List<Notification.Topic> getTopics(String packageName, int uid) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ List<Notification.Topic> topics = new ArrayList<>();
+ for (Topic t : r.topics.values()) {
+ topics.add(t.topic);
+ }
+ return topics;
}
@Override
- public void setPackagePriority(String packageName, int uid, int priority) {
- if (priority == getPackagePriority(packageName, uid)) {
- return;
- }
- getOrCreateRecord(packageName, uid).priority = priority;
- removeDefaultRecords();
+ public int getTopicPriority(String packageName, int uid, Notification.Topic topic) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ return getOrCreateTopic(r, topic).priority;
+ }
+
+ @Override
+ public void setTopicPriority(String packageName, int uid, Notification.Topic topic,
+ int priority) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ getOrCreateTopic(r, topic).priority = priority;
updateConfig();
}
@Override
- public int getPackageVisibilityOverride(String packageName, int uid) {
- final Record r = mRecords.get(recordKey(packageName, uid));
- return r != null ? r.visibility : DEFAULT_VISIBILITY;
+ public int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ return getOrCreateTopic(r, topic).visibility;
}
@Override
- public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
- if (visibility == getPackageVisibilityOverride(packageName, uid)) {
- return;
- }
- getOrCreateRecord(packageName, uid).visibility = visibility;
- removeDefaultRecords();
+ public void setTopicVisibilityOverride(String pkgName, int uid, Notification.Topic topic,
+ int visibility) {
+ final Record r = getOrCreateRecord(pkgName, uid);
+ getOrCreateTopic(r, topic).visibility = visibility;
updateConfig();
}
+ private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+ Topic t = r.topics.get(topic.getId());
+ if (t != null) {
+ return t;
+ } else {
+ t = new Topic(topic);
+ r.topics.put(topic.getId(), t);
+ return t;
+ }
+ }
+
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;
@@ -385,15 +448,22 @@
pw.print(" (");
pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
pw.print(')');
- if (r.priority != DEFAULT_PRIORITY) {
- pw.print(" priority=");
- pw.print(Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- pw.print(" visibility=");
- pw.print(Notification.visibilityToString(r.visibility));
- }
pw.println();
+ for (Topic t : r.topics.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.print(t.topic.getId());
+ if (t.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(t.priority));
+ }
+ if (t.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(t.visibility));
+ }
+ pw.println();
+ }
}
}
}
@@ -429,8 +499,16 @@
String pkg;
int uid = UNKNOWN_UID;
+ Map<String, Topic> topics = new ArrayMap<>();
+ }
+
+ private static class Topic {
+ Notification.Topic topic;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
- }
+ public Topic(Notification.Topic topic) {
+ this.topic = topic;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
similarity index 79%
rename from services/core/java/com/android/server/notification/PackagePriorityExtractor.java
rename to services/core/java/com/android/server/notification/TopicPriorityExtractor.java
index 6beed9c..5bf989ae 100644
--- a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
@@ -18,8 +18,11 @@
import android.content.Context;
import android.util.Slog;
-public class PackagePriorityExtractor implements NotificationSignalExtractor {
- private static final String TAG = "ImportantPackageExtractor";
+/**
+ * Determines if the given notification can bypass Do Not Disturb.
+ */
+public class TopicPriorityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "ImportantTopicExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,8 @@
return null;
}
- final int packagePriority = mConfig.getPackagePriority(
- record.sbn.getPackageName(), record.sbn.getUid());
+ final int packagePriority = mConfig.getTopicPriority(record.sbn.getPackageName(),
+ record.sbn.getUid(), record.sbn.getNotification().getTopic());
record.setPackagePriority(packagePriority);
return null;
diff --git a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
similarity index 80%
rename from services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
rename to services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
index af99db7..e053382 100644
--- a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
@@ -18,8 +18,11 @@
import android.content.Context;
import android.util.Slog;
-public class PackageVisibilityExtractor implements NotificationSignalExtractor {
- private static final String TAG = "PackageVisibilityExtractor";
+/**
+ * Determines if the given notification can display sensitive content on the lockscreen.
+ */
+public class TopicVisibilityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "TopicVisibilityExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,9 @@
return null;
}
- final int packageVisibility = mConfig.getPackageVisibilityOverride(
- record.sbn.getPackageName(), record.sbn.getUid());
+ final int packageVisibility = mConfig.getTopicVisibilityOverride(
+ record.sbn.getPackageName(), record.sbn.getUid(),
+ record.sbn.getNotification().getTopic());
record.setPackageVisibilityOverride(packageVisibility);
return null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4f3544b..21a4206 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -617,6 +617,9 @@
final DefaultPermissionGrantPolicy mDefaultPermissionPolicy =
new DefaultPermissionGrantPolicy(this);
+ // List of packages names to keep cached, even if they are uninstalled for all users
+ private List<String> mKeepUninstalledPackages;
+
private static class IFVerificationParams {
PackageParser.Package pkg;
boolean replacing;
@@ -13015,7 +13018,7 @@
final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
final int[] users = deleteAllUsers ? sUserManager.getUserIds() : new int[]{ userId };
if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"deletePackage for user " + userId);
}
@@ -13090,6 +13093,10 @@
return false;
}
+ private boolean shouldKeepUninstalledPackageLPr(String packageName) {
+ return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+ }
+
/**
* This method is an internal method that could be get invoked either
* to delete an installed package or to clean up a failed installation.
@@ -13512,7 +13519,9 @@
false, // blockUninstall
ps.readUserState(userId).domainVerificationStatus, 0);
if (!isSystemApp(ps)) {
- if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+ // Do not uninstall the APK if an app should be cached
+ boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
+ if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
// Other user still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
@@ -16686,15 +16695,21 @@
if (DEBUG_CLEAN_APKS) {
Slog.i(TAG, "Checking package " + packageName);
}
- boolean keep = false;
- for (int i = 0; i < users.length; i++) {
- if (users[i] != userHandle && ps.getInstalled(users[i])) {
- keep = true;
- if (DEBUG_CLEAN_APKS) {
- Slog.i(TAG, " Keeping package " + packageName + " for user "
- + users[i]);
+ boolean keep = shouldKeepUninstalledPackageLPr(packageName);
+ if (keep) {
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Keeping package " + packageName + " - requested by DO");
+ }
+ } else {
+ for (int i = 0; i < users.length; i++) {
+ if (users[i] != userHandle && ps.getInstalled(users[i])) {
+ keep = true;
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Keeping package " + packageName + " for user "
+ + users[i]);
+ }
+ break;
}
- break;
}
}
if (!keep) {
@@ -16896,6 +16911,23 @@
}
}
+ private void deletePackageIfUnusedLPr(final String packageName) {
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ return;
+ }
+ if (!ps.isAnyInstalled(sUserManager.getUserIds())) {
+ // TODO Implement atomic delete if package is unused
+ // It is currently possible that the package will be deleted even if it is installed
+ // after this method returns.
+ mHandler.post(new Runnable() {
+ public void run() {
+ deletePackageX(packageName, 0, PackageManager.DELETE_ALL_USERS);
+ }
+ });
+ }
+ }
+
/**
* Check and throw if the given before/after packages would be considered a
* downgrade.
@@ -17133,6 +17165,34 @@
packageName, userId);
}
}
+
+ @Override
+ public void setKeepUninstalledPackages(final List<String> packageList) {
+ Preconditions.checkNotNull(packageList);
+ List<String> removedFromList = null;
+ synchronized (mPackages) {
+ if (mKeepUninstalledPackages != null) {
+ final int packagesCount = mKeepUninstalledPackages.size();
+ for (int i = 0; i < packagesCount; i++) {
+ String oldPackage = mKeepUninstalledPackages.get(i);
+ if (packageList != null && packageList.contains(oldPackage)) {
+ continue;
+ }
+ if (removedFromList == null) {
+ removedFromList = new ArrayList<>();
+ }
+ removedFromList.add(oldPackage);
+ }
+ }
+ mKeepUninstalledPackages = new ArrayList<>(packageList);
+ if (removedFromList != null) {
+ final int removedCount = removedFromList.size();
+ for (int i = 0; i < removedCount; i++) {
+ deletePackageIfUnusedLPr(removedFromList.get(i));
+ }
+ }
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 4926352..74750a9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,6 +16,14 @@
package com.android.server.wm;
+import android.graphics.Matrix;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
@@ -45,6 +53,8 @@
* Drag/drop state
*/
class DragState {
+ private static final long ANIMATION_DURATION_MS = 500;
+
final WindowManagerService mService;
IBinder mToken;
SurfaceControl mSurfaceControl;
@@ -55,6 +65,8 @@
ClipData mData;
ClipDescription mDataDescription;
boolean mDragResult;
+ float mOriginalAlpha;
+ float mOriginalX, mOriginalY;
float mCurrentX, mCurrentY;
float mThumbOffsetX, mThumbOffsetY;
InputChannel mServerChannel, mClientChannel;
@@ -69,6 +81,10 @@
private final Region mTmpRegion = new Region();
private final Rect mTmpRect = new Rect();
+ private Animation mAnimation;
+ final Transformation mTransformation = new Transformation();
+ private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+
DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
mService = service;
@@ -184,6 +200,9 @@
/* call out to each visible window/session informing it about the drag
*/
void broadcastDragStartedLw(final float touchX, final float touchY) {
+ mOriginalX = mCurrentX = touchX;
+ mOriginalY = mCurrentY = touchY;
+
// Cache a base-class instance of the clip metadata so that parceling
// works correctly in calling out to the apps.
mDataDescription = (mData != null) ? mData.getDescription() : null;
@@ -294,19 +313,32 @@
}
void endDragLw() {
- mService.mDragState.broadcastDragEndedLw();
+ if (!mDragResult) {
+ mAnimation = createReturnAnimationLocked();
+ mService.scheduleAnimationLocked();
+ return; // Will call cleanUpDragLw when the animation is done.
+ }
+ cleanUpDragLw();
+ }
+
+
+ void cleanUpDragLw() {
+ broadcastDragEndedLw();
// stop intercepting input
- mService.mDragState.unregister();
+ unregister();
// free our resources and drop all the object references
- mService.mDragState.reset();
+ reset();
mService.mDragState = null;
mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
}
void notifyMoveLw(float x, float y) {
+ mCurrentX = x;
+ mCurrentY = y;
+
final int myPid = Process.myPid();
// Move the surface to the given touch
@@ -378,6 +410,9 @@
// result from the recipient.
boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder,
float x, float y) {
+ mCurrentX = x;
+ mCurrentY = y;
+
if (touchedWin == null) {
// "drop" outside a valid window -- no recipient to apply a
// timeout to, and we can send the drag-ended message immediately.
@@ -469,4 +504,38 @@
return DragEvent.obtain(action, winX, winY, localState, description, data,
dropPermissionHolder, result);
}
+
+ boolean stepAnimationLocked(long currentTimeMs) {
+ if (mAnimation == null) {
+ return false;
+ }
+
+ mTransformation.clear();
+ if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
+ cleanUpDragLw();
+ return false;
+ }
+
+ final float tmpFloats[] = mService.mTmpFloats;
+ mTransformation.getMatrix().getValues(tmpFloats);
+ mSurfaceControl.setPosition(
+ tmpFloats[Matrix.MTRANS_X] - mThumbOffsetX,
+ tmpFloats[Matrix.MTRANS_Y] - mThumbOffsetY);
+ mSurfaceControl.setAlpha(mTransformation.getAlpha());
+ mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+ tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+ return true;
+ }
+
+ private Animation createReturnAnimationLocked() {
+ final AnimationSet set = new AnimationSet(false);
+ set.addAnimation(new TranslateAnimation(
+ mCurrentX, mOriginalX, mCurrentY, mOriginalY));
+ set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
+ set.setDuration(ANIMATION_DURATION_MS);
+ set.setInterpolator(mCubicEaseOutInterpolator);
+ set.initialize(0, 0, 0, 0);
+ set.start(); // Will start on the first call to getTransformation.
+ return set;
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1caeca0..f23fcdb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -325,8 +325,6 @@
}
mService.mDragState.mData = data;
- mService.mDragState.mCurrentX = touchX;
- mService.mDragState.mCurrentY = touchY;
mService.mDragState.broadcastDragStartedLw(touchX, touchY);
// remember the thumb offsets for later
@@ -401,6 +399,34 @@
}
}
+ public void cancelDragAndDrop(IBinder dragToken) {
+ if (WindowManagerService.DEBUG_DRAG) {
+ Slog.d(WindowManagerService.TAG, "cancelDragAndDrop");
+ }
+
+ synchronized (mService.mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mService.mDragState == null) {
+ Slog.w(WindowManagerService.TAG, "cancelDragAndDrop() without prepareDrag()");
+ throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+ }
+
+ if (mService.mDragState.mToken != dragToken) {
+ Slog.w(WindowManagerService.TAG,
+ "cancelDragAndDrop() does not match prepareDrag()");
+ throw new IllegalStateException(
+ "cancelDragAndDrop() does not match prepareDrag()");
+ }
+
+ mService.mDragState.mDragResult = false;
+ mService.mDragState.endDragLw();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
public void dragRecipientEntered(IWindow window) {
if (WindowManagerService.DEBUG_DRAG) {
Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8085f13..04ab544 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -515,10 +515,11 @@
for (int i = 0; i < count; i++) {
final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
final int otherStackId = otherStack.mStackId;
- if (StackId.isResizeableByDockedStack(otherStackId)) {
+ if (StackId.isResizeableByDockedStack(otherStackId)
+ && !otherStack.mBounds.equals(bounds)) {
mService.mH.sendMessage(
mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
- 1 /*allowResizeInDockedMode*/, bounds));
+ 1 /*allowResizeInDockedMode*/, fullscreen ? null : bounds));
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a96bd2c..46fab2a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -708,6 +708,10 @@
}
}
+ if (mService.mDragState != null) {
+ mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
+ }
+
if (mAnimating) {
mService.scheduleAnimationLocked();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b6600266..e741c45 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -309,6 +309,8 @@
// trying to apply a new one.
private static final boolean ALWAYS_KEEP_CURRENT = true;
+ private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -753,9 +755,6 @@
private boolean completeDropLw(float x, float y) {
WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y);
- mDragState.mCurrentX = x;
- mDragState.mCurrentY = y;
-
DropPermissionHolder dropPermissionHolder = null;
if (dropTargetWin != null &&
(mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
@@ -7175,9 +7174,11 @@
SurfaceControl surface = new SurfaceControl(session, "drag surface",
width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
surface.setLayerStack(display.getLayerStack());
+ float alpha = 1;
if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
- surface.setAlpha(.7071f);
+ alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
}
+ surface.setAlpha(alpha);
if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG "
+ surface + ": CREATE");
@@ -7187,6 +7188,7 @@
mDragState = new DragState(this, token, surface, flags, winBinder);
mDragState.mPid = callerPid;
mDragState.mUid = callerUid;
+ mDragState.mOriginalAlpha = alpha;
token = mDragState.mToken = new Binder();
// 5 second timeout for this window to actually begin the drag
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 642def3..4c15809 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -60,6 +60,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -416,7 +417,7 @@
"cross-profile-widget-providers";
private static final String TAG_PROVIDER = "provider";
private static final String TAG_PACKAGE_LIST_ITEM = "item";
-
+ private static final String TAG_KEEP_UNINSTALLED_PACKAGES = "keep-uninstalled-packages";
private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
final DeviceAdminInfo info;
@@ -489,6 +490,9 @@
// allowed.
List<String> permittedInputMethods;
+ // List of package names to keep cached.
+ List<String> keepUninstalledPackages;
+
// TODO: review implementation decisions with frameworks team
boolean specifiesGlobalProxy = false;
String globalProxySpec = null;
@@ -674,6 +678,7 @@
writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
permittedAccessiblityServices);
writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+ writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
if (hasUserRestrictions()) {
UserRestrictionsUtils.writeRestrictions(
out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -787,6 +792,8 @@
permittedAccessiblityServices = readPackageList(parser, tag);
} else if (TAG_PERMITTED_IMES.equals(tag)) {
permittedInputMethods = readPackageList(parser, tag);
+ } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
+ keepUninstalledPackages = readPackageList(parser, tag);
} else if (TAG_USER_RESTRICTIONS.equals(tag)) {
UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions());
} else {
@@ -981,13 +988,17 @@
pw.println(disabledKeyguardFeatures);
pw.print(prefix); pw.print("crossProfileWidgetProviders=");
pw.println(crossProfileWidgetProviders);
- if (!(permittedAccessiblityServices == null)) {
+ if (permittedAccessiblityServices != null) {
pw.print(prefix); pw.print("permittedAccessibilityServices=");
- pw.println(permittedAccessiblityServices.toString());
+ pw.println(permittedAccessiblityServices);
}
- if (!(permittedInputMethods == null)) {
+ if (permittedInputMethods != null) {
pw.print(prefix); pw.print("permittedInputMethods=");
- pw.println(permittedInputMethods.toString());
+ pw.println(permittedInputMethods);
+ }
+ if (keepUninstalledPackages != null) {
+ pw.print(prefix); pw.print("keepUninstalledPackages=");
+ pw.println(keepUninstalledPackages);
}
pw.print(prefix); pw.println("userRestrictions:");
UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", userRestrictions);
@@ -1068,6 +1079,10 @@
return LocalServices.getService(UserManagerInternal.class);
}
+ PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
NotificationManager getNotificationManager() {
return mContext.getSystemService(NotificationManager.class);
}
@@ -2101,6 +2116,14 @@
new SetupContentObserver(mHandler).register(mContext.getContentResolver());
// Initialize the user setup state, to handle the upgrade case.
updateUserSetupComplete();
+
+ List<String> packageList;
+ synchronized (this) {
+ packageList = getKeepUninstalledPackagesLocked();
+ }
+ if (packageList != null) {
+ mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
+ }
}
private void ensureDeviceOwnerUserStarted() {
@@ -4490,6 +4513,42 @@
}
@Override
+ public void setKeepUninstalledPackages(ComponentName who, List<String> packageList) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(packageList, "packageList is null");
+ final int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ admin.keepUninstalledPackages = packageList;
+ saveSettingsLocked(userHandle);
+ mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
+ }
+ }
+
+ @Override
+ public List<String> getKeepUninstalledPackages(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ if (!mHasFeature) {
+ return null;
+ }
+ // TODO In split system user mode, allow apps on user 0 to query the list
+ synchronized (this) {
+ // Check if this is the device owner who is calling
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ return getKeepUninstalledPackagesLocked();
+ }
+ }
+
+ private List<String> getKeepUninstalledPackagesLocked() {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ return (deviceOwner != null) ? deviceOwner.keepUninstalledPackages : null;
+ }
+
+ @Override
public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
if (!mHasFeature) {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 2c01b8a..56d6fc0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -20,8 +20,8 @@
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.backup.IBackupManager;
-import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.media.IAudioService;
import android.os.Looper;
import android.os.PowerManagerInternal;
@@ -113,6 +113,11 @@
}
@Override
+ PackageManagerInternal getPackageManagerInternal() {
+ return context.packageManagerInternal;
+ }
+
+ @Override
PowerManagerInternal getPowerManagerInternal() {
return context.powerManagerInternal;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index f4fdc95..bb1e06d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.media.IAudioService;
import android.os.Bundle;
@@ -205,6 +206,7 @@
public final SystemPropertiesForMock systemProperties;
public final UserManager userManager;
public final UserManagerInternal userManagerInternal;
+ public final PackageManagerInternal packageManagerInternal;
public final UserManagerForMock userManagerForMock;
public final PowerManagerForMock powerManager;
public final PowerManagerInternal powerManagerInternal;
@@ -237,6 +239,7 @@
userManager = mock(UserManager.class);
userManagerInternal = mock(UserManagerInternal.class);
userManagerForMock = mock(UserManagerForMock.class);
+ packageManagerInternal = mock(PackageManagerInternal.class);
powerManager = mock(PowerManagerForMock.class);
powerManagerInternal = mock(PowerManagerInternal.class);
notificationManager = mock(NotificationManager.class);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
deleted file mode 100644
index 56fd351..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ /dev/null
@@ -1,195 +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.
- */
-
-package com.android.server.pm;
-
-import com.android.server.devicepolicy.DpmTestUtils;
-
-import android.os.Bundle;
-import android.os.UserManager;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-/**
- * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
- *
- * <p>Run with:<pre>
- m FrameworksServicesTests &&
- adb install \
- -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
- adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
- -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
- * </pre>
- */
-public class UserRestrictionsUtilsTest extends AndroidTestCase {
- public void testNonNull() {
- Bundle out = UserRestrictionsUtils.nonNull(null);
- assertNotNull(out);
- out.putBoolean("a", true); // Should not be Bundle.EMPTY.
-
- Bundle in = new Bundle();
- assertSame(in, UserRestrictionsUtils.nonNull(in));
- }
-
- public void testIsEmpty() {
- assertTrue(UserRestrictionsUtils.isEmpty(null));
- assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
- assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
- }
-
- public void testClone() {
- Bundle in = new Bundle();
- Bundle out = UserRestrictionsUtils.clone(in);
- assertNotSame(in, out);
- DpmTestUtils.assertRestrictions(out, new Bundle());
-
- out = UserRestrictionsUtils.clone(null);
- assertNotNull(out);
- out.putBoolean("a", true); // Should not be Bundle.EMPTY.
- }
-
- public void testMerge() {
- Bundle a = DpmTestUtils.newRestrictions("a", "d");
- Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
-
- UserRestrictionsUtils.merge(a, b);
-
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
-
- UserRestrictionsUtils.merge(a, null);
-
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
-
- try {
- UserRestrictionsUtils.merge(a, a);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testIsSystemControlled() {
- assertTrue(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_RECORD_AUDIO));
- assertFalse(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_FUN));
- }
-
- public void testCanDeviceOwnerChange() {
- assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
- assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
- assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
- }
-
- public void testCanProfileOwnerChange() {
- assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
- assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
- assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
- assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
- }
-
- public void testSortToGlobalAndLocal() {
- final Bundle local = new Bundle();
- final Bundle global = new Bundle();
-
- UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
- assertEquals(0, global.size());
- assertEquals(0, local.size());
-
- UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
- assertEquals(0, global.size());
- assertEquals(0, local.size());
-
- UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_ADJUST_VOLUME,
- UserManager.DISALLOW_UNMUTE_MICROPHONE,
- UserManager.DISALLOW_USB_FILE_TRANSFER,
- UserManager.DISALLOW_CONFIG_TETHERING,
- UserManager.DISALLOW_OUTGOING_BEAM,
- UserManager.DISALLOW_APPS_CONTROL
- ), global, local);
-
-
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
- // These can be set by PO too, but when DO sets them, they're global.
- UserManager.DISALLOW_ADJUST_VOLUME,
- UserManager.DISALLOW_UNMUTE_MICROPHONE,
-
- // These can only be set by DO.
- UserManager.DISALLOW_USB_FILE_TRANSFER,
- UserManager.DISALLOW_CONFIG_TETHERING
- ), global);
-
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
- // They can be set by both DO/PO.
- UserManager.DISALLOW_OUTGOING_BEAM,
- UserManager.DISALLOW_APPS_CONTROL
- ), local);
- }
-
- public void testAreEqual() {
- assertTrue(UserRestrictionsUtils.areEqual(
- null,
- null));
-
- assertTrue(UserRestrictionsUtils.areEqual(
- null,
- Bundle.EMPTY));
-
- assertTrue(UserRestrictionsUtils.areEqual(
- Bundle.EMPTY,
- null));
-
- assertTrue(UserRestrictionsUtils.areEqual(
- Bundle.EMPTY,
- Bundle.EMPTY));
-
- assertTrue(UserRestrictionsUtils.areEqual(
- new Bundle(),
- Bundle.EMPTY));
-
- assertFalse(UserRestrictionsUtils.areEqual(
- null,
- DpmTestUtils.newRestrictions("a")));
-
- assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
- null));
-
- assertTrue(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
- DpmTestUtils.newRestrictions("a")));
-
- assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
- DpmTestUtils.newRestrictions("a", "b")));
-
- assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a", "b"),
- DpmTestUtils.newRestrictions("a")));
-
- assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("b", "a"),
- DpmTestUtils.newRestrictions("a", "a")));
-
- // Make sure false restrictions are handled correctly.
- final Bundle a = DpmTestUtils.newRestrictions("a");
- a.putBoolean("b", true);
-
- final Bundle b = DpmTestUtils.newRestrictions("a");
- b.putBoolean("b", false);
-
- assertFalse(UserRestrictionsUtils.areEqual(a, b));
- assertFalse(UserRestrictionsUtils.areEqual(b, a));
- }
-}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 67b9d77..8eb30d2 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -160,6 +160,36 @@
}
},
+ new Test("with topic Hello") {
+ public void run() {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon1)
+ .setWhen(mActivityCreateTime)
+ .setContentTitle("hihi")
+ .setContentText("This is a notification!!!")
+ .setContentIntent(makeIntent2())
+ .setTopic(new Notification.Topic("hello", "Hello"))
+ .build();
+
+ mNM.notify(999, n);
+ }
+ },
+
+ new Test("with topic GoodBye") {
+ public void run() {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon1)
+ .setWhen(mActivityCreateTime)
+ .setContentTitle("byebye")
+ .setContentText("This is a notification!!!")
+ .setContentIntent(makeIntent2())
+ .setTopic(new Notification.Topic("bye", "Goodbye"))
+ .build();
+
+ mNM.notify(9999, n);
+ }
+ },
+
new Test("Whens") {
public void run()
{
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 1ec0547..5c73fb6a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -167,6 +167,11 @@
}
@Override
+ public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
public void dragRecipientEntered(IWindow window) throws RemoteException {
// pass for now
}