Merge "Change popup menu header color to secondary color."
diff --git a/api/current.txt b/api/current.txt
index e3c9366..77c64d7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9235,6 +9235,7 @@
method public void setAppLabel(java.lang.CharSequence);
method public void setAppPackageName(java.lang.String);
method public void setInstallLocation(int);
+ method public void setOriginatingUid(int);
method public void setOriginatingUri(android.net.Uri);
method public void setReferrerUri(android.net.Uri);
method public void setSize(long);
diff --git a/api/system-current.txt b/api/system-current.txt
index b1590d8..cff0055 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9518,6 +9518,7 @@
method public void setAppPackageName(java.lang.String);
method public void setGrantedRuntimePermissions(java.lang.String[]);
method public void setInstallLocation(int);
+ method public void setOriginatingUid(int);
method public void setOriginatingUri(android.net.Uri);
method public void setReferrerUri(android.net.Uri);
method public void setSize(long);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 16f825d..d444638 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -603,7 +603,17 @@
createDependencyGraph();
// Now that all dependencies are set up, start the animations that should be started.
- start(mRootNode);
+ boolean setIsEmpty = false;
+ if (mStartDelay > 0) {
+ start(mRootNode);
+ } else if (mNodes.size() > 1) {
+ // No delay, but there are other animators in the set
+ onChildAnimatorEnded(mDelayAnim);
+ } else {
+ // Set is empty, no delay, no other animation. Skip to end in this case
+ setIsEmpty = true;
+ }
+
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
@@ -612,18 +622,9 @@
tmpListeners.get(i).onAnimationStart(this);
}
}
- if (mNodes.size() == 0 && mStartDelay == 0) {
- // Handle unusual case where empty AnimatorSet is started - should send out
- // end event immediately since the event will not be sent out at all otherwise
- mStarted = false;
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this);
- }
- }
+ if (setIsEmpty) {
+ // In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
+ onChildAnimatorEnded(mDelayAnim);
}
}
@@ -751,44 +752,7 @@
public void onAnimationEnd(Animator animation) {
animation.removeListener(this);
mAnimatorSet.mPlayingSet.remove(animation);
- Node animNode = mAnimatorSet.mNodeMap.get(animation);
- animNode.mEnded = true;
-
- if (!mAnimatorSet.mTerminated) {
- List<Node> children = animNode.mChildNodes;
- // Start children animations, if any.
- int childrenSize = children == null ? 0 : children.size();
- for (int i = 0; i < childrenSize; i++) {
- if (children.get(i).mLatestParent == animNode) {
- mAnimatorSet.start(children.get(i));
- }
- }
- // Listeners are already notified of the AnimatorSet ending in cancel() or
- // end(); the logic below only kicks in when animations end normally
- boolean allDone = true;
- // Traverse the tree and find if there's any unfinished node
- int size = mAnimatorSet.mNodes.size();
- for (int i = 0; i < size; i++) {
- if (!mAnimatorSet.mNodes.get(i).mEnded) {
- allDone = false;
- break;
- }
- }
- if (allDone) {
- // If this was the last child animation to end, then notify listeners that this
- // AnimatorSet has ended
- if (mAnimatorSet.mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mAnimatorSet.mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(mAnimatorSet);
- }
- }
- mAnimatorSet.mStarted = false;
- mAnimatorSet.mPaused = false;
- }
- }
+ mAnimatorSet.onChildAnimatorEnded(animation);
}
// Nothing to do
@@ -801,6 +765,47 @@
}
+ private void onChildAnimatorEnded(Animator animation) {
+ Node animNode = mNodeMap.get(animation);
+ animNode.mEnded = true;
+
+ if (!mTerminated) {
+ List<Node> children = animNode.mChildNodes;
+ // Start children animations, if any.
+ int childrenSize = children == null ? 0 : children.size();
+ for (int i = 0; i < childrenSize; i++) {
+ if (children.get(i).mLatestParent == animNode) {
+ start(children.get(i));
+ }
+ }
+ // Listeners are already notified of the AnimatorSet ending in cancel() or
+ // end(); the logic below only kicks in when animations end normally
+ boolean allDone = true;
+ // Traverse the tree and find if there's any unfinished node
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ if (!mNodes.get(i).mEnded) {
+ allDone = false;
+ break;
+ }
+ }
+ if (allDone) {
+ // If this was the last child animation to end, then notify listeners that this
+ // AnimatorSet has ended
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this);
+ }
+ }
+ mStarted = false;
+ mPaused = false;
+ }
+ }
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index da9a8c8..1995ef5 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -669,6 +669,10 @@
* The frame delay may be ignored when the animation system uses an external timing
* source, such as the display refresh rate (vsync), to govern animations.
*
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to check the frame delay for that animation. A runtime exception will be
+ * thrown if the calling thread does not have a Looper.
+ *
* @return the requested time between frames, in milliseconds
*/
public static long getFrameDelay() {
@@ -685,6 +689,10 @@
* The frame delay may be ignored when the animation system uses an external timing
* source, such as the display refresh rate (vsync), to govern animations.
*
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to have the new frame delay take effect on that animation. A runtime
+ * exception will be thrown if the calling thread does not have a Looper.
+ *
* @param frameDelay the requested time between frames, in milliseconds
*/
public static void setFrameDelay(long frameDelay) {
@@ -924,6 +932,13 @@
updateScaledDuration(); // in case the scale factor has changed since creation time
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, mStartDelay);
+
+ if (mStartDelay == 0) {
+ // If there's no start delay, init the animation and notify start listeners right away
+ // Otherwise, postpone this until the first frame after the start delay.
+ startAnimation();
+ setCurrentFraction(mSeekFraction == -1 ? 0 : mSeekFraction);
+ }
}
@Override
@@ -1067,6 +1082,7 @@
mStartListenersCalled = false;
mPlayingBackwards = false;
mReversing = false;
+ mLastFrameTime = 0;
mCurrentIteration = 0;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
@@ -1176,12 +1192,13 @@
* @hide
*/
public final void doAnimationFrame(long frameTime) {
- mLastFrameTime = frameTime;
AnimationHandler handler = AnimationHandler.getInstance();
- if (!mRunning) {
+ if (mLastFrameTime == 0) {
// First frame
handler.addOneShotCommitCallback(this);
- startAnimation();
+ if (mStartDelay > 0) {
+ startAnimation();
+ }
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
@@ -1191,6 +1208,7 @@
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
+ mLastFrameTime = frameTime;
if (mPaused) {
if (mPauseTime < 0) {
mPauseTime = frameTime;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6606f9b..61a9a84 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -468,36 +468,51 @@
*/
public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
-
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
- * that the resize is from the window manager (instead of the user).
+ * that the resize doesn't need to preserve the window, and can be skipped if bounds
+ * is unchanged. This mode is used by window manager in most cases.
* @hide
*/
public static final int RESIZE_MODE_SYSTEM = 0;
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
- * that the resize is from the window manager (instead of the user) due to a screen
- * rotation change.
+ * that the resize should preserve the window if possible.
* @hide
*/
- public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = 1;
-
- /**
- * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
- * that the resize is initiated by the user (most likely via a drag action on the
- * window's edge or corner).
- * @hide
- */
- public static final int RESIZE_MODE_USER = 2;
+ public static final int RESIZE_MODE_PRESERVE_WINDOW = (0x1 << 0);
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
* that the resize should be performed even if the bounds appears unchanged.
* @hide
*/
- public static final int RESIZE_MODE_FORCED = 3;
+ public static final int RESIZE_MODE_FORCED = (0x1 << 1);
+
+ /**
+ * Input parameter to {@link android.app.IActivityManager#resizeTask} used by window
+ * manager during a screen rotation.
+ * @hide
+ */
+ public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW;
+
+ /**
+ * Input parameter to {@link android.app.IActivityManager#resizeTask} used when the
+ * resize is due to a drag action.
+ * @hide
+ */
+ public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW;
+
+ /**
+ * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
+ * that the resize should preserve the window if possible, and should not be skipped
+ * even if the bounds is unchanged. Usually used to force a resizing when a drag action
+ * is ending.
+ * @hide
+ */
+ public static final int RESIZE_MODE_USER_FORCED =
+ RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED;
/** @hide */
public int getFrontActivityScreenCompatMode() {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 40eb799..373a23f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -55,4 +55,13 @@
* @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
*/
public abstract ComponentName getHomeActivityForUser(int userId);
+
+ /**
+ * Called when a user has been deleted. This can happen during normal device usage
+ * or just at startup, when partially removed users are purged. Any state persisted by the
+ * ActivityManager should be purged now.
+ *
+ * @param userId The user being cleaned up.
+ */
+ public abstract void onUserRemoved(int userId);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 9341be1..3283005 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -868,6 +868,9 @@
public static final int MODE_INHERIT_EXISTING = 2;
/** {@hide} */
+ public static final int UID_UNKNOWN = -1;
+
+ /** {@hide} */
public int mode = MODE_INVALID;
/** {@hide} */
public int installFlags;
@@ -886,6 +889,8 @@
/** {@hide} */
public Uri originatingUri;
/** {@hide} */
+ public int originatingUid = UID_UNKNOWN;
+ /** {@hide} */
public Uri referrerUri;
/** {@hide} */
public String abiOverride;
@@ -915,6 +920,7 @@
appIcon = source.readParcelable(null);
appLabel = source.readString();
originatingUri = source.readParcelable(null);
+ originatingUid = source.readInt();
referrerUri = source.readParcelable(null);
abiOverride = source.readString();
volumeUuid = source.readString();
@@ -983,6 +989,15 @@
}
/**
+ * Sets the UID that initiated package installation. Used for verification purposes.
+ *
+ * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+ */
+ public void setOriginatingUid(int originatingUid) {
+ this.originatingUid = originatingUid;
+ }
+
+ /**
* Optionally set the URI that referred you to install this package. Used
* for verification purposes.
*
@@ -1022,6 +1037,11 @@
}
/** {@hide} */
+ public void setInstallFlagsForcePermissionPrompt() {
+ installFlags |= PackageManager.INSTALL_FORCE_PERMISSION_PROMPT;
+ }
+
+ /** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
pw.printHexPair("installFlags", installFlags);
@@ -1031,6 +1051,7 @@
pw.printPair("appIcon", (appIcon != null));
pw.printPair("appLabel", appLabel);
pw.printPair("originatingUri", originatingUri);
+ pw.printPair("originatingUid", originatingUid);
pw.printPair("referrerUri", referrerUri);
pw.printPair("abiOverride", abiOverride);
pw.printPair("volumeUuid", volumeUuid);
@@ -1053,6 +1074,7 @@
dest.writeParcelable(appIcon, flags);
dest.writeString(appLabel);
dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
dest.writeParcelable(referrerUri, flags);
dest.writeString(abiOverride);
dest.writeString(volumeUuid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 697b946..82cbbbe 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -422,6 +422,15 @@
public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
/**
+ * Flag parameter for {@link #installPackage} to indicate that we always want to force
+ * the prompt for permission approval. This overrides any special behaviour for internal
+ * components.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FORCE_PERMISSION_PROMPT = 0x00000400;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index e965d65..3f566eb 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1545,7 +1545,7 @@
/**
* <p>Whether video stabilization is
* active.</p>
- * <p>Video stabilization automatically translates and scales images from
+ * <p>Video stabilization automatically warps images from
* the camera in order to stabilize motion between consecutive frames.</p>
* <p>If enabled, video stabilization can modify the
* {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
@@ -1555,6 +1555,15 @@
* the video stabilization modes in the first several capture results may
* still be "OFF", and it will become "ON" when the initialization is
* done.</p>
+ * <p>In addition, not all recording sizes or frame rates may be supported for
+ * stabilization by a device that reports stabilization support. It is guaranteed
+ * that an output targeting a MediaRecorder or MediaCodec will be stabilized if
+ * the recording resolution is less than or equal to 1920 x 1080 (width less than
+ * or equal to 1920, height less than or equal to 1080), and the recording
+ * frame rate is less than or equal to 30fps. At other sizes, the CaptureResult
+ * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
+ * OFF if the recording output is not stabilized, or if there are no output
+ * Surface types that can be stabilized.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
@@ -1566,6 +1575,7 @@
* </ul></p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 46eddb3..b3acf2b 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2056,7 +2056,7 @@
/**
* <p>Whether video stabilization is
* active.</p>
- * <p>Video stabilization automatically translates and scales images from
+ * <p>Video stabilization automatically warps images from
* the camera in order to stabilize motion between consecutive frames.</p>
* <p>If enabled, video stabilization can modify the
* {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
@@ -2066,6 +2066,15 @@
* the video stabilization modes in the first several capture results may
* still be "OFF", and it will become "ON" when the initialization is
* done.</p>
+ * <p>In addition, not all recording sizes or frame rates may be supported for
+ * stabilization by a device that reports stabilization support. It is guaranteed
+ * that an output targeting a MediaRecorder or MediaCodec will be stabilized if
+ * the recording resolution is less than or equal to 1920 x 1080 (width less than
+ * or equal to 1920, height less than or equal to 1080), and the recording
+ * frame rate is less than or equal to 30fps. At other sizes, the CaptureResult
+ * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
+ * OFF if the recording output is not stabilized, or if there are no output
+ * Surface types that can be stabilized.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
@@ -2077,6 +2086,7 @@
* </ul></p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index c373308..cd483b1 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.MathUtils;
import java.io.Serializable;
import java.util.ArrayList;
@@ -1345,18 +1346,19 @@
*/
void readFromParcelInner(Parcel parcel) {
int length = parcel.readInt();
- if (length < 0) {
- throw new RuntimeException("Bad length in parcel: " + length);
- }
readFromParcelInner(parcel, length);
}
private void readFromParcelInner(Parcel parcel, int length) {
- if (length == 0) {
+ if (length < 0) {
+ throw new RuntimeException("Bad length in parcel: " + length);
+
+ } else if (length == 0) {
// Empty Bundle or end of data.
mParcelledData = EMPTY_PARCEL;
return;
}
+
int magic = parcel.readInt();
if (magic != BUNDLE_MAGIC) {
//noinspection ThrowableInstanceNeverThrown
@@ -1366,7 +1368,7 @@
// Advance within this Parcel
int offset = parcel.dataPosition();
- parcel.setDataPosition(offset + length);
+ parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
Parcel p = Parcel.obtain();
p.setDataPosition(0);
diff --git a/core/java/android/os/ParcelableParcel.java b/core/java/android/os/ParcelableParcel.java
index 11785f1..5bbe6488 100644
--- a/core/java/android/os/ParcelableParcel.java
+++ b/core/java/android/os/ParcelableParcel.java
@@ -16,6 +16,8 @@
package android.os;
+import android.util.MathUtils;
+
/**
* Parcelable containing a raw Parcel of data.
* @hide
@@ -33,9 +35,13 @@
mParcel = Parcel.obtain();
mClassLoader = loader;
int size = src.readInt();
+ if (size < 0) {
+ throw new IllegalArgumentException("Negative size read from parcel");
+ }
+
int pos = src.dataPosition();
- mParcel.appendFrom(src, src.dataPosition(), size);
- src.setDataPosition(pos + size);
+ src.setDataPosition(MathUtils.addOrThrow(pos, size));
+ mParcel.appendFrom(src, pos, size);
}
public Parcel getParcel() {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 1b104e3..241e6db 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,6 +19,7 @@
import static android.net.TrafficStats.KB_IN_BYTES;
import static android.system.OsConstants.SEEK_SET;
+import android.annotation.Nullable;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -764,19 +765,33 @@
* @see #buildDocumentUri(String, String)
* @see #buildDocumentUriUsingTree(Uri, String)
*/
- public static boolean isDocumentUri(Context context, Uri uri) {
- final List<String> paths = uri.getPathSegments();
- if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) {
- return isDocumentsProvider(context, uri.getAuthority());
- }
- if (paths.size() == 4 && PATH_TREE.equals(paths.get(0))
- && PATH_DOCUMENT.equals(paths.get(2))) {
- return isDocumentsProvider(context, uri.getAuthority());
+ public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
+ if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
+ final List<String> paths = uri.getPathSegments();
+ if (paths.size() == 2) {
+ return PATH_DOCUMENT.equals(paths.get(0));
+ } else if (paths.size() == 4) {
+ return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
+ }
}
return false;
}
/** {@hide} */
+ public static boolean isRootUri(Context context, @Nullable Uri uri) {
+ if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
+ final List<String> paths = uri.getPathSegments();
+ return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
+ }
+ return false;
+ }
+
+ /** {@hide} */
+ public static boolean isContentUri(@Nullable Uri uri) {
+ return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
+ }
+
+ /** {@hide} */
public static boolean isTreeUri(Uri uri) {
final List<String> paths = uri.getPathSegments();
return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 1665c75..f2b6041 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -16,15 +16,17 @@
package android.text;
-import com.android.internal.annotations.GuardedBy;
-
import android.annotation.Nullable;
import android.util.Log;
-import libcore.io.IoUtils;
+import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Locale;
@@ -45,12 +47,18 @@
@GuardedBy("sLock")
final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
- final static Hyphenator sEmptyHyphenator = new Hyphenator(StaticLayout.nLoadHyphenator(""));
+ final static Hyphenator sEmptyHyphenator =
+ new Hyphenator(StaticLayout.nLoadHyphenator(null, 0), null);
final private long mNativePtr;
- private Hyphenator(long nativePtr) {
+ // We retain a reference to the buffer to keep the memory mapping valid
+ @SuppressWarnings("unused")
+ final private ByteBuffer mBuffer;
+
+ private Hyphenator(long nativePtr, ByteBuffer b) {
mNativePtr = nativePtr;
+ mBuffer = b;
}
public long getNativePtr() {
@@ -94,12 +102,18 @@
}
private static Hyphenator loadHyphenator(String languageTag) {
- String patternFilename = "hyph-"+languageTag.toLowerCase(Locale.US)+".pat.txt";
+ String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
File patternFile = new File(getSystemHyphenatorLocation(), patternFilename);
try {
- String patternData = IoUtils.readFileAsString(patternFile.getAbsolutePath());
- long nativePtr = StaticLayout.nLoadHyphenator(patternData);
- return new Hyphenator(nativePtr);
+ RandomAccessFile f = new RandomAccessFile(patternFile, "r");
+ try {
+ FileChannel fc = f.getChannel();
+ MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
+ long nativePtr = StaticLayout.nLoadHyphenator(buf, 0);
+ return new Hyphenator(nativePtr, buf);
+ } finally {
+ f.close();
+ }
} catch (IOException e) {
Log.e(TAG, "error loading hyphenation " + patternFile, e);
return null;
@@ -152,7 +166,7 @@
sMap.put(null, null);
// TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
- String[] availableLanguages = {"en-US", "eu", "hu", "hy", "nb", "nn", "sa", "und-Ethi"};
+ String[] availableLanguages = {"en-US", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
for (int i = 0; i < availableLanguages.length; i++) {
String languageTag = availableLanguages[i];
Hyphenator h = loadHyphenator(languageTag);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 79c4a55..6ece091 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -29,6 +29,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
@@ -1244,7 +1245,7 @@
private static native void nFreeBuilder(long nativePtr);
private static native void nFinishBuilder(long nativePtr);
- /* package */ static native long nLoadHyphenator(String patternData);
+ /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset);
private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 8b57d3d..acca3ed 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -185,4 +185,24 @@
public static void randomSeed(long seed) {
sRandom.setSeed(seed);
}
+
+ /**
+ * Returns the sum of the two parameters, or throws an exception if the resulting sum would
+ * cause an overflow or underflow.
+ * @throws IllegalArgumentException when overflow or underflow would occur.
+ */
+ public static int addOrThrow(int a, int b) throws IllegalArgumentException {
+ if (b == 0) {
+ return a;
+ }
+
+ if (b > 0 && a <= (Integer.MAX_VALUE - b)) {
+ return a + b;
+ }
+
+ if (b < 0 && a >= (Integer.MIN_VALUE - b)) {
+ return a + b;
+ }
+ throw new IllegalArgumentException("Addition overflow: " + a + " + " + b);
+ }
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index dcef142..304e9c0 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -349,7 +349,7 @@
* @param right The right side of the protected bounds.
* @param bottom The bottom side of the protected bounds.
*/
- public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ public void setContentDrawBounds(int left, int top, int right, int bottom) {
mStagedContentBounds.set(left, top, right, bottom);
}
@@ -370,9 +370,9 @@
// renderer.
if (!mCurrentContentBounds.equals(mStagedContentBounds)) {
mCurrentContentBounds.set(mStagedContentBounds);
- nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left,
- mCurrentContentBounds.top, mCurrentContentBounds.right,
- mCurrentContentBounds.bottom);
+ nSetContentDrawBounds(mNativeProxy, mCurrentContentBounds.left,
+ mCurrentContentBounds.top, mCurrentContentBounds.right,
+ mCurrentContentBounds.bottom);
}
attachInfo.mIgnoreDirtyState = false;
@@ -600,6 +600,6 @@
boolean placeFront);
private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
- private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left,
+ private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 263ec7d1..2c7a436 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6649,12 +6649,24 @@
}
/**
- * Sets a delegate for implementing accessibility support via composition as
- * opposed to inheritance. The delegate's primary use is for implementing
- * backwards compatible widgets. For more details see {@link AccessibilityDelegate}.
+ * Sets a delegate for implementing accessibility support via composition
+ * (as opposed to inheritance). For more details, see
+ * {@link AccessibilityDelegate}.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*
- * @param delegate The delegate instance.
- *
+ * @param delegate the object to which accessibility method calls should be
+ * delegated
* @see AccessibilityDelegate
*/
public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {
@@ -22277,6 +22289,18 @@
* corresponding delegate method without altering the behavior of the rest
* accessibility related methods of the host view.
* </p>
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*/
public static class AccessibilityDelegate {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f6c60ed..7cf23e7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -73,6 +73,7 @@
import android.view.animation.Interpolator;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.WindowCallbacks;
import android.widget.Scroller;
import com.android.internal.R;
@@ -115,6 +116,12 @@
private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
/**
+ * Set to false if we do not want to use the multi threaded renderer. Note that by disabling
+ * this, WindowCallbacks will not fire.
+ */
+ private static final boolean USE_MT_RENDERER = true;
+
+ /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -132,11 +139,11 @@
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
- static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
+ static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
static boolean sFirstDrawComplete = false;
+ static final ArrayList<WindowCallbacks> sWindowCallbacks = new ArrayList();
- static final ArrayList<ComponentCallbacks> sConfigCallbacks
- = new ArrayList<ComponentCallbacks>();
+ static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
final Context mContext;
final IWindowSession mWindowSession;
@@ -417,6 +424,22 @@
}
}
+ public static void addWindowCallbacks(WindowCallbacks callback) {
+ if (USE_MT_RENDERER) {
+ synchronized (sWindowCallbacks) {
+ sWindowCallbacks.add(callback);
+ }
+ }
+ }
+
+ public static void removeWindowCallbacks(WindowCallbacks callback) {
+ if (USE_MT_RENDERER) {
+ synchronized (sWindowCallbacks) {
+ sWindowCallbacks.remove(callback);
+ }
+ }
+ }
+
// FIXME for perf testing only
private boolean mProfile = false;
@@ -1378,6 +1401,7 @@
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
+ endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
@@ -1701,14 +1725,20 @@
final boolean dragResizing = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0;
if (mDragResizing != dragResizing) {
- mDragResizing = dragResizing;
- mFullRedrawNeeded = true;
+ if (dragResizing) {
+ startDragResizing(frame);
+ } else {
+ // We shouldn't come here, but if we come we should end the resize.
+ endDragResizing();
+ }
}
- if (dragResizing) {
- mCanvasOffsetX = mWinFrame.left;
- mCanvasOffsetY = mWinFrame.top;
- } else {
- mCanvasOffsetX = mCanvasOffsetY = 0;
+ if (!USE_MT_RENDERER) {
+ if (dragResizing) {
+ mCanvasOffsetX = mWinFrame.left;
+ mCanvasOffsetY = mWinFrame.top;
+ } else {
+ mCanvasOffsetX = mCanvasOffsetY = 0;
+ }
}
} catch (RemoteException e) {
}
@@ -6635,6 +6665,15 @@
Configuration newConfig) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
+ // Tell all listeners that we are resizing the window so that the chrome can get
+ // updated as fast as possible on a separate thread,
+ if (mViewAncestor.get().mDragResizing) {
+ synchronized (sWindowCallbacks) {
+ for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
+ sWindowCallbacks.get(i).onWindowSizeIsChanging(frame);
+ }
+ }
+ }
viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, stableInsets, outsets, reportDraw, newConfig);
}
@@ -6804,6 +6843,36 @@
}
/**
+ * Start a drag resizing which will inform all listeners that a window resize is taking place.
+ */
+ private void startDragResizing(Rect initialBounds) {
+ if (!mDragResizing) {
+ mDragResizing = true;
+ synchronized (sWindowCallbacks) {
+ for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
+ sWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds);
+ }
+ }
+ mFullRedrawNeeded = true;
+ }
+ }
+
+ /**
+ * End a drag resize which will inform all listeners that a window resize has ended.
+ */
+ private void endDragResizing() {
+ if (mDragResizing) {
+ mDragResizing = false;
+ synchronized (sWindowCallbacks) {
+ for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
+ sWindowCallbacks.get(i).onWindowDragResizeEnd();
+ }
+ }
+ mFullRedrawNeeded = true;
+ }
+ }
+
+ /**
* Class for managing the accessibility interaction connection
* based on the global accessibility state.
*/
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
new file mode 100644
index 0000000..cb6e983
--- /dev/null
+++ b/core/java/android/view/WindowCallbacks.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.view;
+
+import android.graphics.Rect;
+
+/**
+ * These callbacks are used to communicate window configuration changes while the user is performing
+ * window changes.
+ * @hide
+ */
+public interface WindowCallbacks {
+ /**
+ * Called by the system when the window got changed by the user, before the layouter got called.
+ * It can be used to perform a "quick and dirty" resize which should never take more then 4ms to
+ * complete.
+ *
+ * <p>At the time the layouting has not happened yet.
+ *
+ * @param newBounds The new window frame bounds.
+ */
+ void onWindowSizeIsChanging(Rect newBounds);
+
+ /**
+ * Called when a drag resize starts.
+ * @param initialBounds The initial bounds where the window will be.
+ */
+ void onWindowDragResizeStart(Rect initialBounds);
+
+ /**
+ * Called when a drag resize ends.
+ */
+ void onWindowDragResizeEnd();
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b6d7364..c9b8119 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2615,7 +2615,7 @@
if (action == MotionEvent.ACTION_DOWN) {
int y = (int)event.getY();
if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
- Log.i(TAG, "Watchiing!");
+ Log.i(TAG, "Watching!");
mWatchingForMenu = true;
}
return false;
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index 56cf921..6960a3b 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -17,15 +17,23 @@
package com.android.internal.widget;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.AttributeSet;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
import android.view.MotionEvent;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
import android.view.View;
+import android.view.ViewRootImpl;
import android.widget.LinearLayout;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.Window;
+import android.view.WindowCallbacks;
import android.util.Log;
import android.util.TypedValue;
@@ -58,7 +66,7 @@
* This will be mitigated once b/22527834 will be addressed.
*/
public class NonClientDecorView extends LinearLayout
- implements View.OnClickListener, View.OnTouchListener {
+ implements View.OnClickListener, View.OnTouchListener, WindowCallbacks {
private final static String TAG = "NonClientDecorView";
// The height of a window which has focus in DIP.
private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
@@ -67,6 +75,8 @@
private PhoneWindow mOwner = null;
private boolean mWindowHasShadow = false;
private boolean mShowDecor = false;
+ // True when this object is listening for window size changes.
+ private boolean mAttachedCallbacksToRootViewImpl = false;
// True if the window is being dragged.
private boolean mDragging = false;
@@ -85,6 +95,9 @@
// to max until the first layout command has been executed.
private boolean mAllowUpdateElevation = false;
+ // The resize frame renderer.
+ private ResizeFrameThread mFrameRendererThread = null;
+
public NonClientDecorView(Context context) {
super(context);
}
@@ -108,6 +121,18 @@
// By changing the outline provider to BOUNDS, the window can remove its
// background without removing the shadow.
mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+
+ if (!mAttachedCallbacksToRootViewImpl) {
+ // If there is no window callback installed there was no window set before. Set it now.
+ // Note that our ViewRootImpl object will not change.
+ getViewRootImpl().addWindowCallbacks(this);
+ mAttachedCallbacksToRootViewImpl = true;
+ } else if (mFrameRendererThread != null) {
+ // We are resizing and this call happened due to a configuration change. Tell the
+ // renderer about it.
+ mFrameRendererThread.onConfigurationChange();
+ }
+
findViewById(R.id.maximize_window).setOnClickListener(this);
findViewById(R.id.close_window).setOnClickListener(this);
}
@@ -251,7 +276,9 @@
**/
private void updateElevation() {
float elevation = 0;
- if (mWindowHasShadow) {
+ // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
+ // is bound to the content size and not the target size.
+ if (mWindowHasShadow && mFrameRendererThread == null) {
boolean fill = isFillingScreen();
elevation = fill ? 0 :
(mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
@@ -293,4 +320,270 @@
}
}
}
+
+ @Override
+ public void onWindowDragResizeStart(Rect initialBounds) {
+ if (mOwner.isDestroyed()) {
+ // If the owner's window is gone, we should not be able to come here anymore.
+ releaseResources();
+ return;
+ }
+ if (mFrameRendererThread != null) {
+ return;
+ }
+ final ThreadedRenderer renderer =
+ (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
+ if (renderer != null) {
+ mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
+ // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
+ // If we want to get the shadow shown while resizing, we would need to elevate a new
+ // element which owns the caption and has the elevation.
+ updateElevation();
+ }
+ }
+
+ @Override
+ public void onWindowDragResizeEnd() {
+ releaseThreadedRenderer();
+ }
+
+ @Override
+ public void onWindowSizeIsChanging(Rect newBounds) {
+ if (mFrameRendererThread != null) {
+ mFrameRendererThread.setTargetRect(newBounds);
+ }
+ }
+
+ /**
+ * Release the renderer thread which is usually done when the user stops resizing.
+ */
+ private void releaseThreadedRenderer() {
+ if (mFrameRendererThread != null) {
+ mFrameRendererThread.releaseRenderer();
+ mFrameRendererThread = null;
+ // Bring the shadow back.
+ updateElevation();
+ }
+ }
+
+ /**
+ * Called when the parent window is destroyed to release all resources. Note that this will also
+ * destroy the renderer thread.
+ */
+ private void releaseResources() {
+ releaseThreadedRenderer();
+ if (mAttachedCallbacksToRootViewImpl) {
+ ViewRootImpl.removeWindowCallbacks(this);
+ mAttachedCallbacksToRootViewImpl = false;
+ }
+ }
+
+ /**
+ * The thread which draws the chrome while we are resizing.
+ * It starts with the creation and it ends once someone calls destroy().
+ * Any size changes can be passed by a call to setTargetRect will passed to the thread and
+ * executed via the Choreographer.
+ */
+ private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
+ // This is containing the last requested size by a resize command. Note that this size might
+ // or might not have been applied to the output already.
+ private final Rect mTargetRect = new Rect();
+
+ // The render nodes for the multi threaded renderer.
+ private ThreadedRenderer mRenderer;
+ private RenderNode mFrameNode;
+ private RenderNode mBackdropNode;
+
+ private final Rect mOldTargetRect = new Rect();
+ private final Rect mNewTargetRect = new Rect();
+ private Choreographer mChoreographer;
+
+ // Cached size values from the last render for the case that the view hierarchy is gone
+ // during a configuration change.
+ private int mLastContentWidth;
+ private int mLastContentHeight;
+ private int mLastCaptionHeight;
+ private int mLastXOffset;
+ private int mLastYOffset;
+
+ ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
+ mRenderer = renderer;
+
+ // Create the render nodes for our frame and backdrop which can be resized independently
+ // from the content.
+ mFrameNode = RenderNode.create("FrameNode", null);
+ mBackdropNode = RenderNode.create("BackdropNode", null);
+
+ mRenderer.addRenderNode(mFrameNode, false);
+ mRenderer.addRenderNode(mBackdropNode, true);
+
+ // Set the initial bounds and draw once so that we do not get a broken frame.
+ mTargetRect.set(initialBounds);
+ changeWindowSize(initialBounds);
+
+ // Kick off our draw thread.
+ start();
+ }
+
+ /**
+ * Call this function asynchronously when the window size has been changed. The change will
+ * be picked up once per frame and the frame will be re-rendered accordingly.
+ * @param newTargetBounds The new target bounds.
+ */
+ public void setTargetRect(Rect newTargetBounds) {
+ synchronized (this) {
+ mTargetRect.set(newTargetBounds);
+ // Notify of a bounds change.
+ pingRenderLocked();
+ }
+ }
+
+ /**
+ * The window got replaced due to a configuration change.
+ */
+ public void onConfigurationChange() {
+ if (mRenderer != null) {
+ // Enforce a window redraw.
+ mOldTargetRect.set(0, 0, 0, 0);
+ pingRenderLocked();
+ }
+ }
+
+ /**
+ * All resources of the renderer will be released. This function can be called from the
+ * the UI thread as well as the renderer thread.
+ */
+ public void releaseRenderer() {
+ synchronized (this) {
+ if (mRenderer != null) {
+ // Invalidate the current content bounds.
+ mRenderer.setContentDrawBounds(0, 0, 0, 0);
+
+ // Remove the render nodes again (see comment above - better to do that only once).
+ mRenderer.removeRenderNode(mFrameNode);
+ mRenderer.removeRenderNode(mBackdropNode);
+
+ mRenderer = null;
+
+ // Exit the renderer loop.
+ pingRenderLocked();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ Looper.prepare();
+ mChoreographer = Choreographer.getInstance();
+ Looper.loop();
+ } finally {
+ releaseRenderer();
+ }
+ synchronized (this) {
+ // Make sure no more messages are being sent.
+ mChoreographer = null;
+ }
+ }
+
+ /**
+ * The implementation of the FrameCallback.
+ * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+ * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
+ */
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if (mRenderer == null) {
+ // Tell the looper to stop. We are done.
+ Looper.myLooper().quit();
+ return;
+ }
+ // Prevent someone from changing this while we are copying.
+ synchronized (this) {
+ mNewTargetRect.set(mTargetRect);
+ }
+ if (!mNewTargetRect.equals(mOldTargetRect)) {
+ mOldTargetRect.set(mNewTargetRect);
+ changeWindowSize(mNewTargetRect);
+ }
+ }
+
+ /**
+ * Resizing the frame to fit the new window size.
+ * @param newBounds The window bounds which needs to be drawn.
+ */
+ private void changeWindowSize(Rect newBounds) {
+ long startTime = System.currentTimeMillis();
+
+ // While a configuration change is taking place the view hierarchy might become
+ // inaccessible. For that case we remember the previous metrics to avoid flashes.
+ View caption = getChildAt(0);
+ View content = getChildAt(1);
+ if (content != null && caption != null) {
+ mLastContentWidth = content.getWidth();
+ mLastContentHeight = content.getHeight();
+ mLastCaptionHeight = caption.getHeight();
+
+ // Get the draw position within our surface.
+ int[] surfaceOrigin = new int[2];
+ surfaceOrigin[0] = 0;
+ surfaceOrigin[1] = 0;
+
+ // Get the shadow offsets.
+ getLocationInSurface(surfaceOrigin);
+ mLastXOffset = surfaceOrigin[0];
+ mLastYOffset = surfaceOrigin[1];
+ }
+
+ // Since the surface is spanning the entire screen, we have to add the start offset of
+ // the bounds to get to the surface location.
+ final int left = mLastXOffset + newBounds.left;
+ final int top = mLastYOffset + newBounds.top;
+ final int width = newBounds.width();
+ final int height = newBounds.height();
+
+ // Produce the draw calls.
+ // TODO(skuhne): Create a separate caption view which draws this. If the shadow should
+ // be resized while the window resizes, this hierarchy needs to have the elevation.
+ // That said - it is probably no good idea to draw the shadow every time since it costs
+ // a considerable time which we should rather spend for resizing the content and it does
+ // barely show while the entire screen is moving.
+ mFrameNode.setLeftTopRightBottom(left, top, left + width, top + mLastCaptionHeight);
+ DisplayListCanvas canvas = mFrameNode.start(width, height);
+ canvas.drawColor(Color.BLACK);
+ mFrameNode.end(canvas);
+
+ mBackdropNode.setLeftTopRightBottom(left, top + mLastCaptionHeight, left + width,
+ top + height);
+
+ // The backdrop: clear everything with the background. Clipping is done elsewhere.
+ canvas = mBackdropNode.start(width, height - mLastCaptionHeight);
+ // TODO(skuhne): mOwner.getDecorView().mBackgroundFallback.draw(..) - or similar.
+ // Note: This might not work (calculator for example uses a transparent background).
+ canvas.drawColor(0xff808080);
+ mBackdropNode.end(canvas);
+
+ // The current content buffer is drawn here.
+ mRenderer.setContentDrawBounds(
+ mLastXOffset,
+ mLastYOffset + mLastCaptionHeight,
+ mLastXOffset + mLastContentWidth,
+ mLastYOffset + mLastCaptionHeight + mLastContentHeight);
+
+ // We need to render both rendered nodes explicitly.
+ mRenderer.drawRenderNode(mFrameNode);
+ mRenderer.drawRenderNode(mBackdropNode);
+ }
+
+ /**
+ * Sends a message to the renderer to wake up and perform the next action which can be
+ * either the next rendering or the self destruction if mRenderer is null.
+ * Note: This call must be synchronized.
+ */
+ private void pingRenderLocked() {
+ if (mChoreographer != null) {
+ mChoreographer.postFrameCallback(this);
+ }
+ }
+ }
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 223dae0..314e4b6 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -66,10 +66,6 @@
static jclass gFontMetricsInt_class;
static JMetricsID gFontMetricsInt_fieldID;
-static jclass gPaint_class;
-static jfieldID gPaint_nativeInstanceID;
-static jfieldID gPaint_nativeTypefaceID;
-
static void defaultSettingsForAndroid(Paint* paint) {
// GlyphID encoding is required because we are using Harfbuzz shaping
paint->setTextEncoding(Paint::kGlyphID_TextEncoding);
@@ -82,37 +78,17 @@
static thread_local LocaleCacheEntry sSingleEntryLocaleCache;
-class PaintGlue {
-public:
+namespace PaintGlue {
enum MoveOpt {
AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT
};
- static Paint* getNativePaint(JNIEnv* env, jobject paint) {
- SkASSERT(env);
- SkASSERT(paint);
- SkASSERT(env->IsInstanceOf(paint, gPaint_class));
- jlong paintHandle = env->GetLongField(paint, gPaint_nativeInstanceID);
- android::Paint* p = reinterpret_cast<android::Paint*>(paintHandle);
- SkASSERT(p);
- return p;
- }
-
- static TypefaceImpl* getNativeTypeface(JNIEnv* env, jobject paint) {
- SkASSERT(env);
- SkASSERT(paint);
- SkASSERT(env->IsInstanceOf(paint, gPaint_class));
- jlong typefaceHandle = env->GetLongField(paint, gPaint_nativeTypefaceID);
- android::TypefaceImpl* p = reinterpret_cast<android::TypefaceImpl*>(typefaceHandle);
- return p;
- }
-
static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
delete obj;
}
- static jlong init(JNIEnv* env, jobject clazz) {
+ static jlong init(JNIEnv* env, jobject) {
static_assert(1 << 0 == SkPaint::kAntiAlias_Flag, "paint_flags_mismatch");
static_assert(1 << 2 == SkPaint::kDither_Flag, "paint_flags_mismatch");
static_assert(1 << 3 == SkPaint::kUnderlineText_Flag, "paint_flags_mismatch");
@@ -149,9 +125,8 @@
// Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
static const uint32_t sFilterBitmapFlag = 0x02;
- static jint getFlags(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- Paint* nativePaint = getNativePaint(env, paint);
+ static jint getFlags(JNIEnv* env, jobject, jlong paintHandle) {
+ Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
uint32_t result = nativePaint->getFlags();
result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
@@ -160,9 +135,8 @@
return static_cast<jint>(result);
}
- static void setFlags(JNIEnv* env, jobject paint, jint flags) {
- NPE_CHECK_RETURN_VOID(env, paint);
- Paint* nativePaint = getNativePaint(env, paint);
+ static void setFlags(JNIEnv* env, jobject, jlong paintHandle, jint flags) {
+ Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
// Instead of modifying 0x02, change the filter level.
nativePaint->setFilterQuality(flags & sFilterBitmapFlag
? kLow_SkFilterQuality
@@ -175,57 +149,47 @@
nativePaint->setFlags(flags);
}
- static jint getHinting(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return getNativePaint(env, paint)->getHinting()
+ static jint getHinting(JNIEnv* env, jobject, jlong paintHandle) {
+ return reinterpret_cast<Paint*>(paintHandle)->getHinting()
== Paint::kNo_Hinting ? 0 : 1;
}
- static void setHinting(JNIEnv* env, jobject paint, jint mode) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setHinting(
+ static void setHinting(JNIEnv* env, jobject, jlong paintHandle, jint mode) {
+ reinterpret_cast<Paint*>(paintHandle)->setHinting(
mode == 0 ? Paint::kNo_Hinting : Paint::kNormal_Hinting);
}
- static void setAntiAlias(JNIEnv* env, jobject paint, jboolean aa) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setAntiAlias(aa);
+ static void setAntiAlias(JNIEnv* env, jobject, jlong paintHandle, jboolean aa) {
+ reinterpret_cast<Paint*>(paintHandle)->setAntiAlias(aa);
}
- static void setLinearText(JNIEnv* env, jobject paint, jboolean linearText) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setLinearText(linearText);
+ static void setLinearText(JNIEnv* env, jobject, jlong paintHandle, jboolean linearText) {
+ reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
}
- static void setSubpixelText(JNIEnv* env, jobject paint, jboolean subpixelText) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setSubpixelText(subpixelText);
+ static void setSubpixelText(JNIEnv* env, jobject, jlong paintHandle, jboolean subpixelText) {
+ reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
}
- static void setUnderlineText(JNIEnv* env, jobject paint, jboolean underlineText) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setUnderlineText(underlineText);
+ static void setUnderlineText(JNIEnv* env, jobject, jlong paintHandle, jboolean underlineText) {
+ reinterpret_cast<Paint*>(paintHandle)->setUnderlineText(underlineText);
}
- static void setStrikeThruText(JNIEnv* env, jobject paint, jboolean strikeThruText) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setStrikeThruText(strikeThruText);
+ static void setStrikeThruText(JNIEnv* env, jobject, jlong paintHandle, jboolean strikeThruText) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrikeThruText(strikeThruText);
}
- static void setFakeBoldText(JNIEnv* env, jobject paint, jboolean fakeBoldText) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setFakeBoldText(fakeBoldText);
+ static void setFakeBoldText(JNIEnv* env, jobject, jlong paintHandle, jboolean fakeBoldText) {
+ reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
}
- static void setFilterBitmap(JNIEnv* env, jobject paint, jboolean filterBitmap) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setFilterQuality(
+ static void setFilterBitmap(JNIEnv* env, jobject, jlong paintHandle, jboolean filterBitmap) {
+ reinterpret_cast<Paint*>(paintHandle)->setFilterQuality(
filterBitmap ? kLow_SkFilterQuality : kNone_SkFilterQuality);
}
- static void setDither(JNIEnv* env, jobject paint, jboolean dither) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setDither(dither);
+ static void setDither(JNIEnv* env, jobject, jlong paintHandle, jboolean dither) {
+ reinterpret_cast<Paint*>(paintHandle)->setDither(dither);
}
static jint getStyle(JNIEnv* env, jobject clazz,jlong objHandle) {
@@ -239,48 +203,40 @@
obj->setStyle(style);
}
- static jint getColor(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jint getColor(JNIEnv* env, jobject, jlong paintHandle) {
int color;
- color = getNativePaint(env, paint)->getColor();
+ color = reinterpret_cast<Paint*>(paintHandle)->getColor();
return static_cast<jint>(color);
}
- static jint getAlpha(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jint getAlpha(JNIEnv* env, jobject, jlong paintHandle) {
int alpha;
- alpha = getNativePaint(env, paint)->getAlpha();
+ alpha = reinterpret_cast<Paint*>(paintHandle)->getAlpha();
return static_cast<jint>(alpha);
}
- static void setColor(JNIEnv* env, jobject paint, jint color) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setColor(color);
+ static void setColor(JNIEnv* env, jobject, jlong paintHandle, jint color) {
+ reinterpret_cast<Paint*>(paintHandle)->setColor(color);
}
- static void setAlpha(JNIEnv* env, jobject paint, jint a) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setAlpha(a);
+ static void setAlpha(JNIEnv* env, jobject, jlong paintHandle, jint a) {
+ reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
}
- static jfloat getStrokeWidth(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return SkScalarToFloat(getNativePaint(env, paint)->getStrokeWidth());
+ static jfloat getStrokeWidth(JNIEnv* env, jobject, jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeWidth());
}
- static void setStrokeWidth(JNIEnv* env, jobject paint, jfloat width) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setStrokeWidth(width);
+ static void setStrokeWidth(JNIEnv* env, jobject, jlong paintHandle, jfloat width) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrokeWidth(width);
}
- static jfloat getStrokeMiter(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return SkScalarToFloat(getNativePaint(env, paint)->getStrokeMiter());
+ static jfloat getStrokeMiter(JNIEnv* env, jobject, jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeMiter());
}
- static void setStrokeMiter(JNIEnv* env, jobject paint, jfloat miter) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setStrokeMiter(miter);
+ static void setStrokeMiter(JNIEnv* env, jobject, jlong paintHandle, jfloat miter) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrokeMiter(miter);
}
static jint getStrokeCap(JNIEnv* env, jobject clazz, jlong objHandle) {
@@ -417,46 +373,38 @@
obj->setTextLocale(sSingleEntryLocaleCache.languageTag);
}
- static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- Paint* obj = getNativePaint(env, paint);
+ static jboolean isElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(paintHandle);
return obj->getFontVariant() == VARIANT_ELEGANT;
}
- static void setElegantTextHeight(JNIEnv* env, jobject paint, jboolean aa) {
- NPE_CHECK_RETURN_VOID(env, paint);
- Paint* obj = getNativePaint(env, paint);
+ static void setElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle, jboolean aa) {
+ Paint* obj = reinterpret_cast<Paint*>(paintHandle);
obj->setFontVariant(aa ? VARIANT_ELEGANT : VARIANT_DEFAULT);
}
- static jfloat getTextSize(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return SkScalarToFloat(getNativePaint(env, paint)->getTextSize());
+ static jfloat getTextSize(JNIEnv* env, jobject, jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
}
- static void setTextSize(JNIEnv* env, jobject paint, jfloat textSize) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setTextSize(textSize);
+ static void setTextSize(JNIEnv* env, jobject, jlong paintHandle, jfloat textSize) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
}
- static jfloat getTextScaleX(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return SkScalarToFloat(getNativePaint(env, paint)->getTextScaleX());
+ static jfloat getTextScaleX(JNIEnv* env, jobject, jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
}
- static void setTextScaleX(JNIEnv* env, jobject paint, jfloat scaleX) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setTextScaleX(scaleX);
+ static void setTextScaleX(JNIEnv* env, jobject, jlong paintHandle, jfloat scaleX) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
}
- static jfloat getTextSkewX(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- return SkScalarToFloat(getNativePaint(env, paint)->getTextSkewX());
+ static jfloat getTextSkewX(JNIEnv* env, jobject, jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
}
- static void setTextSkewX(JNIEnv* env, jobject paint, jfloat skewX) {
- NPE_CHECK_RETURN_VOID(env, paint);
- getNativePaint(env, paint)->setTextSkewX(skewX);
+ static void setTextSkewX(JNIEnv* env, jobject, jlong paintHandle, jfloat skewX) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
}
static jfloat getLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle) {
@@ -489,14 +437,15 @@
paint->setHyphenEdit((uint32_t)hyphen);
}
- static SkScalar getMetricsInternal(JNIEnv* env, jobject jpaint, Paint::FontMetrics *metrics) {
+ static SkScalar getMetricsInternal(jlong paintHandle, jlong typefaceHandle,
+ Paint::FontMetrics *metrics) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
const int kElegantAscent = 1900;
const int kElegantDescent = -500;
const int kElegantLeading = 0;
- Paint* paint = getNativePaint(env, jpaint);
- TypefaceImpl* typeface = getNativeTypeface(env, jpaint);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
typeface = TypefaceImpl_resolveDefault(typeface);
FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = paint->getTextSkewX();
@@ -520,24 +469,22 @@
return spacing;
}
- static jfloat ascent(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jfloat ascent(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle) {
Paint::FontMetrics metrics;
- getMetricsInternal(env, paint, &metrics);
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
return SkScalarToFloat(metrics.fAscent);
}
- static jfloat descent(JNIEnv* env, jobject paint) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jfloat descent(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle) {
Paint::FontMetrics metrics;
- getMetricsInternal(env, paint, &metrics);
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
return SkScalarToFloat(metrics.fDescent);
}
- static jfloat getFontMetrics(JNIEnv* env, jobject paint, jobject metricsObj) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle,
+ jlong typefaceHandle, jobject metricsObj) {
Paint::FontMetrics metrics;
- SkScalar spacing = getMetricsInternal(env, paint, &metrics);
+ SkScalar spacing = getMetricsInternal(paintHandle, typefaceHandle, &metrics);
if (metricsObj) {
SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
@@ -550,11 +497,11 @@
return SkScalarToFloat(spacing);
}
- static jint getFontMetricsInt(JNIEnv* env, jobject paint, jobject metricsObj) {
- NPE_CHECK_RETURN_ZERO(env, paint);
+ static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle,
+ jlong typefaceHandle, jobject metricsObj) {
Paint::FontMetrics metrics;
- getMetricsInternal(env, paint, &metrics);
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
int ascent = SkScalarRoundToInt(metrics.fAscent);
int descent = SkScalarRoundToInt(metrics.fDescent);
int leading = SkScalarRoundToInt(metrics.fLeading);
@@ -573,7 +520,6 @@
static jfloat doTextAdvances(JNIEnv *env, Paint *paint, TypefaceImpl* typeface,
const jchar *text, jint start, jint count, jint contextCount, jint bidiFlags,
jfloatArray advances, jint advancesIndex) {
- NPE_CHECK_RETURN_ZERO(env, paint);
NPE_CHECK_RETURN_ZERO(env, text);
if ((start | count | contextCount | advancesIndex) < 0 || contextCount < count) {
@@ -841,7 +787,7 @@
static void getStringBounds(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle,
jstring text, jint start, jint end, jint bidiFlags, jobject bounds) {
- const Paint* paint = reinterpret_cast<Paint*>(paintHandle);;
+ const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
const jchar* textArray = env->GetStringChars(text, NULL);
doTextBounds(env, textArray + start, end - start, bounds, *paint, typeface, bidiFlags);
@@ -961,97 +907,97 @@
return result;
}
-};
+}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
- {"finalizer", "(J)V", (void*) PaintGlue::finalizer},
- {"native_init","()J", (void*) PaintGlue::init},
- {"native_initWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
+ {"nFinalizer", "(J)V", (void*) PaintGlue::finalizer},
+ {"nInit","()J", (void*) PaintGlue::init},
+ {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
- {"native_reset","!(J)V", (void*) PaintGlue::reset},
- {"native_set","!(JJ)V", (void*) PaintGlue::assign},
- {"getFlags","!()I", (void*) PaintGlue::getFlags},
- {"setFlags","!(I)V", (void*) PaintGlue::setFlags},
- {"getHinting","!()I", (void*) PaintGlue::getHinting},
- {"setHinting","!(I)V", (void*) PaintGlue::setHinting},
- {"setAntiAlias","!(Z)V", (void*) PaintGlue::setAntiAlias},
- {"setSubpixelText","!(Z)V", (void*) PaintGlue::setSubpixelText},
- {"setLinearText","!(Z)V", (void*) PaintGlue::setLinearText},
- {"setUnderlineText","!(Z)V", (void*) PaintGlue::setUnderlineText},
- {"setStrikeThruText","!(Z)V", (void*) PaintGlue::setStrikeThruText},
- {"setFakeBoldText","!(Z)V", (void*) PaintGlue::setFakeBoldText},
- {"setFilterBitmap","!(Z)V", (void*) PaintGlue::setFilterBitmap},
- {"setDither","!(Z)V", (void*) PaintGlue::setDither},
- {"native_getStyle","!(J)I", (void*) PaintGlue::getStyle},
- {"native_setStyle","!(JI)V", (void*) PaintGlue::setStyle},
- {"getColor","!()I", (void*) PaintGlue::getColor},
- {"setColor","!(I)V", (void*) PaintGlue::setColor},
- {"getAlpha","!()I", (void*) PaintGlue::getAlpha},
- {"setAlpha","!(I)V", (void*) PaintGlue::setAlpha},
- {"getStrokeWidth","!()F", (void*) PaintGlue::getStrokeWidth},
- {"setStrokeWidth","!(F)V", (void*) PaintGlue::setStrokeWidth},
- {"getStrokeMiter","!()F", (void*) PaintGlue::getStrokeMiter},
- {"setStrokeMiter","!(F)V", (void*) PaintGlue::setStrokeMiter},
- {"native_getStrokeCap","!(J)I", (void*) PaintGlue::getStrokeCap},
- {"native_setStrokeCap","!(JI)V", (void*) PaintGlue::setStrokeCap},
- {"native_getStrokeJoin","!(J)I", (void*) PaintGlue::getStrokeJoin},
- {"native_setStrokeJoin","!(JI)V", (void*) PaintGlue::setStrokeJoin},
- {"native_getFillPath","!(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"native_setShader","!(JJ)J", (void*) PaintGlue::setShader},
- {"native_setColorFilter","!(JJ)J", (void*) PaintGlue::setColorFilter},
- {"native_setXfermode","!(JJ)J", (void*) PaintGlue::setXfermode},
- {"native_setPathEffect","!(JJ)J", (void*) PaintGlue::setPathEffect},
- {"native_setMaskFilter","!(JJ)J", (void*) PaintGlue::setMaskFilter},
- {"native_setTypeface","!(JJ)J", (void*) PaintGlue::setTypeface},
- {"native_setRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
- {"native_getTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
- {"native_setTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
- {"native_setTextLocale","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocale},
- {"isElegantTextHeight","!()Z", (void*) PaintGlue::isElegantTextHeight},
- {"setElegantTextHeight","!(Z)V", (void*) PaintGlue::setElegantTextHeight},
- {"getTextSize","!()F", (void*) PaintGlue::getTextSize},
- {"setTextSize","!(F)V", (void*) PaintGlue::setTextSize},
- {"getTextScaleX","!()F", (void*) PaintGlue::getTextScaleX},
- {"setTextScaleX","!(F)V", (void*) PaintGlue::setTextScaleX},
- {"getTextSkewX","!()F", (void*) PaintGlue::getTextSkewX},
- {"setTextSkewX","!(F)V", (void*) PaintGlue::setTextSkewX},
- {"native_getLetterSpacing","!(J)F", (void*) PaintGlue::getLetterSpacing},
- {"native_setLetterSpacing","!(JF)V", (void*) PaintGlue::setLetterSpacing},
- {"native_setFontFeatureSettings","(JLjava/lang/String;)V",
+ {"nReset","!(J)V", (void*) PaintGlue::reset},
+ {"nSet","!(JJ)V", (void*) PaintGlue::assign},
+ {"nGetFlags","!(J)I", (void*) PaintGlue::getFlags},
+ {"nSetFlags","!(JI)V", (void*) PaintGlue::setFlags},
+ {"nGetHinting","!(J)I", (void*) PaintGlue::getHinting},
+ {"nSetHinting","!(JI)V", (void*) PaintGlue::setHinting},
+ {"nSetAntiAlias","!(JZ)V", (void*) PaintGlue::setAntiAlias},
+ {"nSetSubpixelText","!(JZ)V", (void*) PaintGlue::setSubpixelText},
+ {"nSetLinearText","!(JZ)V", (void*) PaintGlue::setLinearText},
+ {"nSetUnderlineText","!(JZ)V", (void*) PaintGlue::setUnderlineText},
+ {"nSetStrikeThruText","!(JZ)V", (void*) PaintGlue::setStrikeThruText},
+ {"nSetFakeBoldText","!(JZ)V", (void*) PaintGlue::setFakeBoldText},
+ {"nSetFilterBitmap","!(JZ)V", (void*) PaintGlue::setFilterBitmap},
+ {"nSetDither","!(JZ)V", (void*) PaintGlue::setDither},
+ {"nGetStyle","!(J)I", (void*) PaintGlue::getStyle},
+ {"nSetStyle","!(JI)V", (void*) PaintGlue::setStyle},
+ {"nGetColor","!(J)I", (void*) PaintGlue::getColor},
+ {"nSetColor","!(JI)V", (void*) PaintGlue::setColor},
+ {"nGetAlpha","!(J)I", (void*) PaintGlue::getAlpha},
+ {"nSetAlpha","!(JI)V", (void*) PaintGlue::setAlpha},
+ {"nGetStrokeWidth","!(J)F", (void*) PaintGlue::getStrokeWidth},
+ {"nSetStrokeWidth","!(JF)V", (void*) PaintGlue::setStrokeWidth},
+ {"nGetStrokeMiter","!(J)F", (void*) PaintGlue::getStrokeMiter},
+ {"nSetStrokeMiter","!(JF)V", (void*) PaintGlue::setStrokeMiter},
+ {"nGetStrokeCap","!(J)I", (void*) PaintGlue::getStrokeCap},
+ {"nSetStrokeCap","!(JI)V", (void*) PaintGlue::setStrokeCap},
+ {"nGetStrokeJoin","!(J)I", (void*) PaintGlue::getStrokeJoin},
+ {"nSetStrokeJoin","!(JI)V", (void*) PaintGlue::setStrokeJoin},
+ {"nGetFillPath","!(JJJ)Z", (void*) PaintGlue::getFillPath},
+ {"nSetShader","!(JJ)J", (void*) PaintGlue::setShader},
+ {"nSetColorFilter","!(JJ)J", (void*) PaintGlue::setColorFilter},
+ {"nSetXfermode","!(JJ)J", (void*) PaintGlue::setXfermode},
+ {"nSetPathEffect","!(JJ)J", (void*) PaintGlue::setPathEffect},
+ {"nSetMaskFilter","!(JJ)J", (void*) PaintGlue::setMaskFilter},
+ {"nSetTypeface","!(JJ)J", (void*) PaintGlue::setTypeface},
+ {"nSetRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
+ {"nGetTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
+ {"nSetTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
+ {"nSetTextLocale","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocale},
+ {"nIsElegantTextHeight","!(J)Z", (void*) PaintGlue::isElegantTextHeight},
+ {"nSetElegantTextHeight","!(JZ)V", (void*) PaintGlue::setElegantTextHeight},
+ {"nGetTextSize","!(J)F", (void*) PaintGlue::getTextSize},
+ {"nSetTextSize","!(JF)V", (void*) PaintGlue::setTextSize},
+ {"nGetTextScaleX","!(J)F", (void*) PaintGlue::getTextScaleX},
+ {"nSetTextScaleX","!(JF)V", (void*) PaintGlue::setTextScaleX},
+ {"nGetTextSkewX","!(J)F", (void*) PaintGlue::getTextSkewX},
+ {"nSetTextSkewX","!(JF)V", (void*) PaintGlue::setTextSkewX},
+ {"nGetLetterSpacing","!(J)F", (void*) PaintGlue::getLetterSpacing},
+ {"nSetLetterSpacing","!(JF)V", (void*) PaintGlue::setLetterSpacing},
+ {"nSetFontFeatureSettings","(JLjava/lang/String;)V",
(void*) PaintGlue::setFontFeatureSettings},
- {"native_getHyphenEdit", "!(J)I", (void*) PaintGlue::getHyphenEdit},
- {"native_setHyphenEdit", "!(JI)V", (void*) PaintGlue::setHyphenEdit},
- {"ascent","!()F", (void*) PaintGlue::ascent},
- {"descent","!()F", (void*) PaintGlue::descent},
+ {"nGetHyphenEdit", "!(J)I", (void*) PaintGlue::getHyphenEdit},
+ {"nSetHyphenEdit", "!(JI)V", (void*) PaintGlue::setHyphenEdit},
+ {"nAscent","!(JJ)F", (void*) PaintGlue::ascent},
+ {"nDescent","!(JJ)F", (void*) PaintGlue::descent},
- {"getFontMetrics", "!(Landroid/graphics/Paint$FontMetrics;)F",
+ {"nGetFontMetrics", "!(JJLandroid/graphics/Paint$FontMetrics;)F",
(void*)PaintGlue::getFontMetrics},
- {"getFontMetricsInt", "!(Landroid/graphics/Paint$FontMetricsInt;)I",
+ {"nGetFontMetricsInt", "!(JJLandroid/graphics/Paint$FontMetricsInt;)I",
(void*)PaintGlue::getFontMetricsInt},
- {"native_breakText","(JJ[CIIFI[F)I", (void*) PaintGlue::breakTextC},
- {"native_breakText","(JJLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
- {"native_getTextAdvances","(JJ[CIIIII[FI)F",
+ {"nBreakText","(JJ[CIIFI[F)I", (void*) PaintGlue::breakTextC},
+ {"nBreakText","(JJLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
+ {"nGetTextAdvances","(JJ[CIIIII[FI)F",
(void*) PaintGlue::getTextAdvances___CIIIII_FI},
- {"native_getTextAdvances","(JJLjava/lang/String;IIIII[FI)F",
+ {"nGetTextAdvances","(JJLjava/lang/String;IIIII[FI)F",
(void*) PaintGlue::getTextAdvances__StringIIIII_FI},
- {"native_getTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C},
- {"native_getTextRunCursor", "(JLjava/lang/String;IIIII)I",
+ {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C},
+ {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I",
(void*) PaintGlue::getTextRunCursor__String},
- {"native_getTextPath", "(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
- {"native_getTextPath", "(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
- {"nativeGetStringBounds", "(JJLjava/lang/String;IIILandroid/graphics/Rect;)V",
+ {"nGetTextPath", "(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
+ {"nGetTextPath", "(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
+ {"nGetStringBounds", "(JJLjava/lang/String;IIILandroid/graphics/Rect;)V",
(void*) PaintGlue::getStringBounds },
- {"nativeGetCharArrayBounds", "(JJ[CIIILandroid/graphics/Rect;)V",
+ {"nGetCharArrayBounds", "(JJ[CIIILandroid/graphics/Rect;)V",
(void*) PaintGlue::getCharArrayBounds },
- {"native_hasGlyph", "(JJILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
- {"native_getRunAdvance", "(JJ[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
- {"native_getOffsetForAdvance", "(JJ[CIIIIZF)I",
+ {"nHasGlyph", "(JJILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
+ {"nGetRunAdvance", "(JJ[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
+ {"nGetOffsetForAdvance", "(JJ[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
- {"native_setShadowLayer", "!(JFFFI)V", (void*)PaintGlue::setShadowLayer},
- {"native_hasShadowLayer", "!(J)Z", (void*)PaintGlue::hasShadowLayer}
+ {"nSetShadowLayer", "!(JFFFI)V", (void*)PaintGlue::setShadowLayer},
+ {"nHasShadowLayer", "!(J)Z", (void*)PaintGlue::hasShadowLayer}
};
int register_android_graphics_Paint(JNIEnv* env) {
@@ -1073,10 +1019,6 @@
gFontMetricsInt_fieldID.bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
gFontMetricsInt_fieldID.leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
- gPaint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Paint"));
- gPaint_nativeInstanceID = GetFieldIDOrDie(env, gPaint_class, "mNativePaint", "J");
- gPaint_nativeTypefaceID = GetFieldIDOrDie(env, gPaint_class, "mNativeTypeface", "J");
-
return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
}
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index a151e00..83f76ea 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -117,9 +117,17 @@
b->finish();
}
-static jlong nLoadHyphenator(JNIEnv* env, jclass, jstring patternData) {
- ScopedStringChars str(env, patternData);
- Hyphenator* hyphenator = Hyphenator::load(str.get(), str.size());
+static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset) {
+ const uint8_t* bytebuf = nullptr;
+ if (buffer != nullptr) {
+ void* rawbuf = env->GetDirectBufferAddress(buffer);
+ if (rawbuf != nullptr) {
+ bytebuf = reinterpret_cast<const uint8_t*>(rawbuf) + offset;
+ } else {
+ ALOGE("failed to get direct buffer address");
+ }
+ }
+ Hyphenator* hyphenator = Hyphenator::loadBinary(bytebuf);
return reinterpret_cast<jlong>(hyphenator);
}
@@ -177,7 +185,7 @@
{"nNewBuilder", "()J", (void*) nNewBuilder},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
{"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
- {"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator},
+ {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;I)J", (void*) nLoadHyphenator},
{"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale},
{"nSetupParagraph", "(J[CIFIF[IIII)V", (void*) nSetupParagraph},
{"nSetIndents", "(J[I)V", (void*) nSetIndents},
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index c79f833..17eb876 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -461,10 +461,10 @@
proxy->drawRenderNode(renderNode);
}
-static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env,
+static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env,
jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setContentOverdrawProtectionBounds(left, top, right, bottom);
+ proxy->setContentDrawBounds(left, top, right, bottom);
}
// ----------------------------------------------------------------------------
@@ -522,8 +522,7 @@
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
- { "nSetContentOverdrawProtectionBounds", "(JIIII)V",
- (void*)android_view_ThreadedRenderer_setContentOverdrawProtectionBounds},
+ { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 58de87a..11b4a9e 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -430,7 +430,7 @@
* @param flags initial flag bits, as if they were passed via setFlags().
*/
public Paint(int flags) {
- mNativePaint = native_init();
+ mNativePaint = nInit();
setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
// TODO: Turning off hinting has undesirable side effects, we need to
// revisit hinting once we add support for subpixel positioning
@@ -448,13 +448,14 @@
* new paint.
*/
public Paint(Paint paint) {
- mNativePaint = native_initWithPaint(paint.getNativeInstance());
+ mNativePaint = nInitWithPaint(paint.getNativeInstance());
setClassVariablesFrom(paint);
}
/** Restores the paint to its default settings. */
public void reset() {
- native_reset(mNativePaint);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nReset(mNativePaint);
setFlags(HIDDEN_DEFAULT_PAINT_FLAGS);
// TODO: Turning off hinting has undesirable side effects, we need to
@@ -488,9 +489,11 @@
* methods on this.
*/
public void set(Paint src) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ if (src.mNativePaint == 0) throw new NullPointerException("Source is already finalized!");
if (this != src) {
// copy over the native settings
- native_set(mNativePaint, src.mNativePaint);
+ nSet(mNativePaint, src.mNativePaint);
setClassVariablesFrom(src);
}
}
@@ -538,10 +541,11 @@
* @hide
*/
public long getNativeInstance() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
if (newNativeShader != mNativeShader) {
mNativeShader = newNativeShader;
- native_setShader(mNativePaint, mNativeShader);
+ nSetShader(mNativePaint, mNativeShader);
}
return mNativePaint;
}
@@ -574,26 +578,46 @@
*
* @return the paint's flags (see enums ending in _Flag for bit masks)
*/
- public native int getFlags();
+ public int getFlags() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetFlags(mNativePaint);
+ }
+
+ private native int nGetFlags(long paintPtr);
/**
* Set the paint's flags. Use the Flag enum to specific flag values.
*
* @param flags The new flag bits for the paint
*/
- public native void setFlags(int flags);
+ public void setFlags(int flags) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetFlags(mNativePaint, flags);
+ }
+
+ private native void nSetFlags(long paintPtr, int flags);
/**
* Return the paint's hinting mode. Returns either
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
*/
- public native int getHinting();
+ public int getHinting() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetHinting(mNativePaint);
+ }
+
+ private native int nGetHinting(long paintPtr);
/**
* Set the paint's hinting mode. May be either
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
*/
- public native void setHinting(int mode);
+ public void setHinting(int mode) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetHinting(mNativePaint, mode);
+ }
+
+ private native void nSetHinting(long paintPtr, int mode);
/**
* Helper for getFlags(), returning true if ANTI_ALIAS_FLAG bit is set
@@ -615,7 +639,12 @@
*
* @param aa true to set the antialias bit in the flags, false to clear it
*/
- public native void setAntiAlias(boolean aa);
+ public void setAntiAlias(boolean aa) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetAntiAlias(mNativePaint, aa);
+ }
+
+ private native void nSetAntiAlias(long paintPtr, boolean aa);
/**
* Helper for getFlags(), returning true if DITHER_FLAG bit is set
@@ -641,7 +670,12 @@
*
* @param dither true to set the dithering bit in flags, false to clear it
*/
- public native void setDither(boolean dither);
+ public void setDither(boolean dither) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetDither(mNativePaint, dither);
+ }
+
+ private native void nSetDither(long paintPtr, boolean dither);
/**
* Helper for getFlags(), returning true if LINEAR_TEXT_FLAG bit is set
@@ -658,7 +692,12 @@
* @param linearText true to set the linearText bit in the paint's flags,
* false to clear it.
*/
- public native void setLinearText(boolean linearText);
+ public void setLinearText(boolean linearText) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetLinearText(mNativePaint, linearText);
+ }
+
+ private native void nSetLinearText(long paintPtr, boolean linearText);
/**
* Helper for getFlags(), returning true if SUBPIXEL_TEXT_FLAG bit is set
@@ -675,7 +714,12 @@
* @param subpixelText true to set the subpixelText bit in the paint's
* flags, false to clear it.
*/
- public native void setSubpixelText(boolean subpixelText);
+ public void setSubpixelText(boolean subpixelText) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetSubpixelText(mNativePaint, subpixelText);
+ }
+
+ private native void nSetSubpixelText(long paintPtr, boolean subpixelText);
/**
* Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set
@@ -692,7 +736,12 @@
* @param underlineText true to set the underlineText bit in the paint's
* flags, false to clear it.
*/
- public native void setUnderlineText(boolean underlineText);
+ public void setUnderlineText(boolean underlineText) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetUnderlineText(mNativePaint, underlineText);
+ }
+
+ private native void nSetUnderlineText(long paintPtr, boolean underlineText);
/**
* Helper for getFlags(), returning true if STRIKE_THRU_TEXT_FLAG bit is set
@@ -709,7 +758,12 @@
* @param strikeThruText true to set the strikeThruText bit in the paint's
* flags, false to clear it.
*/
- public native void setStrikeThruText(boolean strikeThruText);
+ public void setStrikeThruText(boolean strikeThruText) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStrikeThruText(mNativePaint, strikeThruText);
+ }
+
+ private native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
/**
* Helper for getFlags(), returning true if FAKE_BOLD_TEXT_FLAG bit is set
@@ -726,7 +780,12 @@
* @param fakeBoldText true to set the fakeBoldText bit in the paint's
* flags, false to clear it.
*/
- public native void setFakeBoldText(boolean fakeBoldText);
+ public void setFakeBoldText(boolean fakeBoldText) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetFakeBoldText(mNativePaint, fakeBoldText);
+ }
+
+ private native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);
/**
* Whether or not the bitmap filter is activated.
@@ -749,7 +808,12 @@
* @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's
* flags, false to clear it.
*/
- public native void setFilterBitmap(boolean filter);
+ public void setFilterBitmap(boolean filter) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetFilterBitmap(mNativePaint, filter);
+ }
+
+ private native void nSetFilterBitmap(long paintPtr, boolean filter);
/**
* Return the paint's style, used for controlling how primitives'
@@ -759,7 +823,8 @@
* @return the paint's style setting (Fill, Stroke, StrokeAndFill)
*/
public Style getStyle() {
- return sStyleArray[native_getStyle(mNativePaint)];
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return sStyleArray[nGetStyle(mNativePaint)];
}
/**
@@ -770,7 +835,8 @@
* @param style The new style to set in the paint
*/
public void setStyle(Style style) {
- native_setStyle(mNativePaint, style.nativeInt);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStyle(mNativePaint, style.nativeInt);
}
/**
@@ -782,7 +848,12 @@
* @return the paint's color (and alpha).
*/
@ColorInt
- public native int getColor();
+ public int getColor() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetColor(mNativePaint);
+ }
+
+ private native int nGetColor(long paintPtr);
/**
* Set the paint's color. Note that the color is an int containing alpha
@@ -792,7 +863,12 @@
*
* @param color The new color (including alpha) to set in the paint.
*/
- public native void setColor(@ColorInt int color);
+ public void setColor(@ColorInt int color) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetColor(mNativePaint, color);
+ }
+
+ private native void nSetColor(long paintPtr, @ColorInt int color);
/**
* Helper to getColor() that just returns the color's alpha value. This is
@@ -801,7 +877,12 @@
*
* @return the alpha component of the paint's color.
*/
- public native int getAlpha();
+ public int getAlpha() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetAlpha(mNativePaint);
+ }
+
+ private native int nGetAlpha(long paintPtr);
/**
* Helper to setColor(), that only assigns the color's alpha value,
@@ -810,7 +891,12 @@
*
* @param a set the alpha component [0..255] of the paint's color.
*/
- public native void setAlpha(int a);
+ public void setAlpha(int a) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetAlpha(mNativePaint, a);
+ }
+
+ private native void nSetAlpha(long paintPtr, int a);
/**
* Helper to setColor(), that takes a,r,g,b and constructs the color int
@@ -833,7 +919,12 @@
* @return the paint's stroke width, used whenever the paint's style is
* Stroke or StrokeAndFill.
*/
- public native float getStrokeWidth();
+ public float getStrokeWidth() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetStrokeWidth(mNativePaint);
+ }
+
+ private native float nGetStrokeWidth(long paintPtr);
/**
* Set the width for stroking.
@@ -843,7 +934,12 @@
* @param width set the paint's stroke width, used whenever the paint's
* style is Stroke or StrokeAndFill.
*/
- public native void setStrokeWidth(float width);
+ public void setStrokeWidth(float width) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStrokeWidth(mNativePaint, width);
+ }
+
+ private native void nSetStrokeWidth(long paintPtr, float width);
/**
* Return the paint's stroke miter value. Used to control the behavior
@@ -852,7 +948,12 @@
* @return the paint's miter limit, used whenever the paint's style is
* Stroke or StrokeAndFill.
*/
- public native float getStrokeMiter();
+ public float getStrokeMiter() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetStrokeMiter(mNativePaint);
+ }
+
+ private native float nGetStrokeMiter(long paintPtr);
/**
* Set the paint's stroke miter value. This is used to control the behavior
@@ -861,7 +962,12 @@
* @param miter set the miter limit on the paint, used whenever the paint's
* style is Stroke or StrokeAndFill.
*/
- public native void setStrokeMiter(float miter);
+ public void setStrokeMiter(float miter) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStrokeMiter(mNativePaint, miter);
+ }
+
+ private native void nSetStrokeMiter(long paintPtr, float miter);
/**
* Return the paint's Cap, controlling how the start and end of stroked
@@ -871,7 +977,8 @@
* style is Stroke or StrokeAndFill.
*/
public Cap getStrokeCap() {
- return sCapArray[native_getStrokeCap(mNativePaint)];
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return sCapArray[nGetStrokeCap(mNativePaint)];
}
/**
@@ -881,7 +988,8 @@
* style is Stroke or StrokeAndFill.
*/
public void setStrokeCap(Cap cap) {
- native_setStrokeCap(mNativePaint, cap.nativeInt);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStrokeCap(mNativePaint, cap.nativeInt);
}
/**
@@ -890,7 +998,8 @@
* @return the paint's Join.
*/
public Join getStrokeJoin() {
- return sJoinArray[native_getStrokeJoin(mNativePaint)];
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return sJoinArray[nGetStrokeJoin(mNativePaint)];
}
/**
@@ -900,7 +1009,8 @@
* Stroke or StrokeAndFill.
*/
public void setStrokeJoin(Join join) {
- native_setStrokeJoin(mNativePaint, join.nativeInt);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetStrokeJoin(mNativePaint, join.nativeInt);
}
/**
@@ -915,7 +1025,8 @@
* drawn with a hairline (width == 0)
*/
public boolean getFillPath(Path src, Path dst) {
- return native_getFillPath(mNativePaint, src.ni(), dst.ni());
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetFillPath(mNativePaint, src.ni(), dst.ni());
}
/**
@@ -958,10 +1069,11 @@
* @return filter
*/
public ColorFilter setColorFilter(ColorFilter filter) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long filterNative = 0;
if (filter != null)
filterNative = filter.native_instance;
- native_setColorFilter(mNativePaint, filterNative);
+ nSetColorFilter(mNativePaint, filterNative);
mColorFilter = filter;
return filter;
}
@@ -985,10 +1097,11 @@
* @return xfermode
*/
public Xfermode setXfermode(Xfermode xfermode) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long xfermodeNative = 0;
if (xfermode != null)
xfermodeNative = xfermode.native_instance;
- native_setXfermode(mNativePaint, xfermodeNative);
+ nSetXfermode(mNativePaint, xfermodeNative);
mXfermode = xfermode;
return xfermode;
}
@@ -1012,11 +1125,12 @@
* @return effect
*/
public PathEffect setPathEffect(PathEffect effect) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long effectNative = 0;
if (effect != null) {
effectNative = effect.native_instance;
}
- native_setPathEffect(mNativePaint, effectNative);
+ nSetPathEffect(mNativePaint, effectNative);
mPathEffect = effect;
return effect;
}
@@ -1041,11 +1155,12 @@
* @return maskfilter
*/
public MaskFilter setMaskFilter(MaskFilter maskfilter) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long maskfilterNative = 0;
if (maskfilter != null) {
maskfilterNative = maskfilter.native_instance;
}
- native_setMaskFilter(mNativePaint, maskfilterNative);
+ nSetMaskFilter(mNativePaint, maskfilterNative);
mMaskFilter = maskfilter;
return maskfilter;
}
@@ -1072,11 +1187,12 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long typefaceNative = 0;
if (typeface != null) {
typefaceNative = typeface.native_instance;
}
- native_setTypeface(mNativePaint, typefaceNative);
+ nSetTypeface(mNativePaint, typefaceNative);
mTypeface = typeface;
mNativeTypeface = typefaceNative;
return typeface;
@@ -1110,11 +1226,12 @@
*/
@Deprecated
public Rasterizer setRasterizer(Rasterizer rasterizer) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long rasterizerNative = 0;
if (rasterizer != null) {
rasterizerNative = rasterizer.native_instance;
}
- native_setRasterizer(mNativePaint, rasterizerNative);
+ nSetRasterizer(mNativePaint, rasterizerNative);
mRasterizer = rasterizer;
return rasterizer;
}
@@ -1132,7 +1249,8 @@
* opaque, or the alpha from the shadow color if not.
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
- native_setShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
}
/**
@@ -1149,7 +1267,8 @@
* @hide
*/
public boolean hasShadowLayer() {
- return native_hasShadowLayer(mNativePaint);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nHasShadowLayer(mNativePaint);
}
/**
@@ -1161,7 +1280,8 @@
* @return the paint's Align value for drawing text.
*/
public Align getTextAlign() {
- return sAlignArray[native_getTextAlign(mNativePaint)];
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return sAlignArray[nGetTextAlign(mNativePaint)];
}
/**
@@ -1173,7 +1293,8 @@
* @param align set the paint's Align value for drawing text.
*/
public void setTextAlign(Align align) {
- native_setTextAlign(mNativePaint, align.nativeInt);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetTextAlign(mNativePaint, align.nativeInt);
}
/**
@@ -1206,6 +1327,7 @@
* @param locale the paint's locale value for drawing text, must not be null.
*/
public void setTextLocale(@NonNull Locale locale) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (locale == null) {
throw new IllegalArgumentException("locale cannot be null");
}
@@ -1213,7 +1335,7 @@
return;
}
mLocales = new LocaleList(locale);
- native_setTextLocale(mNativePaint, locale.toString());
+ nSetTextLocale(mNativePaint, locale.toString());
}
/**
@@ -1244,13 +1366,14 @@
* @param locales the paint's locale list for drawing text, must not be null or empty.
*/
public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (locales == null || locales.isEmpty()) {
throw new IllegalArgumentException("locales cannot be null or empty");
}
if (locales.equals(mLocales)) return;
mLocales = locales;
// TODO: Pass the whole LocaleList to native code
- native_setTextLocale(mNativePaint, locales.getPrimary().toString());
+ nSetTextLocale(mNativePaint, locales.getPrimary().toString());
}
/**
@@ -1258,7 +1381,12 @@
*
* @return true if elegant metrics are enabled for text drawing.
*/
- public native boolean isElegantTextHeight();
+ public boolean isElegantTextHeight() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nIsElegantTextHeight(mNativePaint);
+ }
+
+ private native boolean nIsElegantTextHeight(long paintPtr);
/**
* Set the paint's elegant height metrics flag. This setting selects font
@@ -1267,21 +1395,36 @@
*
* @param elegant set the paint's elegant metrics flag for drawing text.
*/
- public native void setElegantTextHeight(boolean elegant);
+ public void setElegantTextHeight(boolean elegant) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetElegantTextHeight(mNativePaint, elegant);
+ }
+
+ private native void nSetElegantTextHeight(long paintPtr, boolean elegant);
/**
* Return the paint's text size.
*
* @return the paint's text size.
*/
- public native float getTextSize();
+ public float getTextSize() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetTextSize(mNativePaint);
+ }
+
+ private native float nGetTextSize(long paintPtr);
/**
* Set the paint's text size. This value must be > 0
*
* @param textSize set the paint's text size.
*/
- public native void setTextSize(float textSize);
+ public void setTextSize(float textSize) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetTextSize(mNativePaint, textSize);
+ }
+
+ private native void nSetTextSize(long paintPtr, float textSize);
/**
* Return the paint's horizontal scale factor for text. The default value
@@ -1289,7 +1432,12 @@
*
* @return the paint's scale factor in X for drawing/measuring text
*/
- public native float getTextScaleX();
+ public float getTextScaleX() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetTextScaleX(mNativePaint);
+ }
+
+ private native float nGetTextScaleX(long paintPtr);
/**
* Set the paint's horizontal scale factor for text. The default value
@@ -1298,7 +1446,12 @@
*
* @param scaleX set the paint's scale in X for drawing/measuring text.
*/
- public native void setTextScaleX(float scaleX);
+ public void setTextScaleX(float scaleX) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetTextScaleX(mNativePaint, scaleX);
+ }
+
+ private native void nSetTextScaleX(long paintPtr, float scaleX);
/**
* Return the paint's horizontal skew factor for text. The default value
@@ -1306,7 +1459,12 @@
*
* @return the paint's skew factor in X for drawing text.
*/
- public native float getTextSkewX();
+ public float getTextSkewX() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetTextSkewX(mNativePaint);
+ }
+
+ private native float nGetTextSkewX(long paintPtr);
/**
* Set the paint's horizontal skew factor for text. The default value
@@ -1314,7 +1472,12 @@
*
* @param skewX set the paint's skew factor in X for drawing text.
*/
- public native void setTextSkewX(float skewX);
+ public void setTextSkewX(float skewX) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetTextSkewX(mNativePaint, skewX);
+ }
+
+ private native void nSetTextSkewX(long paintPtr, float skewX);
/**
* Return the paint's letter-spacing for text. The default value
@@ -1323,7 +1486,8 @@
* @return the paint's letter-spacing for drawing text.
*/
public float getLetterSpacing() {
- return native_getLetterSpacing(mNativePaint);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetLetterSpacing(mNativePaint);
}
/**
@@ -1334,7 +1498,8 @@
* @param letterSpacing set the paint's letter-spacing for drawing text.
*/
public void setLetterSpacing(float letterSpacing) {
- native_setLetterSpacing(mNativePaint, letterSpacing);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetLetterSpacing(mNativePaint, letterSpacing);
}
/**
@@ -1355,6 +1520,7 @@
* @param settings the font feature settings string to use, may be null.
*/
public void setFontFeatureSettings(String settings) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (settings != null && settings.equals("")) {
settings = null;
}
@@ -1363,7 +1529,7 @@
return;
}
mFontFeatureSettings = settings;
- native_setFontFeatureSettings(mNativePaint, settings);
+ nSetFontFeatureSettings(mNativePaint, settings);
}
/**
@@ -1374,7 +1540,8 @@
* @hide
*/
public int getHyphenEdit() {
- return native_getHyphenEdit(mNativePaint);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetHyphenEdit(mNativePaint);
}
/**
@@ -1386,7 +1553,8 @@
* @hide
*/
public void setHyphenEdit(int hyphen) {
- native_setHyphenEdit(mNativePaint, hyphen);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ nSetHyphenEdit(mNativePaint, hyphen);
}
/**
@@ -1396,7 +1564,12 @@
* @return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*/
- public native float ascent();
+ public float ascent() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nAscent(mNativePaint, mNativeTypeface);
+ }
+
+ private native float nAscent(long paintPtr, long typefacePtr);
/**
* Return the distance below (positive) the baseline (descent) based on the
@@ -1405,7 +1578,12 @@
* @return the distance below (positive) the baseline (descent) based on
* the current typeface and text size.
*/
- public native float descent();
+ public float descent() {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nDescent(mNativePaint, mNativeTypeface);
+ }
+
+ private native float nDescent(long paintPtr, long typefacePtr);
/**
* Class that describes the various metrics for a font at a given text size.
@@ -1447,7 +1625,13 @@
* the appropriate values given the paint's text attributes.
* @return the font's recommended interline spacing.
*/
- public native float getFontMetrics(FontMetrics metrics);
+ public float getFontMetrics(FontMetrics metrics) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetFontMetrics(mNativePaint, mNativeTypeface, metrics);
+ }
+
+ private native float nGetFontMetrics(long paintPtr,
+ long typefacePtr, FontMetrics metrics);
/**
* Allocates a new FontMetrics object, and then calls getFontMetrics(fm)
@@ -1487,7 +1671,13 @@
*
* @return the font's interline spacing.
*/
- public native int getFontMetricsInt(FontMetricsInt fmi);
+ public int getFontMetricsInt(FontMetricsInt fmi) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nGetFontMetricsInt(mNativePaint, mNativeTypeface, fmi);
+ }
+
+ private native int nGetFontMetricsInt(long paintPtr,
+ long typefacePtr, FontMetricsInt fmi);
public FontMetricsInt getFontMetricsInt() {
FontMetricsInt fm = new FontMetricsInt();
@@ -1515,6 +1705,7 @@
* @return The width of the text
*/
public float measureText(char[] text, int index, int count) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1526,13 +1717,13 @@
return 0f;
}
if (!mHasCompatScaling) {
- return (float) Math.ceil(native_getTextAdvances(mNativePaint, mNativeTypeface, text,
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, mNativeTypeface, text,
index, count, index, count, mBidiFlags, null, 0));
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float w = native_getTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index,
+ float w = nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index,
count, mBidiFlags, null, 0);
setTextSize(oldSize);
return (float) Math.ceil(w*mInvCompatScaling);
@@ -1547,6 +1738,7 @@
* @return The width of the text
*/
public float measureText(String text, int start, int end) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1558,12 +1750,12 @@
return 0f;
}
if (!mHasCompatScaling) {
- return (float) Math.ceil(native_getTextAdvances(mNativePaint, mNativeTypeface, text,
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, mNativeTypeface, text,
start, end, start, end, mBidiFlags, null, 0));
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float w = native_getTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start,
+ float w = nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start,
end, mBidiFlags, null, 0);
setTextSize(oldSize);
return (float) Math.ceil(w * mInvCompatScaling);
@@ -1576,6 +1768,7 @@
* @return The width of the text
*/
public float measureText(String text) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1636,6 +1829,7 @@
*/
public int breakText(char[] text, int index, int count,
float maxWidth, float[] measuredWidth) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1647,20 +1841,20 @@
return 0;
}
if (!mHasCompatScaling) {
- return native_breakText(mNativePaint, mNativeTypeface, text, index, count, maxWidth,
+ return nBreakText(mNativePaint, mNativeTypeface, text, index, count, maxWidth,
mBidiFlags, measuredWidth);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- int res = native_breakText(mNativePaint, mNativeTypeface, text, index, count,
+ int res = nBreakText(mNativePaint, mNativeTypeface, text, index, count,
maxWidth * mCompatScaling, mBidiFlags, measuredWidth);
setTextSize(oldSize);
if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
return res;
}
- private static native int native_breakText(long native_object, long native_typeface,
+ private static native int nBreakText(long nObject, long nTypeface,
char[] text, int index, int count,
float maxWidth, int bidiFlags, float[] measuredWidth);
@@ -1683,6 +1877,7 @@
public int breakText(CharSequence text, int start, int end,
boolean measureForwards,
float maxWidth, float[] measuredWidth) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1731,6 +1926,7 @@
*/
public int breakText(String text, boolean measureForwards,
float maxWidth, float[] measuredWidth) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1739,20 +1935,20 @@
return 0;
}
if (!mHasCompatScaling) {
- return native_breakText(mNativePaint, mNativeTypeface, text, measureForwards,
+ return nBreakText(mNativePaint, mNativeTypeface, text, measureForwards,
maxWidth, mBidiFlags, measuredWidth);
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
- int res = native_breakText(mNativePaint, mNativeTypeface, text, measureForwards,
+ int res = nBreakText(mNativePaint, mNativeTypeface, text, measureForwards,
maxWidth*mCompatScaling, mBidiFlags, measuredWidth);
setTextSize(oldSize);
if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
return res;
}
- private static native int native_breakText(long native_object, long native_typeface,
+ private static native int nBreakText(long nObject, long nTypeface,
String text, boolean measureForwards,
float maxWidth, int bidiFlags, float[] measuredWidth);
@@ -1768,6 +1964,7 @@
*/
public int getTextWidths(char[] text, int index, int count,
float[] widths) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1780,14 +1977,14 @@
return 0;
}
if (!mHasCompatScaling) {
- native_getTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
+ nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
mBidiFlags, widths, 0);
return count;
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- native_getTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
+ nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
mBidiFlags, widths, 0);
setTextSize(oldSize);
for (int i = 0; i < count; i++) {
@@ -1851,6 +2048,7 @@
* @return the number of code units in the specified text.
*/
public int getTextWidths(String text, int start, int end, float[] widths) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1865,14 +2063,14 @@
return 0;
}
if (!mHasCompatScaling) {
- native_getTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
+ nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
mBidiFlags, widths, 0);
return end - start;
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- native_getTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
+ nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
mBidiFlags, widths, 0);
setTextSize(oldSize);
for (int i = 0; i < end - start; i++) {
@@ -1904,6 +2102,7 @@
int contextIndex, int contextCount, boolean isRtl, float[] advances,
int advancesIndex) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (chars == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1920,14 +2119,14 @@
return 0f;
}
if (!mHasCompatScaling) {
- return native_getTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
+ return nGetTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
contextIndex, contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float res = native_getTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
+ float res = nGetTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
contextIndex, contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
advancesIndex);
setTextSize(oldSize);
@@ -1950,7 +2149,7 @@
public float getTextRunAdvances(CharSequence text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float[] advances,
int advancesIndex) {
-
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2032,6 +2231,7 @@
*/
public float getTextRunAdvances(String text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2048,14 +2248,14 @@
}
if (!mHasCompatScaling) {
- return native_getTextAdvances(mNativePaint, mNativeTypeface, text, start, end,
+ return nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end,
contextStart, contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float totalAdvance = native_getTextAdvances(mNativePaint, mNativeTypeface, text, start,
+ float totalAdvance = nGetTextAdvances(mNativePaint, mNativeTypeface, text, start,
end, contextStart, contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
advancesIndex);
setTextSize(oldSize);
@@ -2096,6 +2296,7 @@
*/
public int getTextRunCursor(char[] text, int contextStart, int contextLength,
int dir, int offset, int cursorOpt) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
int contextEnd = contextStart + contextLength;
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
@@ -2104,7 +2305,7 @@
throw new IndexOutOfBoundsException();
}
- return native_getTextRunCursor(mNativePaint, text,
+ return nGetTextRunCursor(mNativePaint, text,
contextStart, contextLength, dir, offset, cursorOpt);
}
@@ -2183,6 +2384,7 @@
*/
public int getTextRunCursor(String text, int contextStart, int contextEnd,
int dir, int offset, int cursorOpt) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
| (text.length() - contextEnd) | cursorOpt) < 0)
@@ -2190,7 +2392,7 @@
throw new IndexOutOfBoundsException();
}
- return native_getTextRunCursor(mNativePaint, text,
+ return nGetTextRunCursor(mNativePaint, text,
contextStart, contextEnd, dir, offset, cursorOpt);
}
@@ -2209,10 +2411,11 @@
*/
public void getTextPath(char[] text, int index, int count,
float x, float y, Path path) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException();
}
- native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, index, count, x, y,
+ nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, index, count, x, y,
path.ni());
}
@@ -2231,10 +2434,11 @@
*/
public void getTextPath(String text, int start, int end,
float x, float y, Path path) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
- native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y,
+ nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y,
path.ni());
}
@@ -2249,13 +2453,14 @@
* allocated by the caller.
*/
public void getTextBounds(String text, int start, int end, Rect bounds) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
if (bounds == null) {
throw new NullPointerException("need bounds Rect");
}
- nativeGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds);
+ nGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds);
}
/**
@@ -2269,13 +2474,14 @@
* allocated by the caller.
*/
public void getTextBounds(char[] text, int index, int count, Rect bounds) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException();
}
if (bounds == null) {
throw new NullPointerException("need bounds Rect");
}
- nativeGetCharArrayBounds(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags,
+ nGetCharArrayBounds(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags,
bounds);
}
@@ -2296,7 +2502,8 @@
* @return true if the typeface has a glyph for the string
*/
public boolean hasGlyph(String string) {
- return native_hasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
+ return nHasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
}
/**
@@ -2337,6 +2544,7 @@
*/
public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, int offset) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2349,7 +2557,7 @@
return 0.0f;
}
// TODO: take mCompatScaling into account (or eliminate compat scaling)?
- return native_getRunAdvance(mNativePaint, mNativeTypeface, text, start, end,
+ return nGetRunAdvance(mNativePaint, mNativeTypeface, text, start, end,
contextStart, contextEnd, isRtl, offset);
}
@@ -2367,6 +2575,7 @@
*/
public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2417,6 +2626,7 @@
*/
public int getOffsetForAdvance(char[] text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float advance) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2426,7 +2636,7 @@
throw new IndexOutOfBoundsException();
}
// TODO: take mCompatScaling into account (or eliminate compat scaling)?
- return native_getOffsetForAdvance(mNativePaint, mNativeTypeface, text, start, end,
+ return nGetOffsetForAdvance(mNativePaint, mNativeTypeface, text, start, end,
contextStart, contextEnd, isRtl, advance);
}
@@ -2444,6 +2654,7 @@
*/
public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float advance) {
+ if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2464,90 +2675,88 @@
@Override
protected void finalize() throws Throwable {
try {
- finalizer(mNativePaint);
- mNativePaint = 0;
+ if (mNativePaint != 0) {
+ nFinalizer(mNativePaint);
+ mNativePaint = 0;
+ }
} finally {
super.finalize();
}
}
- private static native long native_init();
- private static native long native_initWithPaint(long paint);
- private static native void native_reset(long native_object);
- private static native void native_set(long native_dst, long native_src);
- private static native int native_getStyle(long native_object);
- private static native void native_setStyle(long native_object, int style);
- private static native int native_getStrokeCap(long native_object);
- private static native void native_setStrokeCap(long native_object, int cap);
- private static native int native_getStrokeJoin(long native_object);
- private static native void native_setStrokeJoin(long native_object,
+ private static native long nInit();
+ private static native long nInitWithPaint(long paint);
+ private static native void nReset(long paintPtr);
+ private static native void nSet(long paintPtrDest, long paintPtrSrc);
+ private static native int nGetStyle(long paintPtr);
+ private static native void nSetStyle(long paintPtr, int style);
+ private static native int nGetStrokeCap(long paintPtr);
+ private static native void nSetStrokeCap(long paintPtr, int cap);
+ private static native int nGetStrokeJoin(long paintPtr);
+ private static native void nSetStrokeJoin(long paintPtr,
int join);
- private static native boolean native_getFillPath(long native_object,
+ private static native boolean nGetFillPath(long paintPtr,
long src, long dst);
- private static native long native_setShader(long native_object, long shader);
- private static native long native_setColorFilter(long native_object,
+ private static native long nSetShader(long paintPtr, long shader);
+ private static native long nSetColorFilter(long paintPtr,
long filter);
- private static native long native_setXfermode(long native_object,
+ private static native long nSetXfermode(long paintPtr,
long xfermode);
- private static native long native_setPathEffect(long native_object,
+ private static native long nSetPathEffect(long paintPtr,
long effect);
- private static native long native_setMaskFilter(long native_object,
+ private static native long nSetMaskFilter(long paintPtr,
long maskfilter);
- private static native long native_setTypeface(long native_object,
+ private static native long nSetTypeface(long paintPtr,
long typeface);
- private static native long native_setRasterizer(long native_object,
+ private static native long nSetRasterizer(long paintPtr,
long rasterizer);
- private static native int native_getTextAlign(long native_object);
- private static native void native_setTextAlign(long native_object,
+ private static native int nGetTextAlign(long paintPtr);
+ private static native void nSetTextAlign(long paintPtr,
int align);
- private static native void native_setTextLocale(long native_object,
+ private static native void nSetTextLocale(long paintPtr,
String locale);
- private static native int native_getTextGlyphs(long native_object,
- String text, int start, int end, int contextStart, int contextEnd,
- int flags, char[] glyphs);
-
- private static native float native_getTextAdvances(long native_object, long native_typeface,
+ private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
char[] text, int index, int count, int contextIndex, int contextCount,
int bidiFlags, float[] advances, int advancesIndex);
- private static native float native_getTextAdvances(long native_object, long native_typeface,
+ private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
String text, int start, int end, int contextStart, int contextEnd,
int bidiFlags, float[] advances, int advancesIndex);
- private native int native_getTextRunCursor(long native_object, char[] text,
+ private native int nGetTextRunCursor(long paintPtr, char[] text,
int contextStart, int contextLength, int dir, int offset, int cursorOpt);
- private native int native_getTextRunCursor(long native_object, String text,
+ private native int nGetTextRunCursor(long paintPtr, String text,
int contextStart, int contextEnd, int dir, int offset, int cursorOpt);
- private static native void native_getTextPath(long native_object, long native_typeface,
+ private static native void nGetTextPath(long paintPtr, long typefacePtr,
int bidiFlags, char[] text, int index, int count, float x, float y, long path);
- private static native void native_getTextPath(long native_object, long native_typeface,
+ private static native void nGetTextPath(long paintPtr, long typefacePtr,
int bidiFlags, String text, int start, int end, float x, float y, long path);
- private static native void nativeGetStringBounds(long nativePaint, long native_typeface,
+ private static native void nGetStringBounds(long nativePaint, long typefacePtr,
String text, int start, int end, int bidiFlags, Rect bounds);
- private static native void nativeGetCharArrayBounds(long nativePaint, long native_typeface,
+ private static native void nGetCharArrayBounds(long nativePaint, long typefacePtr,
char[] text, int index, int count, int bidiFlags, Rect bounds);
- private static native void finalizer(long nativePaint);
+ private static native void nFinalizer(long nativePaint);
- private static native void native_setShadowLayer(long native_object,
+ private static native void nSetShadowLayer(long paintPtr,
float radius, float dx, float dy, int color);
- private static native boolean native_hasShadowLayer(long native_object);
+ private static native boolean nHasShadowLayer(long paintPtr);
- private static native float native_getLetterSpacing(long native_object);
- private static native void native_setLetterSpacing(long native_object,
+ private static native float nGetLetterSpacing(long paintPtr);
+ private static native void nSetLetterSpacing(long paintPtr,
float letterSpacing);
- private static native void native_setFontFeatureSettings(long native_object,
+ private static native void nSetFontFeatureSettings(long paintPtr,
String settings);
- private static native int native_getHyphenEdit(long native_object);
- private static native void native_setHyphenEdit(long native_object, int hyphen);
- private static native boolean native_hasGlyph(long native_object, long native_typeface,
+ private static native int nGetHyphenEdit(long paintPtr);
+ private static native void nSetHyphenEdit(long paintPtr, int hyphen);
+ private static native boolean nHasGlyph(long paintPtr, long typefacePtr,
int bidiFlags, String string);
- private static native float native_getRunAdvance(long native_object, long native_typeface,
+ private static native float nGetRunAdvance(long paintPtr, long typefacePtr,
char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
int offset);
- private static native int native_getOffsetForAdvance(long native_object,
- long native_typeface, char[] text, int start, int end, int contextStart, int contextEnd,
+ private static native int nGetOffsetForAdvance(long paintPtr,
+ long typefacePtr, char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, float advance);
}
diff --git a/graphics/tests/graphicstests/src/android/graphics/PaintTest.java b/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
index b67aa7d..6763dd1 100644
--- a/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
@@ -162,4 +162,60 @@
} catch (IndexOutOfBoundsException e) {
}
}
+
+ public void testMeasureTextBidi() {
+ Paint p = new Paint();
+ {
+ String bidiText = "abc \u0644\u063A\u0629 def";
+ p.setBidiFlags(Paint.BIDI_LTR);
+ float width = p.measureText(bidiText, 0, 4);
+ p.setBidiFlags(Paint.BIDI_RTL);
+ width += p.measureText(bidiText, 4, 7);
+ p.setBidiFlags(Paint.BIDI_LTR);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ {
+ String bidiText = "abc \u0644\u063A\u0629 def";
+ p.setBidiFlags(Paint.BIDI_DEFAULT_LTR);
+ float width = p.measureText(bidiText, 0, 4);
+ width += p.measureText(bidiText, 4, 7);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ {
+ String bidiText = "abc \u0644\u063A\u0629 def";
+ p.setBidiFlags(Paint.BIDI_FORCE_LTR);
+ float width = p.measureText(bidiText, 0, 4);
+ width += p.measureText(bidiText, 4, 7);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ {
+ String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+ p.setBidiFlags(Paint.BIDI_RTL);
+ float width = p.measureText(bidiText, 0, 4);
+ p.setBidiFlags(Paint.BIDI_LTR);
+ width += p.measureText(bidiText, 4, 7);
+ p.setBidiFlags(Paint.BIDI_RTL);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ {
+ String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+ p.setBidiFlags(Paint.BIDI_DEFAULT_RTL);
+ float width = p.measureText(bidiText, 0, 4);
+ width += p.measureText(bidiText, 4, 7);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ {
+ String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+ p.setBidiFlags(Paint.BIDI_FORCE_RTL);
+ float width = p.measureText(bidiText, 0, 4);
+ width += p.measureText(bidiText, 4, 7);
+ width += p.measureText(bidiText, 7, bidiText.length());
+ assertEquals(width, p.measureText(bidiText), 1.0f);
+ }
+ }
}
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index a81ffb9..018572c 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -44,6 +44,12 @@
#define DEBUG_COLOR_MERGEDBATCH 0x5f7f7fff
#define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f
+static bool avoidOverdraw() {
+ // Don't avoid overdraw when visualizing it, since that makes it harder to
+ // debug where it's coming from, and when the problem occurs.
+ return !Properties::debugOverdraw;
+};
+
/////////////////////////////////////////////////////////////////////////////////
// Operation Batches
/////////////////////////////////////////////////////////////////////////////////
@@ -495,7 +501,7 @@
&& mSaveStack.empty()
&& !state->mRoundRectClipState;
- if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
+ if (CC_LIKELY(avoidOverdraw()) && mBatches.size() &&
state->mClipSideFlags != kClipSide_ConservativeFull &&
deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) {
// avoid overdraw by resetting drawing state + discarding drawing ops
@@ -533,7 +539,11 @@
if (deferInfo.mergeable) {
// Try to merge with any existing batch with same mergeId.
- if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) {
+ std::unordered_map<mergeid_t, DrawBatch*>& mergingBatch
+ = mMergingBatches[deferInfo.batchId];
+ auto getResult = mergingBatch.find(deferInfo.mergeId);
+ if (getResult != mergingBatch.end()) {
+ targetBatch = getResult->second;
if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) {
targetBatch = nullptr;
}
@@ -577,7 +587,8 @@
if (deferInfo.mergeable) {
targetBatch = new MergingDrawBatch(deferInfo,
renderer.getViewportWidth(), renderer.getViewportHeight());
- mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch);
+ mMergingBatches[deferInfo.batchId].insert(
+ std::make_pair(deferInfo.mergeId, targetBatch));
} else {
targetBatch = new DrawBatch(deferInfo);
mBatchLookup[deferInfo.batchId] = targetBatch;
@@ -642,7 +653,7 @@
// save and restore so that reordering doesn't affect final state
renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- if (CC_LIKELY(mAvoidOverdraw)) {
+ if (CC_LIKELY(avoidOverdraw())) {
for (unsigned int i = 1; i < mBatches.size(); i++) {
if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) {
discardDrawingBatches(i - 1);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 4f2dca5..7873fbd 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -17,9 +17,10 @@
#ifndef ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
#define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
+#include <unordered_map>
+
#include <utils/Errors.h>
#include <utils/LinearAllocator.h>
-#include <utils/TinyHashMap.h>
#include "Matrix.h"
#include "OpenGLRenderer.h"
@@ -82,8 +83,8 @@
class DeferredDisplayList {
friend struct DeferStateStruct; // used to give access to allocator
public:
- DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) :
- mBounds(bounds), mAvoidOverdraw(avoidOverdraw) {
+ DeferredDisplayList(const Rect& bounds)
+ : mBounds(bounds) {
clear();
}
~DeferredDisplayList() { clear(); }
@@ -151,7 +152,6 @@
// layer space bounds of rendering
Rect mBounds;
- const bool mAvoidOverdraw;
/**
* At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so
@@ -177,7 +177,7 @@
* MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
* collide, which avoids the need to resolve mergeid collisions.
*/
- TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count];
+ std::unordered_map<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count];
LinearAllocator mAllocator;
};
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a401ce1..0c1af5f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1431,10 +1431,7 @@
return;
}
- // Don't avoid overdraw when visualizing, since that makes it harder to
- // debug where it's coming from, and when the problem occurs.
- bool avoidOverdraw = !Properties::debugOverdraw;
- DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
+ DeferredDisplayList deferredList(mState.currentClipRect());
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9dc5b45..ddfd621 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -62,7 +62,7 @@
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
, mJankTracker(thread.timeLord().frameIntervalNanos())
, mProfiler(mFrames)
- , mContentOverdrawProtectionBounds(0, 0, 0, 0) {
+ , mContentDrawBounds(0, 0, 0, 0) {
mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
@@ -309,7 +309,7 @@
Rect outBounds;
// It there are multiple render nodes, they are as follows:
// #0 - backdrop
- // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds)
+ // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
// #2 - frame
// Usually the backdrop cannot be seen since it will be entirely covered by the content. While
// resizing however it might become partially visible. The following render loop will crop the
@@ -317,66 +317,72 @@
// against the backdrop (since that indicates a shrinking of the window) and then the frame
// around everything.
// The bounds of the backdrop against which the content should be clipped.
- Rect backdropBounds = mContentOverdrawProtectionBounds;
+ Rect backdropBounds = mContentDrawBounds;
+ // Usually the contents bounds should be mContentDrawBounds - however - we will
+ // move it towards the fixed edge to give it a more stable appearance (for the moment).
+ Rect contentBounds;
// If there is no content bounds we ignore the layering as stated above and start with 2.
- int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0;
+ int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() <= 2) ? 2 : 0;
// Draw all render nodes. Note that
for (const sp<RenderNode>& node : mRenderNodes) {
if (layer == 0) { // Backdrop.
- // Draw the backdrop clipped to the inverse content bounds.
+ // Draw the backdrop clipped to the inverse content bounds, but assume that the content
+ // was moved to the upper left corner.
const RenderProperties& properties = node->properties();
Rect targetBounds(properties.getLeft(), properties.getTop(),
properties.getRight(), properties.getBottom());
+ // Move the content bounds towards the fixed corner of the backdrop.
+ const int x = targetBounds.left;
+ const int y = targetBounds.top;
+ contentBounds.set(x, y, x + mContentDrawBounds.getWidth(),
+ y + mContentDrawBounds.getHeight());
// Remember the intersection of the target bounds and the intersection bounds against
// which we have to crop the content.
+ backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
backdropBounds.intersect(targetBounds);
// Check if we have to draw something on the left side ...
- if (targetBounds.left < mContentOverdrawProtectionBounds.left) {
+ if (targetBounds.left < contentBounds.left) {
mCanvas->save(SkCanvas::kClip_SaveFlag);
if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
- mContentOverdrawProtectionBounds.left, targetBounds.bottom,
+ contentBounds.left, targetBounds.bottom,
SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
}
// Reduce the target area by the area we have just painted.
- targetBounds.left = std::min(mContentOverdrawProtectionBounds.left,
- targetBounds.right);
+ targetBounds.left = std::min(contentBounds.left, targetBounds.right);
mCanvas->restore();
}
// ... or on the right side ...
- if (targetBounds.right > mContentOverdrawProtectionBounds.right &&
+ if (targetBounds.right > contentBounds.right &&
!targetBounds.isEmpty()) {
mCanvas->save(SkCanvas::kClip_SaveFlag);
- if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top,
+ if (mCanvas->clipRect(contentBounds.right, targetBounds.top,
targetBounds.right, targetBounds.bottom,
SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
}
// Reduce the target area by the area we have just painted.
- targetBounds.right = std::max(targetBounds.left,
- mContentOverdrawProtectionBounds.right);
+ targetBounds.right = std::max(targetBounds.left, contentBounds.right);
mCanvas->restore();
}
// ... or at the top ...
- if (targetBounds.top < mContentOverdrawProtectionBounds.top &&
+ if (targetBounds.top < contentBounds.top &&
!targetBounds.isEmpty()) {
mCanvas->save(SkCanvas::kClip_SaveFlag);
if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
- mContentOverdrawProtectionBounds.top,
+ contentBounds.top,
SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
}
// Reduce the target area by the area we have just painted.
- targetBounds.top = std::min(mContentOverdrawProtectionBounds.top,
- targetBounds.bottom);
+ targetBounds.top = std::min(contentBounds.top, targetBounds.bottom);
mCanvas->restore();
}
// ... or at the bottom.
- if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom &&
+ if (targetBounds.bottom > contentBounds.bottom &&
!targetBounds.isEmpty()) {
mCanvas->save(SkCanvas::kClip_SaveFlag);
- if (mCanvas->clipRect(targetBounds.left,
- mContentOverdrawProtectionBounds.bottom, targetBounds.right,
+ if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right,
targetBounds.bottom, SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
}
@@ -384,10 +390,17 @@
}
} else if (layer == 1) { // Content
// It gets cropped against the bounds of the backdrop to stay inside.
- mCanvas->save(SkCanvas::kClip_SaveFlag);
- if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top,
- backdropBounds.right, backdropBounds.bottom,
- SkRegion::kIntersect_Op)) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+
+ // We shift and clip the content to match its final location in the window.
+ const float left = mContentDrawBounds.left;
+ const float top = mContentDrawBounds.top;
+ const float dx = backdropBounds.left - left;
+ const float dy = backdropBounds.top - top;
+ const float width = backdropBounds.getWidth();
+ const float height = backdropBounds.getHeight();
+ mCanvas->translate(dx, dy);
+ if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) {
mCanvas->drawRenderNode(node.get(), outBounds);
}
mCanvas->restore();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 1c3845c..e0cbabd 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -126,8 +126,8 @@
mRenderNodes.end());
}
- void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
- mContentOverdrawProtectionBounds.set(left, top, right, bottom);
+ void setContentDrawBounds(int left, int top, int right, int bottom) {
+ mContentDrawBounds.set(left, top, right, bottom);
}
private:
@@ -167,7 +167,7 @@
std::set<RenderNode*> mPrefetechedLayers;
// Stores the bounds of the main content.
- Rect mContentOverdrawProtectionBounds;
+ Rect mContentDrawBounds;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index f43a769..26aae90 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -529,15 +529,14 @@
staticPostAndWait(task);
}
-CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top,
+CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top,
int right, int bottom) {
- args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right,
- args->bottom);
+ args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom);
return nullptr;
}
-void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
- SETUP_TASK(setContentOverdrawProtectionBounds);
+void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
+ SETUP_TASK(setContentDrawBounds);
args->context = mContext;
args->left = left;
args->top = top;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 046f24a..d1b62f1 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -109,7 +109,7 @@
ANDROID_API void addRenderNode(RenderNode* node, bool placeFront);
ANDROID_API void removeRenderNode(RenderNode* node);
ANDROID_API void drawRenderNode(RenderNode* node);
- ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom);
+ ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
private:
RenderThread& mRenderThread;
diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h
deleted file mode 100644
index 4ff9a42..0000000
--- a/libs/hwui/utils/TinyHashMap.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HWUI_TINYHASHMAP_H
-#define ANDROID_HWUI_TINYHASHMAP_H
-
-#include <utils/BasicHashtable.h>
-
-namespace android {
-namespace uirenderer {
-
-/**
- * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry.
- */
-template <typename TKey, typename TValue>
-class TinyHashMap {
-public:
- typedef key_value_pair_t<TKey, TValue> TEntry;
-
- /**
- * Puts an entry in the hash, removing any existing entry with the same key
- */
- void put(TKey key, TValue value) {
- hash_t hash = android::hash_type(key);
-
- ssize_t index = mTable.find(-1, hash, key);
- if (index != -1) {
- mTable.removeAt(index);
- }
-
- TEntry initEntry(key, value);
- mTable.add(hash, initEntry);
- }
-
- /**
- * Return true if key is in the map, in which case stores the value in the output ref
- */
- bool get(TKey key, TValue& outValue) {
- hash_t hash = android::hash_type(key);
- ssize_t index = mTable.find(-1, hash, key);
- if (index == -1) {
- return false;
- }
- outValue = mTable.entryAt(index).value;
- return true;
- }
-
- void clear() { mTable.clear(); }
-
-private:
- BasicHashtable<TKey, TEntry> mTable;
-};
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_TINYHASHMAP_H
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index 54ad60e..4b8f81e 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -18,6 +18,7 @@
import android.os.Parcel;
import android.util.Log;
+import android.util.MathUtils;
import java.util.Calendar;
import java.util.Collections;
@@ -332,7 +333,14 @@
}
// Skip to the next one.
- parcel.setDataPosition(start + size);
+ try {
+ parcel.setDataPosition(MathUtils.addOrThrow(start, size));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid size: " + e.getMessage());
+ error = true;
+ break;
+ }
+
bytesLeft -= size;
++recCount;
}
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index 673a254..7e0649b 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -23,13 +23,6 @@
android:actionViewClass="android.widget.SearchView"
android:imeOptions="actionSearch" />
<item
- android:id="@+id/menu_create_dir"
- android:title="@string/menu_create_dir"
- android:icon="@drawable/ic_menu_new_folder"
- android:alphabeticShortcut="e"
- android:showAsAction="always"
- android:visible="false" />
- <item
android:id="@+id/menu_sort"
android:title="@string/menu_sort"
android:icon="@drawable/ic_menu_sortby"
@@ -63,6 +56,13 @@
android:showAsAction="never"
android:visible="false" />
<item
+ android:id="@+id/menu_create_dir"
+ android:title="@string/menu_create_dir"
+ android:icon="@drawable/ic_menu_new_folder"
+ android:alphabeticShortcut="e"
+ android:showAsAction="always"
+ android:visible="false" />
+ <item
android:id="@+id/menu_paste_from_clipboard"
android:title="@string/menu_paste_from_clipboard"
android:alphabeticShortcut="v"
@@ -70,11 +70,11 @@
android:visible="false" />
<!-- Copy action is defined in mode_directory.xml -->
<item
- android:id="@+id/menu_advanced"
+ android:id="@+id/menu_file_size"
android:showAsAction="never"
android:visible="false" />
<item
- android:id="@+id/menu_file_size"
+ android:id="@+id/menu_advanced"
android:showAsAction="never"
android:visible="false" />
<item
diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml
index ce5b174..ed7820b 100644
--- a/packages/DocumentsUI/res/values/config.xml
+++ b/packages/DocumentsUI/res/values/config.xml
@@ -15,5 +15,5 @@
-->
<resources>
- <bool name="productivity_device">false</bool>
+ <bool name="productivity_device">true</bool>
</resources>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index a4acb604..d21b5ee 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -27,7 +27,7 @@
<string name="title_save">Save to</string>
<!-- Menu item that creates a new directory/folder at the current location [CHAR LIMIT=24] -->
- <string name="menu_create_dir">Create folder</string>
+ <string name="menu_create_dir">New folder</string>
<!-- Menu item that switches view to show documents as a large-format grid of thumbnails [CHAR LIMIT=24] -->
<string name="menu_grid">Grid view</string>
<!-- Menu item that switches view to show documents as a list [CHAR LIMIT=24] -->
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index a6a45e5..caaa2b9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -20,6 +20,7 @@
import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
import static com.android.documentsui.DirectoryFragment.ANIM_UP;
+import static com.android.documentsui.Shared.DEBUG;
import static com.android.internal.util.Preconditions.checkArgument;
import android.app.Activity;
@@ -127,10 +128,10 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- boolean shown = super.onPrepareOptionsMenu(menu);
+ super.onPrepareOptionsMenu(menu);
final RootInfo root = getCurrentRoot();
- final DocumentInfo cwd = getCurrentDirectory();
+ final boolean inRecents = getCurrentDirectory() == null;
final MenuItem sort = menu.findItem(R.id.menu_sort);
final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
@@ -140,26 +141,28 @@
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
final MenuItem settings = menu.findItem(R.id.menu_settings);
- mSearchManager.update(root);
+ // I'm thinkin' this isn't necesary here. If it is...'cuz of a bug....
+ // then uncomment the linke and let's get a proper bug reference here.
+ // mSearchManager.update(root);
// Search uses backend ranking; no sorting
- sort.setVisible(cwd != null && !mSearchManager.isSearching());
+ sort.setVisible(!inRecents && !mSearchManager.isSearching());
+
+ // grid/list is effectively a toggle.
+ grid.setVisible(mState.derivedMode != State.MODE_GRID);
+ list.setVisible(mState.derivedMode != State.MODE_LIST);
+
+ sortSize.setVisible(mState.showSize); // Only sort by size when visible
+ fileSize.setVisible(!mState.forceSize);
+ advanced.setVisible(!mState.forceAdvanced);
+ settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
? R.string.menu_advanced_hide : R.string.menu_advanced_show);
fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
? R.string.menu_file_size_hide : R.string.menu_file_size_show);
- State state = getDisplayState();
-
- sortSize.setVisible(state.showSize); // Only sort by size when visible
- fileSize.setVisible(!state.showSize);
- grid.setVisible(state.derivedMode != State.MODE_GRID);
- list.setVisible(state.derivedMode != State.MODE_LIST);
- advanced.setVisible(!mState.showAdvanced);
- settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
-
- return shown;
+ return true;
}
State buildDefaultState() {
@@ -185,12 +188,10 @@
void onStackRestored(boolean restored, boolean external) {}
void onRootPicked(RootInfo root) {
- State state = getDisplayState();
-
// Clear entire backstack and start in new root
- state.stack.root = root;
- state.stack.clear();
- state.stackTouched = true;
+ mState.stack.root = root;
+ mState.stack.clear();
+ mState.stackTouched = true;
mSearchManager.update(root);
@@ -280,6 +281,7 @@
return cwd != null
&& cwd.isCreateSupported()
&& !mSearchManager.isSearching()
+ && !root.isRecents()
&& !root.isDownloads();
}
@@ -289,8 +291,8 @@
}
void openDirectory(DocumentInfo doc) {
- getDisplayState().stack.push(doc);
- getDisplayState().stackTouched = true;
+ mState.stack.push(doc);
+ mState.stackTouched = true;
onCurrentDirectoryChanged(ANIM_DOWN);
}
@@ -367,16 +369,15 @@
}
void setDisplayAdvancedDevices(boolean display) {
- State state = getDisplayState();
LocalPreferences.setDisplayAdvancedDevices(this, display);
- state.showAdvanced = state.forceAdvanced | display;
+ mState.showAdvanced = mState.forceAdvanced | display;
RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
invalidateOptionsMenu();
}
void setDisplayFileSize(boolean display) {
LocalPreferences.setDisplayFileSize(this, display);
- getDisplayState().showSize = display;
+ mState.showSize = display;
DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
invalidateOptionsMenu();
}
@@ -389,7 +390,7 @@
* Set state sort order based on explicit user action.
*/
void setUserSortOrder(int sortOrder) {
- getDisplayState().userSortOrder = sortOrder;
+ mState.userSortOrder = sortOrder;
DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
}
@@ -397,7 +398,7 @@
* Set state mode based on explicit user action.
*/
void setUserMode(int mode) {
- getDisplayState().userMode = mode;
+ mState.userMode = mode;
DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
}
@@ -411,7 +412,7 @@
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
- state.putParcelable(EXTRA_STATE, getDisplayState());
+ state.putParcelable(EXTRA_STATE, mState);
}
@Override
@@ -420,16 +421,15 @@
}
RootInfo getCurrentRoot() {
- State state = getDisplayState();
- if (state.stack.root != null) {
- return state.stack.root;
+ if (mState.stack.root != null) {
+ return mState.stack.root;
} else {
return mRoots.getRecentsRoot();
}
}
public DocumentInfo getCurrentDirectory() {
- return getDisplayState().stack.peek();
+ return mState.stack.peek();
}
public Executor getExecutorForCurrentDirectory() {
@@ -470,9 +470,8 @@
// Update the restored stack to ensure we have freshest data
stack.updateDocuments(getContentResolver());
- State state = getDisplayState();
- state.stack = stack;
- state.stackTouched = true;
+ mState.stack = stack;
+ mState.stackTouched = true;
onCurrentDirectoryChanged(ANIM_SIDE);
} catch (FileNotFoundException e) {
@@ -502,9 +501,8 @@
@Override
protected void onPostExecute(DocumentInfo result) {
if (result != null) {
- State state = getDisplayState();
- state.stack.push(result);
- state.stackTouched = true;
+ mState.stack.push(result);
+ mState.stackTouched = true;
onCurrentDirectoryChanged(ANIM_SIDE);
}
}
@@ -516,7 +514,9 @@
@Override
protected Void doInBackground(Void... params) {
- State state = getDisplayState();
+ if (DEBUG && !mState.stack.isEmpty()) {
+ Log.w(mTag, "Overwriting existing stack.");
+ }
RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
// Restore last stack for calling package
@@ -528,7 +528,7 @@
mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
final byte[] rawStack = cursor.getBlob(
cursor.getColumnIndex(ResumeColumns.STACK));
- DurableUtils.readFromArray(rawStack, state.stack);
+ DurableUtils.readFromArray(rawStack, mState.stack);
mRestoredStack = true;
}
} catch (IOException e) {
@@ -539,13 +539,13 @@
if (mRestoredStack) {
// Update the restored stack to ensure we have freshest data
- final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
+ final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
try {
- state.stack.updateRoot(matchingRoots);
- state.stack.updateDocuments(getContentResolver());
+ mState.stack.updateRoot(matchingRoots);
+ mState.stack.updateDocuments(getContentResolver());
} catch (FileNotFoundException e) {
Log.w(mTag, "Failed to restore stack: " + e);
- state.stack.reset();
+ mState.stack.reset();
mRestoredStack = false;
}
}
@@ -556,7 +556,7 @@
@Override
protected void onPostExecute(Void result) {
if (isDestroyed()) return;
- getDisplayState().restored = true;
+ mState.restored = true;
onCurrentDirectoryChanged(ANIM_NONE);
onStackRestored(mRestoredStack, mExternal);
}
@@ -600,10 +600,9 @@
return;
}
- State state = getDisplayState();
- while (state.stack.size() > position + 1) {
- state.stackTouched = true;
- state.stack.pop();
+ while (mState.stack.size() > position + 1) {
+ mState.stackTouched = true;
+ mState.stack.pop();
}
onCurrentDirectoryChanged(ANIM_UP);
}
@@ -620,13 +619,12 @@
final class StackAdapter extends BaseAdapter {
@Override
public int getCount() {
- return getDisplayState().stack.size();
+ return mState.stack.size();
}
@Override
public DocumentInfo getItem(int position) {
- State state = getDisplayState();
- return state.stack.get(state.stack.size() - position - 1);
+ return mState.stack.get(mState.stack.size() - position - 1);
}
@Override
@@ -714,13 +712,12 @@
return;
}
- State state = getDisplayState();
- if (state.currentSearch != null) {
+ if (mState.currentSearch != null) {
mMenu.expandActionView();
mView.setIconified(false);
mView.clearFocus();
- mView.setQuery(state.currentSearch, false);
+ mView.setQuery(mState.currentSearch, false);
} else {
mView.clearFocus();
if (!mView.isIconified()) {
@@ -746,7 +743,7 @@
mMenu.setVisible(visible);
if (!visible) {
- getDisplayState().currentSearch = null;
+ mState.currentSearch = null;
}
}
@@ -764,7 +761,7 @@
}
boolean isSearching() {
- return getDisplayState().currentSearch != null;
+ return mState.currentSearch != null;
}
boolean isExpanded() {
@@ -779,7 +776,7 @@
return false;
}
- getDisplayState().currentSearch = null;
+ mState.currentSearch = null;
onCurrentDirectoryChanged(ANIM_NONE);
return false;
}
@@ -798,7 +795,7 @@
mIgnoreNextCollapse = false;
return true;
}
- getDisplayState().currentSearch = null;
+ mState.currentSearch = null;
onCurrentDirectoryChanged(ANIM_NONE);
return true;
}
@@ -806,7 +803,7 @@
@Override
public boolean onQueryTextSubmit(String query) {
mSearchExpanded = true;
- getDisplayState().currentSearch = query;
+ mState.currentSearch = query;
mView.clearFocus();
onCurrentDirectoryChanged(ANIM_NONE);
return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 815fbfe..66f8acd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -62,7 +62,6 @@
private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
- public static final String EXTRA_STACK = "com.android.documentsui.STACK";
public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
public static final String EXTRA_TRANSFER_MODE = "com.android.documentsui.TRANSFER_MODE";
@@ -115,12 +114,12 @@
final Intent copyIntent = new Intent(activity, CopyService.class);
copyIntent.putParcelableArrayListExtra(
EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
- copyIntent.putExtra(EXTRA_STACK, (Parcelable) dstStack);
+ copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
int toastMessage = (mode == TRANSFER_MODE_COPY) ? R.plurals.copy_begin
: R.plurals.move_begin;
- Shared.makeSnackbar(activity,
+ Snackbars.makeSnackbar(activity,
res.getQuantityString(toastMessage, srcDocs.size(), srcDocs.size()),
Snackbar.LENGTH_SHORT).show();
activity.startService(copyIntent);
@@ -142,7 +141,7 @@
}
final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
- final DocumentStack stack = intent.getParcelableExtra(EXTRA_STACK);
+ final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
// Copy by default.
final int transferMode = intent.getIntExtra(EXTRA_TRANSFER_MODE, TRANSFER_MODE_COPY);
@@ -173,7 +172,7 @@
Log.e(TAG, mFailedFiles.size() + " files failed to copy");
final Context context = getApplicationContext();
final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
+ navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -221,7 +220,7 @@
final Context context = getApplicationContext();
final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
+ navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
final String contentTitle = getString(copying ? R.string.copy_notification_title
: R.string.move_notification_title);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 9f44516..c6425a6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -147,7 +147,7 @@
// Navigate into newly created child
mActivity.onDirectoryCreated(result);
} else {
- Shared.makeSnackbar(mActivity, R.string.create_error, Snackbar.LENGTH_SHORT).show();
+ Snackbars.makeSnackbar(mActivity, R.string.create_error, Snackbar.LENGTH_SHORT).show();
}
mActivity.setPending(false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 3c6be6e..0abbf4e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -44,7 +44,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
-import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -54,7 +53,6 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.Parcelable;
@@ -134,8 +132,6 @@
private Model mModel;
private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
- private final Handler mHandler = new Handler(Looper.getMainLooper());
-
private View mEmptyView;
private RecyclerView mRecView;
@@ -217,8 +213,6 @@
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final Context context = inflater.getContext();
- final Resources res = context.getResources();
final View view = inflater.inflate(R.layout.fragment_directory, container, false);
mMessageBar = MessageBar.create(getChildFragmentManager());
@@ -423,7 +417,7 @@
}
CopyService.start(getActivity(), getDisplayState(this).selectedDocumentsForCopy,
- (DocumentStack) data.getParcelableExtra(CopyService.EXTRA_STACK),
+ (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
data.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_NONE));
}
@@ -678,6 +672,7 @@
checkNotNull(mMenu);
// Delegate update logic to our owning action, since specialized logic is desired.
mFragmentTuner.updateActionMenu(mMenu, mType, mNoDeleteCount == 0);
+ Menus.disableHiddenItems(mMenu);
}
@Override
@@ -799,13 +794,12 @@
private void deleteDocuments(final Selection selected) {
Context context = getActivity();
- ContentResolver resolver = context.getContentResolver();
String message = Shared.getQuantityString(context, R.plurals.deleting, selected.size());
mModel.markForDeletion(selected);
final Activity activity = getActivity();
- Shared.makeSnackbar(activity, message, Snackbar.LENGTH_LONG)
+ Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG)
.setAction(
R.string.undo,
new android.view.View.OnClickListener() {
@@ -823,7 +817,7 @@
new Model.DeletionListener() {
@Override
public void onError() {
- Shared.makeSnackbar(
+ Snackbars.makeSnackbar(
activity,
R.string.toast_failed_delete,
Snackbar.LENGTH_LONG)
@@ -1244,7 +1238,7 @@
private void copyDocuments(final List<DocumentInfo> docs, final DocumentInfo destination) {
if (!canCopy(docs, destination)) {
- Shared.makeSnackbar(
+ Snackbars.makeSnackbar(
getActivity(),
R.string.clipboard_files_cannot_paste,
Snackbar.LENGTH_SHORT)
@@ -1298,7 +1292,7 @@
void onDocumentsReady(List<DocumentInfo> docs) {
mClipper.clipDocuments(docs);
Activity activity = getActivity();
- Shared.makeSnackbar(activity,
+ Snackbars.makeSnackbar(activity,
activity.getResources().getQuantityString(
R.plurals.clipboard_files_clipped, docs.size(), docs.size()),
Snackbar.LENGTH_SHORT).show();
@@ -1608,23 +1602,25 @@
@Override
public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ boolean copyEnabled = mManaging && dirType != TYPE_RECENT_OPEN;
+ // TODO: The selection needs to be deletable.
+ boolean moveEnabled =
+ SystemProperties.getBoolean("debug.documentsui.enable_move", false);
+ menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
final MenuItem open = menu.findItem(R.id.menu_open);
final MenuItem share = menu.findItem(R.id.menu_share);
final MenuItem delete = menu.findItem(R.id.menu_delete);
final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
- final MenuItem copyToClipboard = menu.findItem(R.id.menu_copy_to_clipboard);
open.setVisible(!mManaging);
share.setVisible(mManaging);
delete.setVisible(mManaging && canDelete);
- // Disable copying from the Recents view.
- copyTo.setVisible(mManaging && dirType != TYPE_RECENT_OPEN);
- moveTo.setVisible(SystemProperties.getBoolean("debug.documentsui.enable_move", false));
-
- // Only shown in files mode.
- copyToClipboard.setVisible(false);
+ copyTo.setVisible(copyEnabled);
+ copyTo.setEnabled(copyEnabled);
+ moveTo.setVisible(moveEnabled);
+ moveTo.setEnabled(moveEnabled);
}
@Override
@@ -1638,13 +1634,14 @@
@Override
public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(dirType != TYPE_RECENT_OPEN);
+
menu.findItem(R.id.menu_share).setVisible(true);
menu.findItem(R.id.menu_delete).setVisible(canDelete);
- menu.findItem(R.id.menu_copy_to_clipboard).setVisible(true);
menu.findItem(R.id.menu_open).setVisible(false);
- menu.findItem(R.id.menu_copy_to).setVisible(false);
- menu.findItem(R.id.menu_move_to).setVisible(false);
+ menu.findItem(R.id.menu_copy_to).setVisible(true);
+ menu.findItem(R.id.menu_move_to).setVisible(true);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index f6ded4b..6b428f5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -89,8 +89,6 @@
setTheme(R.style.DocumentsNonDialogTheme);
}
- final Context context = this;
-
if (mShowAsDialog) {
mDrawer = DrawerController.createDummy();
@@ -314,41 +312,35 @@
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- final RootInfo root = getCurrentRoot();
final DocumentInfo cwd = getCurrentDirectory();
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
final MenuItem grid = menu.findItem(R.id.menu_grid);
final MenuItem list = menu.findItem(R.id.menu_list);
- final MenuItem advanced = menu.findItem(R.id.menu_advanced);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
final MenuItem settings = menu.findItem(R.id.menu_settings);
- boolean fileSizeVisible = mState.showSize && !mState.forceSize;
- if (mState.action == ACTION_CREATE
+ boolean recents = cwd == null;
+ boolean picking = mState.action == ACTION_CREATE
|| mState.action == ACTION_OPEN_TREE
- || mState.action == ACTION_OPEN_COPY_DESTINATION) {
- createDir.setVisible(cwd != null && cwd.isCreateSupported());
- mSearchManager.showMenu(false);
+ || mState.action == ACTION_OPEN_COPY_DESTINATION;
- // No display options in recent directories
- if (cwd == null) {
- grid.setVisible(false);
- list.setVisible(false);
- fileSizeVisible = false;
- }
+ createDir.setVisible(picking && !recents && cwd.isCreateSupported());
+ mSearchManager.showMenu(!picking);
- if (mState.action == ACTION_CREATE) {
- final FragmentManager fm = getFragmentManager();
- SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
- }
- } else {
- createDir.setVisible(false);
+ // No display options in recent directories
+ grid.setVisible(!(picking && recents));
+ list.setVisible(!(picking && recents));
+
+ fileSize.setVisible(fileSize.isVisible() && !picking);
+ settings.setVisible(false);
+
+ if (mState.action == ACTION_CREATE) {
+ final FragmentManager fm = getFragmentManager();
+ SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
}
- advanced.setVisible(!mState.forceAdvanced);
- fileSize.setVisible(fileSizeVisible);
- settings.setVisible(false);
+ Menus.disableHiddenItems(menu);
return true;
}
@@ -510,7 +502,7 @@
} else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
// Picking a copy destination is only used internally by us, so we
// don't need to extend permissions to the caller.
- intent.putExtra(CopyService.EXTRA_STACK, (Parcelable) mState.stack);
+ intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mState.transferMode);
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -611,7 +603,7 @@
if (result != null) {
onTaskFinished(result);
} else {
- Shared.makeSnackbar(
+ Snackbars.makeSnackbar(
DocumentsActivity.this, R.string.save_error, Snackbar.LENGTH_SHORT).show();
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
index ea0c18a..120f610 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -66,7 +66,7 @@
if (whichButton == DialogInterface.BUTTON_POSITIVE) {
CopyService.start(getActivity(), mFailedSrcList,
(DocumentStack) getActivity().getIntent().getParcelableExtra(
- CopyService.EXTRA_STACK),
+ Shared.EXTRA_STACK),
mTransferMode);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index df803fb..70ddf59 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -30,6 +30,8 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.DocumentsContract;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.util.Log;
@@ -85,32 +87,49 @@
mDrawer = DrawerController.create(this);
RootsFragment.show(getFragmentManager(), null);
- if (!mState.restored) {
- Intent intent = getIntent();
- Uri rootUri = intent.getData();
- // If we've got a specific root to display, restore that root using a dedicated
- // authority. That way a misbehaving provider won't result in an ANR.
- if (rootUri != null && !LauncherActivity.isLaunchUri(rootUri)) {
- new RestoreRootTask(rootUri).executeOnExecutor(
- ProviderExecutor.forAuthority(rootUri.getAuthority()));
+ if (mState.restored) {
+ onCurrentDirectoryChanged(ANIM_NONE);
+ } else {
+ Intent intent = getIntent();
+ Uri uri = intent.getData();
+
+ // If a non-empty stack is present in our state it was read (presumably)
+ // from EXTRA_STACK intent extra. In this case, we'll skip other means of
+ // loading or restoring the stack.
+ if (!mState.stack.isEmpty()) {
+ // When restoring from a stack, if a URI is present, it should only ever
+ // be a launch URI. Launch URIs support sensible activity management, but
+ // don't specify an real content target.
+ if (uri != null) {
+ checkState(LauncherActivity.isLaunchUri(uri));
+ }
+ onCurrentDirectoryChanged(ANIM_NONE);
+ } else if (DocumentsContract.isRootUri(this, uri)) {
+ // If we've got a specific root to display, restore that root using a dedicated
+ // authority. That way a misbehaving provider won't result in an ANR.
+ new RestoreRootTask(uri).executeOnExecutor(
+ ProviderExecutor.forAuthority(uri.getAuthority()));
} else {
+ // Finally, we try to restore a stack from recents.
new RestoreStackTask().execute();
}
+ // TODO: Ensure we're handling CopyService errors correctly across all activities.
// Show a failure dialog if there was a failed operation.
- final DocumentStack dstStack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
final int transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
CopyService.TRANSFER_MODE_NONE);
if (failure != 0) {
final ArrayList<DocumentInfo> failedSrcList =
intent.getParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST);
- FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList, dstStack,
+ FailureDialogFragment.show(
+ getFragmentManager(),
+ failure,
+ failedSrcList,
+ mState.stack,
transferMode);
}
- } else {
- onCurrentDirectoryChanged(ANIM_NONE);
}
}
@@ -126,7 +145,7 @@
// Options specific to the DocumentsActivity.
checkArgument(!intent.hasExtra(Intent.EXTRA_LOCAL_ONLY));
- final DocumentStack stack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
+ final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
if (stack != null) {
state.stack = stack;
}
@@ -206,30 +225,23 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- boolean shown = super.onPrepareOptionsMenu(menu);
-
- menu.findItem(R.id.menu_file_size).setVisible(true);
- menu.findItem(R.id.menu_advanced).setVisible(true);
+ super.onPrepareOptionsMenu(menu);
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
- boolean canCreateDir = canCreateDirectory();
-
createDir.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- createDir.setVisible(canCreateDir);
- createDir.setEnabled(canCreateDir);
+ createDir.setVisible(true);
+ createDir.setEnabled(canCreateDirectory());
+
+ pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
newWindow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
newWindow.setVisible(mProductivityDevice);
- newWindow.setEnabled(mProductivityDevice);
- pasteFromCb.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- pasteFromCb.setVisible(true);
- pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
-
- return shown;
+ Menus.disableHiddenItems(menu, pasteFromCb);
+ return true;
}
@Override
@@ -240,7 +252,7 @@
showCreateDirectoryDialog();
return true;
case R.id.menu_new_window:
- startActivity(LauncherActivity.createLaunchIntent(this));
+ createNewWindow();
return true;
case R.id.menu_paste_from_clipboard:
DirectoryFragment dir = DirectoryFragment.get(getFragmentManager());
@@ -252,6 +264,12 @@
return super.onOptionsItemSelected(item);
}
+ private void createNewWindow() {
+ Intent intent = LauncherActivity.createLaunchIntent(this);
+ intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
+ startActivity(intent);
+ }
+
@Override
void onDirectoryChanged(int anim) {
final FragmentManager fm = getFragmentManager();
@@ -322,7 +340,7 @@
try {
startActivity(intent);
} catch (ActivityNotFoundException ex2) {
- Shared.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
+ Snackbars.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index ed7333d..14a33f9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -140,6 +140,7 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
+ Menus.disableHiddenItems(menu);
return true;
}
@@ -184,7 +185,7 @@
try {
startActivity(view);
} catch (ActivityNotFoundException ex2) {
- Shared.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT)
+ Snackbars.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT)
.show();
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Menus.java b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
new file mode 100644
index 0000000..3f43a3d
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
@@ -0,0 +1,50 @@
+/*
+ * 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.documentsui;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+final class Menus {
+
+ private Menus() {}
+
+ /**
+ * Disables hidden menu items so that they are not invokable via command shortcuts
+ */
+ static void disableHiddenItems(Menu menu, MenuItem... exclusions) {
+ for (int i = 0; i < menu.size(); i++) {
+ MenuItem item = menu.getItem(i);
+ if (item.isVisible()) {
+ continue;
+ }
+ if (contains(exclusions, item)) {
+ continue;
+ }
+ item.setEnabled(false);
+ }
+ }
+
+ private static boolean contains(MenuItem[] exclusions, MenuItem item) {
+ for (int x = 0; x < exclusions.length; x++) {
+ if (exclusions[x] == item) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index 29bcd24..a4d6dc5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -16,19 +16,13 @@
package com.android.documentsui;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.app.Activity;
import android.content.Context;
-import android.support.design.widget.Snackbar;
-import android.view.View;
-/**
- * @hide
- */
+/** @hide */
public final class Shared {
public static final boolean DEBUG = true;
public static final String TAG = "Documents";
+ public static final String EXTRA_STACK = "com.android.documentsui.STACK";
/**
* Generates a formatted quantity string.
@@ -36,14 +30,4 @@
public static final String getQuantityString(Context context, int resourceId, int quantity) {
return context.getResources().getQuantityString(resourceId, quantity, quantity);
}
-
- public static final Snackbar makeSnackbar(Activity activity, int messageId, int duration) {
- return makeSnackbar(activity, activity.getResources().getText(messageId), duration);
- }
-
- public static final Snackbar makeSnackbar(Activity activity, CharSequence message, int duration)
- {
- final View view = checkNotNull(activity.findViewById(R.id.coordinator_layout));
- return Snackbar.make(view, message, duration);
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java b/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java
new file mode 100644
index 0000000..f48b298
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java
@@ -0,0 +1,37 @@
+/*
+ * 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.documentsui;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.app.Activity;
+import android.support.design.widget.Snackbar;
+import android.view.View;
+
+final class Snackbars {
+ private Snackbars() {}
+
+ public static final Snackbar makeSnackbar(Activity activity, int messageId, int duration) {
+ return Snackbars.makeSnackbar(activity, activity.getResources().getText(messageId), duration);
+ }
+
+ public static final Snackbar makeSnackbar(Activity activity, CharSequence message, int duration)
+ {
+ final View view = checkNotNull(activity.findViewById(R.id.coordinator_layout));
+ return Snackbar.make(view, message, duration);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 568e9e4..fc42c3b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -425,7 +425,7 @@
stack.push(DocumentInfo.fromUri(mResolver, dst));
final Intent copyIntent = new Intent(mContext, CopyService.class);
copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
- copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack);
+ copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
// startService(copyIntent);
return copyIntent;
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 47e24e8..368f9f7 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,3 +13,11 @@
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.recents.*
+
+-keepclassmembers class ** {
+ public void onBusEvent(**);
+ public void onInterprocessBusEvent(**);
+}
+-keepclassmembers class ** extends **.EventBus$InterprocessEvent {
+ public <init>(android.os.Bundle);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 23cc8f0..a78351a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -74,6 +74,8 @@
public class Recents extends SystemUI
implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
+ public final static int EVENT_BUS_PRIORITY = 1;
+
final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 9ce6b2c..1a0eb24 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -33,9 +33,12 @@
import android.view.ViewStub;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
@@ -44,16 +47,17 @@
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* The main Recents activity that is started from AlternateRecentsComponent.
*/
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
- RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
+public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
+
+ public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
RecentsConfiguration mConfig;
+ RecentsPackageMonitor mPackageMonitor;
long mLastTabKeyEventTime;
// Top level views
@@ -318,12 +322,17 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // Register this activity with the event bus
+ EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+
// For the non-primary user, ensure that the SystemServicesProxy and configuration is
// initialized
RecentsTaskLoader.initialize(this);
SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
mConfig = RecentsConfiguration.initialize(this, ssp);
mConfig.update(this, ssp, ssp.getWindowRect());
+ mPackageMonitor = new RecentsPackageMonitor();
// Initialize the widget host (the host id is static and does not change)
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
@@ -371,7 +380,7 @@
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
- loader.registerReceivers(this, mRecentsView);
+ mPackageMonitor.register(this);
// Update the recent tasks
updateRecentsTasks();
@@ -415,7 +424,7 @@
unregisterReceiver(mServiceBroadcastReceiver);
// Unregister any broadcast receivers for the task loader
- loader.unregisterReceivers();
+ mPackageMonitor.unregister();
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
// through SystemUI, we need to reset the config launch flags to ensure that we do not
@@ -437,6 +446,7 @@
// Stop listening for widget package changes if there was one bound
mAppWidgetHost.stopListening();
+ EventBus.getDefault().unregister(this);
}
public void onEnterAnimationTriggered() {
@@ -446,16 +456,12 @@
mRecentsView.startEnterRecentsAnimation(ctx);
if (mSearchWidgetInfo != null) {
- final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> cbRef =
- new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(
- RecentsActivity.this);
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
// Start listening for widget package changes if there is one bound
- RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = cbRef.get();
- if (cb != null) {
- mAppWidgetHost.startListening(cb);
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
}
}
});
@@ -572,10 +578,13 @@
mAfterPauseRunnable = r;
}
- /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
+ /**** EventBus events ****/
- @Override
- public void refreshSearchWidgetView() {
+ public final void onBusEvent(AppWidgetProviderChangedEvent event) {
+ refreshSearchWidgetView();
+ }
+
+ private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
int searchWidgetId = ssp.getSearchAppWidgetId(this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 0102332..fc96c11 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -20,24 +20,19 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
/** Our special app widget host for the Search widget */
public class RecentsAppWidgetHost extends AppWidgetHost {
- /* Callbacks to notify when an app package changes */
- interface RecentsAppWidgetHostCallbacks {
- void refreshSearchWidgetView();
- }
-
- RecentsAppWidgetHostCallbacks mCb;
boolean mIsListening;
public RecentsAppWidgetHost(Context context, int hostId) {
super(context, hostId);
}
- public void startListening(RecentsAppWidgetHostCallbacks cb) {
- mCb = cb;
+ public void startListening() {
if (!mIsListening) {
mIsListening = true;
super.startListening();
@@ -47,11 +42,9 @@
@Override
public void stopListening() {
if (mIsListening) {
+ mIsListening = false;
super.stopListening();
}
- // Ensure that we release any references to the callbacks
- mCb = null;
- mIsListening = false;
}
@Override
@@ -66,8 +59,8 @@
@Override
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
super.onProviderChanged(appWidgetId, appWidgetInfo);
- if (mIsListening && mCb != null) {
- mCb.refreshSearchWidgetView();
+ if (mIsListening) {
+ EventBus.getDefault().send(new AppWidgetProviderChangedEvent());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
new file mode 100644
index 0000000..4addfa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.MutableBoolean;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Represents a subscriber, which implements various event bus handler methods.
+ */
+class Subscriber {
+ private WeakReference<Object> mSubscriber;
+
+ long registrationTime;
+
+ Subscriber(Object subscriber, long registrationTime) {
+ mSubscriber = new WeakReference<>(subscriber);
+ this.registrationTime = registrationTime;
+ }
+
+ public String toString(int priority) {
+ Object sub = mSubscriber.get();
+ String id = Integer.toHexString(System.identityHashCode(sub));
+ return sub.getClass().getSimpleName() + " [0x" + id + ", P" + priority + "]";
+ }
+
+ public Object getReference() {
+ return mSubscriber.get();
+ }
+}
+
+/**
+ * Represents an event handler with a priority.
+ */
+class EventHandler {
+ int priority;
+ Subscriber subscriber;
+ EventHandlerMethod method;
+
+ EventHandler(Subscriber subscriber, EventHandlerMethod method, int priority) {
+ this.subscriber = subscriber;
+ this.method = method;
+ this.priority = priority;
+ }
+
+ @Override
+ public String toString() {
+ return subscriber.toString(priority) + " " + method.toString();
+ }
+}
+
+/**
+ * Represents the low level method handling a particular event.
+ */
+class EventHandlerMethod {
+ private Method mMethod;
+ Class<? extends EventBus.Event> eventType;
+
+ EventHandlerMethod(Method method, Class<? extends EventBus.Event> eventType) {
+ mMethod = method;
+ mMethod.setAccessible(true);
+ this.eventType = eventType;
+ }
+
+ public void invoke(Object target, EventBus.Event event)
+ throws InvocationTargetException, IllegalAccessException {
+ mMethod.invoke(target, event);
+ }
+
+ @Override
+ public String toString() {
+ return mMethod.getName() + "(" + eventType.getSimpleName() + ")";
+ }
+}
+
+/**
+ * A simple in-process event bus. It is simple because we can make assumptions about the state of
+ * SystemUI and Recent's lifecycle.
+ *
+ * <p>
+ * Currently, there is a single EventBus that handles {@link EventBus.Event}s for each subscriber
+ * on the main application thread. Publishers can send() events to synchronously call subscribers
+ * of that event, or post() events to be processed in the next run of the {@link Looper}. In
+ * addition, the EventBus supports sending and handling {@link EventBus.InterprocessEvent}s
+ * (within the same package) implemented using standard {@link BroadcastReceiver} mechanism.
+ * Interprocess events must be posted using postInterprocess() to ensure that it is dispatched
+ * correctly across processes.
+ *
+ * <p>
+ * Subscribers must be registered with a particular EventBus before they will receive events, and
+ * handler methods must match a specific signature.
+ *
+ * <p>
+ * Event method signature:<ul>
+ * <li>Methods must be public final
+ * <li>Methods must return void
+ * <li>Methods must be called "onBusEvent"
+ * <li>Methods must take one parameter, of class type deriving from {@link EventBus.Event}
+ * </ul>
+ *
+ * <p>
+ * Interprocess-Event method signature:<ul>
+ * <li>Methods must be public final
+ * <li>Methods must return void
+ * <li>Methods must be called "onInterprocessBusEvent"
+ * <li>Methods must take one parameter, of class type deriving from {@link EventBus.InterprocessEvent}
+ * </ul>
+ * </p>
+ *
+ * </p>
+ * Each subscriber can be registered with a given priority (default 1), and events will be dispatch
+ * in decreasing order of priority. For subscribers with the same priority, events will be
+ * dispatched by latest registration time to earliest.
+ *
+ * <p>
+ * Interprocess events must extend {@link EventBus.InterprocessEvent}, have a constructor which
+ * takes a {@link Bundle} and implement toBundle(). This allows us to serialize events to be sent
+ * across processes.
+ *
+ * <p>
+ * Caveats:<ul>
+ * <li>The EventBus keeps a {@link WeakReference} to the publisher to prevent memory leaks, so
+ * there must be another strong reference to the publisher for it to not get garbage-collected and
+ * continue receiving events.
+ * <li>Because the event handlers are called back using reflection, the EventBus is not intended
+ * for use in tight, performance criticial loops. For most user input/system callback events, this
+ * is generally of low enough frequency to use the EventBus.
+ * <li>Because the event handlers are called back using reflection, there will often be no
+ * references to them from actual code. The proguard configuration will be need to be updated to
+ * keep these extra methods:
+ *
+ * -keepclassmembers class ** {
+ * public void onBusEvent(**);
+ * public void onInterprocessBusEvent(**);
+ * }
+ * -keepclassmembers class ** extends **.EventBus$InterprocessEvent {
+ * public <init>(android.os.Bundle);
+ * }
+ *
+ * <li>Subscriber registration can be expensive depending on the subscriber's {@link Class}. This
+ * is only done once per class type, but if possible, it is best to pre-register an instance of
+ * that class beforehand or when idle.
+ * <li>Each event should be sent once. Events may hold internal information about the current
+ * dispatch, or may be queued to be dispatched on another thread (if posted from a non-main thread),
+ * so it may be unsafe to edit, change, or re-send the event again.
+ * <li>Events should follow a pattern of public-final POD (plain old data) objects, where they are
+ * initialized by the constructor and read by each subscriber of that event. Subscribers should
+ * never alter events as they are processed, and this enforces that pattern.
+ * </ul>
+ *
+ * <p>
+ * Future optimizations:
+ * <li>throw exception/log when a subscriber loses the reference
+ * <li>trace cost per registration & invocation
+ * <li>trace cross-process invocation
+ * <li>register(subscriber, Class<?>...) -- pass in exact class types you want registered
+ * <li>setSubscriberEventHandlerPriority(subscriber, Class<Event>, priority)
+ * <li>allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a
+ * message before invocation (ie. check if task id == this task id)
+ * <li>add postOnce() which automatically debounces
+ * <li>add postDelayed() which delays / postDelayedOnce() which delays and bounces
+ * <li>consolidate register() and registerInterprocess()
+ * <li>sendForResult<ReturnType>(Event) to send and get a result, but who will send the
+ * result?
+ * </p>
+ */
+public class EventBus extends BroadcastReceiver {
+
+ public static final String TAG = "EventBus";
+
+ /**
+ * An event super class that allows us to track internal event state across subscriber
+ * invocations.
+ *
+ * Events should not be edited by subscribers.
+ */
+ public static class Event {
+ // Indicates that this event's dispatch should be traced and logged to logcat
+ boolean trace;
+ // Indicates that this event must be posted on the EventBus's looper thread before invocation
+ boolean requiresPost;
+ // Not currently exposed, allows a subscriber to cancel further dispatch of this event
+ boolean cancelled;
+
+ // Only accessible from derived events
+ protected Event() {}
+ }
+
+ /**
+ * An inter-process event super class that allows us to track user state across subscriber
+ * invocations.
+ */
+ public static class InterprocessEvent extends Event {
+ private static final String EXTRA_USER = "_user";
+
+ // The user which this event originated from
+ public final int user;
+
+ // Only accessible from derived events
+ protected InterprocessEvent(int user) {
+ this.user = user;
+ }
+
+ /**
+ * Called from the event bus
+ */
+ protected InterprocessEvent(Bundle b) {
+ user = b.getInt(EXTRA_USER);
+ }
+
+ protected Bundle toBundle() {
+ Bundle b = new Bundle();
+ b.putInt(EXTRA_USER, user);
+ return b;
+ }
+ }
+
+ /**
+ * Proguard must also know, and keep, all methods matching this signature.
+ *
+ * -keepclassmembers class ** {
+ * public void onBusEvent(**);
+ * public void onInterprocessBusEvent(**);
+ * }
+ */
+ private static final String METHOD_PREFIX = "onBusEvent";
+ private static final String INTERPROCESS_METHOD_PREFIX = "onInterprocessBusEvent";
+
+ // Ensures that interprocess events can only be sent from a process holding this permission. */
+ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+ // Used for passing event data across process boundaries
+ private static final String EXTRA_INTERPROCESS_EVENT_BUNDLE = "interprocess_event_bundle";
+
+ // The default priority of all subscribers
+ private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
+
+ // Used for debugging everything
+ private static final boolean DEBUG_TRACE_ALL = false;
+
+ // Orders the handlers by priority and registration time
+ private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
+ @Override
+ public int compare(EventHandler h1, EventHandler h2) {
+ // Rank the handlers by priority descending, followed by registration time descending.
+ // aka. the later registered
+ if (h1.priority != h2.priority) {
+ return h2.priority - h1.priority;
+ } else {
+ return Long.compare(h2.subscriber.registrationTime, h1.subscriber.registrationTime);
+ }
+ }
+ };
+
+ // Used for initializing the default bus
+ private static final Object sLock = new Object();
+ private static EventBus sDefaultBus;
+
+ // The handler to post all events
+ private Handler mHandler;
+
+ // Keep track of whether we have registered a broadcast receiver already, so that we can
+ // unregister ourselves before re-registering again with a new IntentFilter.
+ private boolean mHasRegisteredReceiver;
+
+ /**
+ * Map from event class -> event handler list. Keeps track of the actual mapping from event
+ * to subscriber method.
+ */
+ private HashMap<Class<? extends Event>, ArrayList<EventHandler>> mEventTypeMap = new HashMap<>();
+
+ /**
+ * Map from subscriber class -> event handler method lists. Used to determine upon registration
+ * of a new subscriber whether we need to read all the subscriber's methods again using
+ * reflection or whether we can just add the subscriber to the event type map.
+ */
+ private HashMap<Class<? extends Object>, ArrayList<EventHandlerMethod>> mSubscriberTypeMap = new HashMap<>();
+
+ /**
+ * Map from interprocess event name -> interprocess event class. Used for mapping the event
+ * name after receiving the broadcast, to the event type. After which a new instance is created
+ * and posted in the local process.
+ */
+ private HashMap<String, Class<? extends InterprocessEvent>> mInterprocessEventNameMap = new HashMap<>();
+
+ /**
+ * Set of all currently registered subscribers
+ */
+ private ArrayList<Subscriber> mSubscribers = new ArrayList<>();
+
+ // For tracing
+ private int mCallCount;
+ private long mCallDurationMicros;
+
+ /**
+ * Private constructor to create an event bus for a given looper.
+ */
+ private EventBus(Looper looper) {
+ mHandler = new Handler(looper);
+ }
+
+ /**
+ * @return the default event bus for the application's main thread.
+ */
+ public static EventBus getDefault() {
+ if (sDefaultBus == null)
+ synchronized (sLock) {
+ if (sDefaultBus == null) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("New EventBus");
+ }
+ sDefaultBus = new EventBus(Looper.getMainLooper());
+ }
+ }
+ return sDefaultBus;
+ }
+
+ /**
+ * Registers a subscriber to receive events with the default priority.
+ *
+ * @param subscriber the subscriber to handle events. If this is the first instance of the
+ * subscriber's class type that has been registered, the class's methods will
+ * be scanned for appropriate event handler methods.
+ */
+ public void register(Object subscriber) {
+ registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY, null);
+ }
+
+ /**
+ * Registers a subscriber to receive events with the given priority.
+ *
+ * @param subscriber the subscriber to handle events. If this is the first instance of the
+ * subscriber's class type that has been registered, the class's methods will
+ * be scanned for appropriate event handler methods.
+ * @param priority the priority that this subscriber will receive events relative to other
+ * subscribers
+ */
+ public void register(Object subscriber, int priority) {
+ registerSubscriber(subscriber, priority, null);
+ }
+
+ /**
+ * Explicitly registers a subscriber to receive interprocess events with the default priority.
+ *
+ * @param subscriber the subscriber to handle events. If this is the first instance of the
+ * subscriber's class type that has been registered, the class's methods will
+ * be scanned for appropriate event handler methods.
+ */
+ public void registerInterprocessAsCurrentUser(Context context, Object subscriber) {
+ registerInterprocessAsCurrentUser(context, subscriber, DEFAULT_SUBSCRIBER_PRIORITY);
+ }
+
+ /**
+ * Registers a subscriber to receive interprocess events with the given priority.
+ *
+ * @param subscriber the subscriber to handle events. If this is the first instance of the
+ * subscriber's class type that has been registered, the class's methods will
+ * be scanned for appropriate event handler methods.
+ * @param priority the priority that this subscriber will receive events relative to other
+ * subscribers
+ */
+ public void registerInterprocessAsCurrentUser(Context context, Object subscriber, int priority) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("registerInterprocessAsCurrentUser(" + subscriber.getClass().getSimpleName() + ")");
+ }
+
+ // Register the subscriber normally, and update the broadcast receiver filter if this is
+ // a new subscriber type with interprocess events
+ MutableBoolean hasInterprocessEventsChanged = new MutableBoolean(false);
+ registerSubscriber(subscriber, priority, hasInterprocessEventsChanged);
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("hasInterprocessEventsChanged: " + hasInterprocessEventsChanged.value);
+ }
+ if (hasInterprocessEventsChanged.value) {
+ registerReceiverForInterprocessEvents(context);
+ }
+ }
+
+ /**
+ * Remove all EventHandlers pointing to the specified subscriber. This does not remove the
+ * mapping of subscriber type to event handler method, in case new instances of this subscriber
+ * are registered.
+ */
+ public void unregister(Object subscriber) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("unregister()");
+ }
+
+ // Fail immediately if we are being called from the non-main thread
+ long callingThreadId = Thread.currentThread().getId();
+ if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+ throw new RuntimeException("Can not unregister() a subscriber from a non-main thread.");
+ }
+
+ // Return early if this is not a registered subscriber
+ if (!findRegisteredSubscriber(subscriber, true /* removeFoundSubscriber */)) {
+ return;
+ }
+
+ Class<?> subscriberType = subscriber.getClass();
+ ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
+ if (subscriberMethods != null) {
+ // For each of the event handlers the subscriber handles, remove all references of that
+ // handler
+ for (EventHandlerMethod method : subscriberMethods) {
+ ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(method.eventType);
+ for (int i = eventHandlers.size() - 1; i >= 0; i--) {
+ if (eventHandlers.get(i).subscriber.getReference() == subscriber) {
+ eventHandlers.remove(i);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Explicit unregistration for interprocess event subscribers. This actually behaves exactly
+ * the same as unregister() since we also do not want to stop listening for specific
+ * inter-process messages in case new instances of that subscriber is registered.
+ */
+ public void unregisterInterprocess(Context context, Object subscriber) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("unregisterInterprocess()");
+ }
+ unregister(subscriber);
+ }
+
+ /**
+ * Sends an event to the subscribers of the given event type immediately. This can only be
+ * called from the same thread as the EventBus's looper thread (for the default EventBus, this
+ * is the main application thread).
+ */
+ public void send(Event event) {
+ // Fail immediately if we are being called from the non-main thread
+ long callingThreadId = Thread.currentThread().getId();
+ if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+ throw new RuntimeException("Can not send() a message from a non-main thread.");
+ }
+
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("send(" + event.getClass().getSimpleName() + ")");
+ }
+
+ // Reset the event's cancelled state
+ event.requiresPost = false;
+ event.cancelled = false;
+ queueEvent(event);
+ }
+
+ /**
+ * Post a message to the subscribers of the given event type. The messages will be posted on
+ * the EventBus's looper thread (for the default EventBus, this is the main application thread).
+ */
+ public void post(Event event) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("post(" + event.getClass().getSimpleName() + ")");
+ }
+
+ // Reset the event's cancelled state
+ event.requiresPost = true;
+ event.cancelled = false;
+ queueEvent(event);
+ }
+
+ /** Prevent post()ing an InterprocessEvent */
+ @Deprecated
+ public void post(InterprocessEvent event) {
+ throw new RuntimeException("Not supported, use postInterprocess");
+ }
+
+ /** Prevent send()ing an InterprocessEvent */
+ @Deprecated
+ public void send(InterprocessEvent event) {
+ throw new RuntimeException("Not supported, use postInterprocess");
+ }
+
+ /**
+ * Posts an interprocess event.
+ */
+ public void postInterprocess(Context context, final InterprocessEvent event) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("postInterprocess(" + event.getClass().getSimpleName() + ")");
+ }
+ String eventType = event.getClass().getName();
+ Bundle eventBundle = event.toBundle();
+ Intent intent = new Intent(eventType);
+ intent.setPackage(context.getPackageName());
+ intent.putExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE, eventBundle);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+ Intent.FLAG_RECEIVER_FOREGROUND);
+ context.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Receiver for interprocess events.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("onReceive(" + intent.getAction() + ", user " + UserHandle.myUserId() + ")");
+ }
+
+ Bundle eventBundle = intent.getBundleExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE);
+ Class<? extends InterprocessEvent> eventType = mInterprocessEventNameMap.get(intent.getAction());
+ try {
+ Constructor<? extends InterprocessEvent> ctor = eventType.getConstructor(Bundle.class);
+ send((Event) ctor.newInstance(eventBundle));
+ } catch (NoSuchMethodException|
+ InvocationTargetException|
+ InstantiationException|
+ IllegalAccessException e) {
+ Log.e(TAG, "Failed to create InterprocessEvent", e);
+ }
+ }
+
+ /**
+ * @return a dump of the current state of the EventBus
+ */
+ public String dump() {
+ StringBuilder output = new StringBuilder();
+ output.append("Registered class types:");
+ output.append("\n");
+ for (Class<?> clz : mSubscriberTypeMap.keySet()) {
+ output.append("\t");
+ output.append(clz.getSimpleName());
+ output.append("\n");
+ }
+ output.append("Event map:");
+ output.append("\n");
+ for (Class<?> clz : mEventTypeMap.keySet()) {
+ output.append("\t");
+ output.append(clz.getSimpleName());
+ output.append(" -> ");
+ output.append("\n");
+ ArrayList<EventHandler> handlers = mEventTypeMap.get(clz);
+ for (EventHandler handler : handlers) {
+ Object subscriber = handler.subscriber.getReference();
+ if (subscriber != null) {
+ String id = Integer.toHexString(System.identityHashCode(subscriber));
+ output.append("\t\t");
+ output.append(subscriber.getClass().getSimpleName());
+ output.append(" [0x" + id + ", #" + handler.priority + "]");
+ output.append("\n");
+ }
+ }
+ }
+ return output.toString();
+ }
+
+ /**
+ * Registers a new subscriber.
+ *
+ * @return return whether or not this
+ */
+ private void registerSubscriber(Object subscriber, int priority,
+ MutableBoolean hasInterprocessEventsChangedOut) {
+ // Fail immediately if we are being called from the non-main thread
+ long callingThreadId = Thread.currentThread().getId();
+ if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+ throw new RuntimeException("Can not register() a subscriber from a non-main thread.");
+ }
+
+ // Return immediately if this exact subscriber is already registered
+ if (findRegisteredSubscriber(subscriber, false /* removeFoundSubscriber */)) {
+ return;
+ }
+
+ long t1 = 0;
+ if (DEBUG_TRACE_ALL) {
+ t1 = SystemClock.currentTimeMicro();
+ logWithPid("registerSubscriber(" + subscriber.getClass().getSimpleName() + ")");
+ }
+ Subscriber sub = new Subscriber(subscriber, SystemClock.uptimeMillis());
+ Class<?> subscriberType = subscriber.getClass();
+ ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
+ if (subscriberMethods != null) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("Subscriber class type already registered");
+ }
+
+ // If we've parsed this subscriber type before, just add to the set for all the known
+ // events
+ for (EventHandlerMethod method : subscriberMethods) {
+ ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(method.eventType);
+ eventTypeHandlers.add(new EventHandler(sub, method, priority));
+ sortEventHandlersByPriority(eventTypeHandlers);
+ }
+ mSubscribers.add(sub);
+ return;
+ } else {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("Subscriber class type requires registration");
+ }
+
+ // If we are parsing this type from scratch, ensure we add it to the subscriber type
+ // map, and pull out he handler methods below
+ subscriberMethods = new ArrayList<>();
+ mSubscriberTypeMap.put(subscriberType, subscriberMethods);
+ mSubscribers.add(sub);
+ }
+
+ // Find all the valid event bus handler methods of the subscriber
+ MutableBoolean isInterprocessEvent = new MutableBoolean(false);
+ Method[] methods = subscriberType.getMethods();
+ for (Method m : methods) {
+ Class<?>[] parameterTypes = m.getParameterTypes();
+ isInterprocessEvent.value = false;
+ if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) {
+ Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
+ ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(eventType);
+ if (eventTypeHandlers == null) {
+ eventTypeHandlers = new ArrayList<>();
+ mEventTypeMap.put(eventType, eventTypeHandlers);
+ }
+ if (isInterprocessEvent.value) {
+ try {
+ // Enforce that the event must have a Bundle constructor
+ eventType.getConstructor(Bundle.class);
+
+ mInterprocessEventNameMap.put(eventType.getName(),
+ (Class<? extends InterprocessEvent>) eventType);
+ if (hasInterprocessEventsChangedOut != null) {
+ hasInterprocessEventsChangedOut.value = true;
+ }
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Expected InterprocessEvent to have a Bundle constructor");
+ }
+ }
+ EventHandlerMethod method = new EventHandlerMethod(m, eventType);
+ EventHandler handler = new EventHandler(sub, method, priority);
+ eventTypeHandlers.add(handler);
+ subscriberMethods.add(method);
+ sortEventHandlersByPriority(eventTypeHandlers);
+
+ if (DEBUG_TRACE_ALL) {
+ logWithPid(" * Method: " + m.getName() +
+ " event: " + parameterTypes[0].getSimpleName() +
+ " interprocess? " + isInterprocessEvent.value);
+ }
+ }
+ }
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("Registered " + subscriber.getClass().getSimpleName() + " in " +
+ (SystemClock.currentTimeMicro() - t1) + " microseconds");
+ }
+ }
+
+ /**
+ * Adds a new message.
+ */
+ private void queueEvent(final Event event) {
+ ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass());
+ if (eventHandlers == null) {
+ return;
+ }
+ // We need to clone the list in case a subscriber unregisters itself during traversal
+ eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone();
+ for (final EventHandler eventHandler : eventHandlers) {
+ if (eventHandler.subscriber.getReference() != null) {
+ if (event.requiresPost) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ processEvent(eventHandler, event);
+ }
+ });
+ } else {
+ processEvent(eventHandler, event);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes and dispatches the given event to the given event handler, on the thread of whoever
+ * calls this method.
+ */
+ private void processEvent(final EventHandler eventHandler, final Event event) {
+ // Skip if the event was already cancelled
+ if (event.cancelled) {
+ if (event.trace || DEBUG_TRACE_ALL) {
+ logWithPid("Event dispatch cancelled");
+ }
+ return;
+ }
+
+ try {
+ if (event.trace || DEBUG_TRACE_ALL) {
+ logWithPid(" -> " + eventHandler.toString());
+ }
+ Object sub = eventHandler.subscriber.getReference();
+ if (sub != null) {
+ long t1 = 0;
+ if (DEBUG_TRACE_ALL) {
+ t1 = SystemClock.currentTimeMicro();
+ }
+ eventHandler.method.invoke(sub, event);
+ if (DEBUG_TRACE_ALL) {
+ long duration = (SystemClock.currentTimeMicro() - t1);
+ mCallDurationMicros += duration;
+ mCallCount++;
+ logWithPid(eventHandler.method.toString() + " duration: " + duration +
+ " microseconds, avg: " + (mCallDurationMicros / mCallCount));
+ }
+ } else {
+ Log.e(TAG, "Failed to deliver event to null subscriber");
+ }
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Failed to invoke method", e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+ /**
+ * Re-registers the broadcast receiver for any new messages that we want to listen for.
+ */
+ private void registerReceiverForInterprocessEvents(Context context) {
+ if (DEBUG_TRACE_ALL) {
+ logWithPid("registerReceiverForInterprocessEvents()");
+ }
+ // Rebuild the receiver filter with the new interprocess events
+ IntentFilter filter = new IntentFilter();
+ for (String eventName : mInterprocessEventNameMap.keySet()) {
+ filter.addAction(eventName);
+ if (DEBUG_TRACE_ALL) {
+ logWithPid(" filter: " + eventName);
+ }
+ }
+ // Re-register the receiver with the new filter
+ if (mHasRegisteredReceiver) {
+ context.unregisterReceiver(this);
+ }
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter, PERMISSION_SELF, mHandler);
+ mHasRegisteredReceiver = true;
+ }
+
+ /**
+ * Returns whether this subscriber is currently registered. If {@param removeFoundSubscriber}
+ * is true, then remove the subscriber before returning.
+ */
+ private boolean findRegisteredSubscriber(Object subscriber, boolean removeFoundSubscriber) {
+ for (int i = mSubscribers.size() - 1; i >= 0; i--) {
+ Subscriber sub = mSubscribers.get(i);
+ if (sub.getReference() == subscriber) {
+ if (removeFoundSubscriber) {
+ mSubscribers.remove(i);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether {@param method} is a valid (normal or interprocess) event bus handler method
+ */
+ private boolean isValidEventBusHandlerMethod(Method method, Class<?>[] parameterTypes,
+ MutableBoolean isInterprocessEventOut) {
+ int modifiers = method.getModifiers();
+ if (Modifier.isPublic(modifiers) &&
+ Modifier.isFinal(modifiers) &&
+ method.getReturnType().equals(Void.TYPE) &&
+ parameterTypes.length == 1) {
+ if (EventBus.InterprocessEvent.class.isAssignableFrom(parameterTypes[0]) &&
+ method.getName().startsWith(INTERPROCESS_METHOD_PREFIX)) {
+ isInterprocessEventOut.value = true;
+ return true;
+ } else if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) &&
+ method.getName().startsWith(METHOD_PREFIX)) {
+ isInterprocessEventOut.value = false;
+ return true;
+ } else {
+ if (DEBUG_TRACE_ALL) {
+ if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) {
+ logWithPid(" Expected method take an Event-based parameter: " + method.getName());
+ } else if (!method.getName().startsWith(INTERPROCESS_METHOD_PREFIX) &&
+ !method.getName().startsWith(METHOD_PREFIX)) {
+ logWithPid(" Expected method start with method prefix: " + method.getName());
+ }
+ }
+ }
+ } else {
+ if (DEBUG_TRACE_ALL) {
+ if (!Modifier.isPublic(modifiers)) {
+ logWithPid(" Expected method to be public: " + method.getName());
+ } else if (!Modifier.isFinal(modifiers)) {
+ logWithPid(" Expected method to be final: " + method.getName());
+ } else if (!method.getReturnType().equals(Void.TYPE)) {
+ logWithPid(" Expected method to return null: " + method.getName());
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sorts the event handlers by priority and registration time.
+ */
+ private void sortEventHandlersByPriority(List<EventHandler> eventHandlers) {
+ Collections.sort(eventHandlers, EVENT_HANDLER_COMPARATOR);
+ }
+
+ /**
+ * Helper method to log the given {@param text} with the current process and user id.
+ */
+ private static void logWithPid(String text) {
+ Log.d(TAG, "[" + android.os.Process.myPid() + ", u" + UserHandle.myUserId() + "] " + text);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
new file mode 100644
index 0000000..52cfe18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
@@ -0,0 +1,28 @@
+/*
+ * 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.systemui.recents.events.activity;
+
+import com.android.systemui.recents.RecentsAppWidgetHost;
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent by the {@link RecentsAppWidgetHost} whenever the search provider widget changes, and
+ * subscribers can update accordingly.
+ */
+public class AppWidgetProviderChangedEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
new file mode 100644
index 0000000..3b68574
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.views.TaskStackView;
+
+/**
+ * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
+ * packages.
+ */
+public class PackagesChangedEvent extends EventBus.Event {
+
+ public final RecentsPackageMonitor monitor;
+ public final String packageName;
+ public final int userId;
+
+ public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
+ this.monitor = monitor;
+ this.packageName = packageName;
+ this.userId = userId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index e48e5f0..8f9a293 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -21,6 +21,8 @@
import android.os.Looper;
import android.os.UserHandle;
import com.android.internal.content.PackageMonitor;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.HashSet;
@@ -31,18 +33,9 @@
* Recents list.
*/
public class RecentsPackageMonitor extends PackageMonitor {
- public interface PackageCallbacks {
- public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName,
- int userId);
- }
-
- PackageCallbacks mCb;
- SystemServicesProxy mSystemServicesProxy;
/** Registers the broadcast receivers with the specified callbacks. */
- public void register(Context context, PackageCallbacks cb) {
- mSystemServicesProxy = new SystemServicesProxy(context);
- mCb = cb;
+ public void register(Context context) {
try {
// We register for events from all users, but will cross-reference them with
// packages for the current user and any profiles they have
@@ -60,17 +53,13 @@
} catch (IllegalStateException e) {
e.printStackTrace();
}
- mSystemServicesProxy = null;
- mCb = null;
}
@Override
public void onPackageRemoved(String packageName, int uid) {
- if (mCb == null) return;
-
// Notify callbacks that a package has changed
final int eventUserId = getChangingUserId();
- mCb.onPackagesChanged(this, packageName, eventUserId);
+ EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
}
@Override
@@ -81,11 +70,9 @@
@Override
public void onPackageModified(String packageName) {
- if (mCb == null) return;
-
// Notify callbacks that a package has changed
final int eventUserId = getChangingUserId();
- mCb.onPackagesChanged(this, packageName, eventUserId);
+ EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
}
/**
@@ -108,7 +95,8 @@
// If we know that the component still exists in the package, then skip
continue;
}
- if (mSystemServicesProxy.getActivityInfo(cn, userId) != null) {
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ if (ssp.getActivityInfo(cn, userId) != null) {
existingComponents.add(cn);
} else {
removedComponents.add(cn);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 760382e..39bef81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -262,8 +262,6 @@
TaskResourceLoadQueue mLoadQueue;
TaskResourceLoader mLoader;
- RecentsPackageMonitor mPackageMonitor;
-
int mMaxThumbnailCacheSize;
int mMaxIconCacheSize;
int mNumVisibleTasksLoaded;
@@ -293,7 +291,6 @@
// Initialize the proxy, cache and loaders
mSystemServicesProxy = new SystemServicesProxy(context);
- mPackageMonitor = new RecentsPackageMonitor();
mLoadQueue = new TaskResourceLoadQueue();
mApplicationIconCache = new DrawableLruCache(iconCacheSize);
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
@@ -519,17 +516,6 @@
mLoadQueue.clearTasks();
}
- /** Registers any broadcast receivers. */
- public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
- // Register the broadcast receiver to handle messages related to packages being added/removed
- mPackageMonitor.register(context, cb);
- }
-
- /** Unregisters any broadcast receivers. */
- public void unregisterReceivers() {
- mPackageMonitor.unregister();
- }
-
/**
* Handles signals from the system, trimming memory when requested to prevent us from running
* out of memory.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 73c9be9..68faccc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -59,8 +59,7 @@
* This view is the the top level layout that contains TaskStacks (which are laid out according
* to their SpaceNode bounds.
*/
-public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks,
- RecentsPackageMonitor.PackageCallbacks {
+public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
private static final String TAG = "RecentsView";
@@ -732,14 +731,4 @@
mCb.onTaskResize(t);
}
}
-
- /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
-
- @Override
- public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
- // Propagate this event down to each task stack view
- if (mTaskStackView != null) {
- mTaskStackView.onPackagesChanged(monitor, packageName, userId);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b5f29a0..76b091c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -33,11 +33,13 @@
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsActivityLaunchState;
+import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -54,7 +56,7 @@
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
- ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks {
+ ViewPool.ViewPoolConsumer<TaskView, Task> {
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
@@ -77,7 +79,7 @@
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
ViewPool<TaskView, Task> mViewPool;
- ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
+ ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
DozeTrigger mUIDozeTrigger;
DismissView mDismissAllButton;
boolean mDismissAllButtonAnimating;
@@ -97,9 +99,9 @@
Matrix mTmpMatrix = new Matrix();
Rect mTmpRect = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
- HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
- ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
- List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
+ HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
+ ArrayList<TaskView> mTaskViews = new ArrayList<>();
+ List<TaskView> mImmutableTaskViews = new ArrayList<>();
LayoutInflater mInflater;
boolean mLayersDisabled;
@@ -147,6 +149,18 @@
mCb = cb;
}
+ @Override
+ protected void onAttachedToWindow() {
+ EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ EventBus.getDefault().unregister(this);
+ }
+
/** Sets the task stack */
void setStack(TaskStack stack) {
// Set the new stack
@@ -1430,13 +1444,12 @@
postInvalidateOnAnimation();
}
- /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
+ /**** EventBus Events ****/
- @Override
- public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
+ public final void onBusEvent(PackagesChangedEvent event) {
// Compute which components need to be removed
- HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved(
- mStack.getTaskKeys(), packageName, userId);
+ HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved(
+ mStack.getTaskKeys(), event.packageName, event.userId);
// For other tasks, just remove them directly if they no longer exist
ArrayList<Task> tasks = mStack.getTasks();
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 8c830e8..3759c91 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -611,7 +611,7 @@
in_allocs[2] = (RsAllocation)C;
rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
- in_allocs, sizeof(in_allocs), nullptr,
+ in_allocs, NELEM(in_allocs), nullptr,
&call, sizeof(call), nullptr, 0);
}
@@ -646,7 +646,7 @@
in_allocs[2] = (RsAllocation)C;
rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
- in_allocs, sizeof(in_allocs), nullptr,
+ in_allocs, NELEM(in_allocs), nullptr,
&call, sizeof(call), nullptr, 0);
}
@@ -681,7 +681,7 @@
in_allocs[2] = (RsAllocation)C;
rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
- in_allocs, sizeof(in_allocs), nullptr,
+ in_allocs, NELEM(in_allocs), nullptr,
&call, sizeof(call), nullptr, 0);
}
@@ -707,7 +707,7 @@
in_allocs[2] = (RsAllocation)C;
rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
- in_allocs, sizeof(in_allocs), nullptr,
+ in_allocs, NELEM(in_allocs), nullptr,
&call, sizeof(call), nullptr, 0);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 87a0b80..b52687a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -45,28 +45,28 @@
/**
* Flag for enabling the screen magnification feature.
*
- * @see #setEnabledFeatures(int)
+ * @see #setUserAndEnabledFeatures(int, int)
*/
static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
/**
* Flag for enabling the touch exploration feature.
*
- * @see #setEnabledFeatures(int)
+ * @see #setUserAndEnabledFeatures(int, int)
*/
static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002;
/**
* Flag for enabling the filtering key events feature.
*
- * @see #setEnabledFeatures(int)
+ * @see #setUserAndEnabledFeatures(int, int)
*/
static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004;
/**
* Flag for enabling "Automatically click on mouse stop" feature.
*
- * @see #setEnabledFeatures(int)
+ * @see #setUserAndEnabledFeatures(int, int)
*/
static final int FLAG_FEATURE_AUTOCLICK = 0x00000008;
@@ -97,6 +97,8 @@
private boolean mInstalled;
+ private int mUserId;
+
private int mEnabledFeatures;
private TouchExplorer mTouchExplorer;
@@ -327,13 +329,14 @@
/* do nothing */
}
- void setEnabledFeatures(int enabledFeatures) {
- if (mEnabledFeatures == enabledFeatures) {
+ void setUserAndEnabledFeatures(int userId, int enabledFeatures) {
+ if (mEnabledFeatures == enabledFeatures && mUserId == userId) {
return;
}
if (mInstalled) {
disableFeatures();
}
+ mUserId = userId;
mEnabledFeatures = enabledFeatures;
if (mInstalled) {
enableFeatures();
@@ -350,7 +353,7 @@
resetStreamState();
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
- mAutoclickController = new AutoclickController(mContext);
+ mAutoclickController = new AutoclickController(mContext, mUserId);
addFirstEventHandler(mAutoclickController);
}
@@ -360,7 +363,7 @@
}
if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
- mScreenMagnifier = new ScreenMagnifier(mContext,
+ mScreenMagnifier = new ScreenMagnifier(mContext, mUserId,
Display.DEFAULT_DISPLAY, mAms);
addFirstEventHandler(mScreenMagnifier);
}
@@ -386,7 +389,7 @@
mEventHandler = handler;
}
- void disableFeatures() {
+ private void disableFeatures() {
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ff2a2ee..749a080 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1296,11 +1296,11 @@
inputFilter = mInputFilter;
setInputFilter = true;
}
- mInputFilter.setEnabledFeatures(flags);
+ mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
} else {
if (mHasInputFilter) {
mHasInputFilter = false;
- mInputFilter.disableFeatures();
+ mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0);
inputFilter = null;
setInputFilter = true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index 8989625..3283378 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -51,6 +51,8 @@
*
* It is expected that each instance will receive mouse events from a single mouse device. User of
* the class should handle cases where multiple mouse devices are present.
+ *
+ * Each instance is associated to a single user (and it does not handle user switch itself).
*/
public class AutoclickController implements EventStreamTransformation {
@@ -60,13 +62,15 @@
private EventStreamTransformation mNext;
private final Context mContext;
+ private final int mUserId;
// Lazily created on the first mouse motion event.
private ClickScheduler mClickScheduler;
private ClickDelayObserver mClickDelayObserver;
- public AutoclickController(Context context) {
+ public AutoclickController(Context context, int userId) {
mContext = context;
+ mUserId = userId;
}
@Override
@@ -75,7 +79,7 @@
if (mClickScheduler == null) {
Handler handler = new Handler(mContext.getMainLooper());
mClickScheduler = new ClickScheduler(handler, DEFAULT_CLICK_DELAY_MS);
- mClickDelayObserver = new ClickDelayObserver(handler);
+ mClickDelayObserver = new ClickDelayObserver(mUserId, handler);
mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler);
}
@@ -168,9 +172,11 @@
private ContentResolver mContentResolver;
private ClickScheduler mClickScheduler;
+ private final int mUserId;
- public ClickDelayObserver(Handler handler) {
+ public ClickDelayObserver(int userId, Handler handler) {
super(handler);
+ mUserId = userId;
}
/**
@@ -199,7 +205,7 @@
mContentResolver = contentResolver;
mClickScheduler = clickScheduler;
mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
- UserHandle.USER_ALL);
+ mUserId);
// Initialize mClickScheduler's initial delay value.
onChange(true, mAutoclickDelaySettingUri);
@@ -222,10 +228,9 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mAutoclickDelaySettingUri.equals(uri)) {
- // TODO: Plumb current user id down to here and use getIntForUser.
- int delay = Settings.Secure.getInt(
+ int delay = Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
- DEFAULT_CLICK_DELAY_MS);
+ DEFAULT_CLICK_DELAY_MS, mUserId);
mClickScheduler.updateDelay(delay);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
index 37276bd..8845bc0 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -29,7 +29,6 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
-import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Property;
@@ -137,6 +136,8 @@
private final AccessibilityManagerService mAms;
+ private final int mUserId;
+
private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
private final int mMultiTapTimeSlop;
private final int mTapDistanceSlop;
@@ -188,8 +189,10 @@
}
};
- public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) {
+ public ScreenMagnifier(Context context, int userId, int displayId,
+ AccessibilityManagerService service) {
mContext = context;
+ mUserId = userId;
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mAms = service;
@@ -813,33 +816,12 @@
while (mDelayedEventQueue != null) {
MotionEventInfo info = mDelayedEventQueue;
mDelayedEventQueue = info.mNext;
- final long offset = SystemClock.uptimeMillis() - info.mCachedTimeMillis;
- MotionEvent event = obtainEventWithOffsetTimeAndDownTime(info.mEvent, offset);
- MotionEvent rawEvent = obtainEventWithOffsetTimeAndDownTime(info.mRawEvent, offset);
- ScreenMagnifier.this.onMotionEvent(event, rawEvent, info.mPolicyFlags);
- event.recycle();
- rawEvent.recycle();
+ ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
+ info.mPolicyFlags);
info.recycle();
}
}
- private MotionEvent obtainEventWithOffsetTimeAndDownTime(MotionEvent event, long offset) {
- final int pointerCount = event.getPointerCount();
- PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
- PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
- for (int i = 0; i < pointerCount; i++) {
- event.getPointerCoords(i, coords[i]);
- event.getPointerProperties(i, properties[i]);
- }
- final long downTime = event.getDownTime() + offset;
- final long eventTime = event.getEventTime() + offset;
- return MotionEvent.obtain(downTime, eventTime,
- event.getAction(), pointerCount, properties, coords,
- event.getMetaState(), event.getButtonState(),
- 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
- event.getSource(), event.getFlags());
- }
-
private void clearDelayedMotionEvents() {
while (mDelayedEventQueue != null) {
MotionEventInfo info = mDelayedEventQueue;
@@ -882,17 +864,17 @@
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- Settings.Secure.putFloat(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
return null;
}
}.execute();
}
private float getPersistedScale() {
- return Settings.Secure.getFloat(mContext.getContentResolver(),
+ return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
- DEFAULT_MAGNIFICATION_SCALE);
+ DEFAULT_MAGNIFICATION_SCALE, mUserId);
}
private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
@@ -915,7 +897,6 @@
public MotionEvent mEvent;
public MotionEvent mRawEvent;
public int mPolicyFlags;
- public long mCachedTimeMillis;
public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
@@ -940,7 +921,6 @@
mEvent = MotionEvent.obtain(event);
mRawEvent = MotionEvent.obtain(rawEvent);
mPolicyFlags = policyFlags;
- mCachedTimeMillis = SystemClock.uptimeMillis();
}
public void recycle() {
@@ -964,7 +944,6 @@
mRawEvent.recycle();
mRawEvent = null;
mPolicyFlags = 0;
- mCachedTimeMillis = 0;
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 9dad7a1..ab1d775 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -37,6 +37,7 @@
import org.xmlpull.v1.XmlSerializer;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.AppGlobals;
@@ -280,8 +281,19 @@
boolean mSystemReady;
/**
- * Id of the currently selected input method.
+ * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
+ * method. This is to be synchronized with the secure settings keyed with
+ * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
+ *
+ * <p>This can be transiently {@code null} when the system is re-initializing input method
+ * settings, e.g., the system locale is just changed.</p>
+ *
+ * <p>Note that {@link #mCurId} is used to track which IME is being connected to
+ * {@link InputMethodManagerService}.</p>
+ *
+ * @see #mCurId
*/
+ @Nullable
String mCurMethodId;
/**
@@ -311,9 +323,14 @@
EditorInfo mCurAttribute;
/**
- * The input method ID of the input method service that we are currently
+ * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
* connected to or in the process of connecting to.
+ *
+ * <p>This can be {@code null} when no input method is connected.</p>
+ *
+ * @see #mCurMethodId
*/
+ @Nullable
String mCurId;
/**
@@ -918,7 +935,6 @@
|| (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
if (!updateOnlyWhenLocaleChanged) {
hideCurrentInputLocked(0, null);
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
if (DEBUG) {
@@ -1474,7 +1490,11 @@
channel.dispose();
}
- void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
+ void unbindCurrentMethodLocked(boolean resetCurrentMethodAndClient, boolean savePosition) {
+ if (resetCurrentMethodAndClient) {
+ mCurMethodId = null;
+ }
+
if (mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
@@ -1501,9 +1521,8 @@
mCurId = null;
clearCurMethodLocked();
- if (reportToClient && mCurClient != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+ if (resetCurrentMethodAndClient) {
+ unbindCurrentClientLocked();
}
}
@@ -1857,13 +1876,11 @@
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
// Here is not the perfect place to reset the switching controller. Ideally
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd10c63..617264c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20,8 +20,11 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.HOME_STACK_ID;
import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -8685,7 +8688,32 @@
Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
return;
}
- mStackSupervisor.resizeTaskLocked(task, bounds, resizeMode);
+ // Place the task in the right stack if it isn't there already based on
+ // the requested bounds.
+ // The stack transition logic is:
+ // - a null bounds on a freeform task moves that task to fullscreen
+ // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
+ // that task to freeform
+ // - otherwise the task is not moved
+ // Note it's not allowed to resize a home stack task, or a docked task.
+ int stackId = task.stack.mStackId;
+ if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID) {
+ throw new IllegalArgumentException("trying to resizeTask on a "
+ + "home or docked task");
+ }
+ if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+ } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
+ stackId = FREEFORM_WORKSPACE_STACK_ID;
+ }
+ boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
+ if (stackId != task.stack.mStackId) {
+ mStackSupervisor.moveTaskToStackUncheckedLocked(
+ task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask");
+ preserveWindow = false;
+ }
+
+ mStackSupervisor.resizeTaskLocked(task, bounds, resizeMode, preserveWindow);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -11893,7 +11921,8 @@
updateCurrentProfileIdsLocked();
mRecentTasks.clear();
- mRecentTasks.addAll(mTaskPersister.restoreTasksLocked());
+ mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
+ getUserManagerLocked().getUserIds()));
mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
mTaskPersister.startPersisting();
@@ -20644,9 +20673,6 @@
// Kill all the processes for the user.
forceStopUserLocked(userId, "finish user");
}
-
- // Explicitly remove the old information in mRecentTasks.
- mRecentTasks.removeTasksForUserLocked(userId);
}
for (int i=0; i<callbacks.size(); i++) {
@@ -20665,6 +20691,10 @@
}
}
+ void onUserRemovedLocked(int userId) {
+ mRecentTasks.removeTasksForUserLocked(userId);
+ }
+
@Override
public UserInfo getCurrentUser() {
if ((checkCallingPermission(INTERACT_ACROSS_USERS)
@@ -20949,6 +20979,13 @@
return homeActivity == null ? null : homeActivity.realActivity;
}
}
+
+ @Override
+ public void onUserRemoved(int userId) {
+ synchronized (ActivityManagerService.this) {
+ ActivityManagerService.this.onUserRemovedLocked(userId);
+ }
+ }
}
private final class SleepTokenImpl extends SleepToken {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 6b5f205..98b6ee6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3058,7 +3058,7 @@
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode) {
+ void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
if (!task.mResizeable) {
Slog.w(TAG, "resizeTask: task " + task + " not resizeable.");
return;
@@ -3066,7 +3066,7 @@
// If this is a forced resize, let it go through even if the bounds is not changing,
// as we might need a relayout due to surface size change (to/from fullscreen).
- final boolean forced = (resizeMode == RESIZE_MODE_FORCED);
+ final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
if (task.mBounds != null && task.mBounds.equals(bounds) && !forced) {
// Nothing to do here...
return;
@@ -3084,22 +3084,11 @@
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + task.taskId);
+ // Do not move the task to another stack here.
+ // This method assumes that the task is already placed in the right stack.
+ // we do not mess with that decision and we only do the resize!
- // The stack of a task is determined by its size (fullscreen vs non-fullscreen).
- // Place the task in the right stack if it isn't there already based on the requested
- // bounds.
- int stackId = task.stack.mStackId;
- if (bounds == null && stackId != FULLSCREEN_WORKSPACE_STACK_ID) {
- stackId = FULLSCREEN_WORKSPACE_STACK_ID;
- } else if (bounds != null
- && stackId != FREEFORM_WORKSPACE_STACK_ID && stackId != DOCKED_STACK_ID) {
- stackId = FREEFORM_WORKSPACE_STACK_ID;
- }
- final boolean changedStacks = stackId != task.stack.mStackId;
- if (changedStacks) {
- moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask");
- }
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + task.taskId);
final Configuration overrideConfig = task.updateOverrideConfiguration(bounds);
// This variable holds information whether the configuration didn't change in a signficant
@@ -3110,9 +3099,6 @@
ActivityRecord r = task.topRunningActivityLocked(null);
if (r != null) {
final ActivityStack stack = task.stack;
- final boolean preserveWindow = !changedStacks &&
- (resizeMode == RESIZE_MODE_USER
- || resizeMode == RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
kept = stack.ensureActivityConfigurationLocked(r, 0, preserveWindow);
// All other activities must be made visible with their correct configuration.
ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
@@ -3204,7 +3190,7 @@
* @param reason Reason the task is been moved.
* @return The stack the task was moved to.
*/
- private ActivityStack moveTaskToStackUncheckedLocked(
+ ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
final ActivityRecord r = task.getTopActivity();
final boolean wasFocused = isFrontStack(task.stack) && (topRunningActivityLocked() == r);
@@ -3261,12 +3247,15 @@
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
- resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM);
+ resizeTaskLocked(task, stack.mBounds,
+ RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID
&& task.mBounds == null && task.mLastNonFullscreenBounds != null) {
- resizeTaskLocked(task, task.mLastNonFullscreenBounds, RESIZE_MODE_SYSTEM);
+ resizeTaskLocked(task, task.mLastNonFullscreenBounds,
+ RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
} else if (stackId == DOCKED_STACK_ID) {
- resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM);
+ resizeTaskLocked(task, stack.mBounds,
+ RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
}
// The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index aa154a7..871331b 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -35,6 +35,7 @@
import android.util.Xml;
import android.os.Process;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -330,7 +331,7 @@
return null;
}
- ArrayList<TaskRecord> restoreTasksLocked() {
+ ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) {
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
@@ -362,15 +363,18 @@
if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
task);
if (task != null) {
- task.isPersistable = true;
// XXX Don't add to write queue... there is no reason to write
// out the stuff we just read, if we don't write it we will
// read the same thing again.
//mWriteQueue.add(new TaskWriteQueueItem(task));
- tasks.add(task);
final int taskId = task.taskId;
- recoveredTaskIds.add(taskId);
mStackSupervisor.setNextTaskId(taskId);
+ // Check if it's a valid user id. Don't add tasks for removed users.
+ if (ArrayUtils.contains(validUserIds, task.userId)) {
+ task.isPersistable = true;
+ tasks.add(task);
+ recoveredTaskIds.add(taskId);
+ }
} else {
Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
fileToString(taskFile));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e49a7e4..c4b57f1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -501,7 +501,6 @@
private volatile IRingtonePlayer mRingtonePlayer;
private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED;
- private int mDeviceRotation = Surface.ROTATION_0;
// Request to override default use of A2DP for media.
private boolean mBluetoothA2dpEnabled;
@@ -545,8 +544,6 @@
// If absolute volume is supported in AVRCP device
private boolean mAvrcpAbsVolSupported = false;
- private AudioOrientationEventListener mOrientationListener;
-
private static Long mLastDeviceConnectMsgTime = new Long(0);
private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
@@ -669,15 +666,7 @@
}
mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
if (mMonitorRotation) {
- mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
- .getDefaultDisplay().getRotation();
- Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation);
-
- mOrientationListener = new AudioOrientationEventListener(mContext);
- mOrientationListener.enable();
-
- // initialize rotation in AudioSystem
- setRotationForAudioSystem();
+ RotationHelper.init(mContext, mAudioHandler);
}
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
@@ -805,7 +794,7 @@
setOrientationForAudioSystem();
}
if (mMonitorRotation) {
- setRotationForAudioSystem();
+ RotationHelper.updateOrientation();
}
synchronized (mBluetoothA2dpEnabledLock) {
@@ -1058,25 +1047,6 @@
return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
}
- private class AudioOrientationEventListener
- extends OrientationEventListener {
- public AudioOrientationEventListener(Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(int orientation) {
- //Even though we're responding to phone orientation events,
- //use display rotation so audio stays in sync with video/dialogs
- int newRotation = ((WindowManager) mContext.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
- if (newRotation != mDeviceRotation) {
- mDeviceRotation = newRotation;
- setRotationForAudioSystem();
- }
- }
- }
-
///////////////////////////////////////////////////////////////////////////
// IPC methods
///////////////////////////////////////////////////////////////////////////
@@ -5066,14 +5036,13 @@
}
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
if (mMonitorRotation) {
- mOrientationListener.onOrientationChanged(0); //argument is ignored anyway
- mOrientationListener.enable();
+ RotationHelper.enable();
}
AudioSystem.setParameters("screen_state=on");
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (mMonitorRotation) {
//reduce wakeups (save current) by only listening when display is on
- mOrientationListener.disable();
+ RotationHelper.disable();
}
AudioSystem.setParameters("screen_state=off");
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
@@ -5322,6 +5291,7 @@
}
}
+ //TODO move to an external "orientation helper" class
private void setOrientationForAudioSystem() {
switch (mDeviceOrientation) {
case Configuration.ORIENTATION_LANDSCAPE:
@@ -5345,26 +5315,6 @@
}
}
- private void setRotationForAudioSystem() {
- switch (mDeviceRotation) {
- case Surface.ROTATION_0:
- AudioSystem.setParameters("rotation=0");
- break;
- case Surface.ROTATION_90:
- AudioSystem.setParameters("rotation=90");
- break;
- case Surface.ROTATION_180:
- AudioSystem.setParameters("rotation=180");
- break;
- case Surface.ROTATION_270:
- AudioSystem.setParameters("rotation=270");
- break;
- default:
- Log.e(TAG, "Unknown device rotation");
- }
- }
-
-
// Handles request to override default use of A2DP for media.
// Must be called synchronized on mConnectedDevices
public void setBluetoothA2dpOnInt(boolean on) {
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
new file mode 100644
index 0000000..f03e6c7
--- /dev/null
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -0,0 +1,209 @@
+/*
+ * 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.audio;
+
+import android.content.Context;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.util.Log;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.android.server.policy.WindowOrientationListener;
+
+/**
+ * Class to handle device rotation events for AudioService, and forward device rotation
+ * to the audio HALs through AudioSystem.
+ *
+ * The role of this class is to monitor device orientation changes, and upon rotation,
+ * verify the UI orientation. In case of a change, send the new orientation, in increments
+ * of 90deg, through AudioSystem.
+ *
+ * Note that even though we're responding to device orientation events, we always
+ * query the display rotation so audio stays in sync with video/dialogs. This is
+ * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE.
+ */
+class RotationHelper {
+
+ private static final String TAG = "AudioService.RotationHelper";
+
+ private static AudioOrientationListener sOrientationListener;
+ private static AudioWindowOrientationListener sWindowOrientationListener;
+
+ private static final Object sRotationLock = new Object();
+ private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
+
+ private static Context sContext;
+
+ /**
+ * post conditions:
+ * - (sWindowOrientationListener != null) xor (sOrientationListener != null)
+ * - sWindowOrientationListener xor sOrientationListener is enabled
+ * - sContext != null
+ */
+ static void init(Context context, Handler handler) {
+ if (context == null) {
+ throw new IllegalArgumentException("Invalid null context");
+ }
+ sContext = context;
+ sWindowOrientationListener = new AudioWindowOrientationListener(context, handler);
+ sWindowOrientationListener.enable();
+ if (!sWindowOrientationListener.canDetectOrientation()) {
+ // cannot use com.android.server.policy.WindowOrientationListener, revert to public
+ // orientation API
+ Log.i(TAG, "Not using WindowOrientationListener, reverting to OrientationListener");
+ sWindowOrientationListener.disable();
+ sWindowOrientationListener = null;
+ sOrientationListener = new AudioOrientationListener(context);
+ sOrientationListener.enable();
+ }
+ }
+
+ static void enable() {
+ if (sWindowOrientationListener != null) {
+ sWindowOrientationListener.enable();
+ } else {
+ sOrientationListener.enable();
+ }
+ updateOrientation();
+ }
+
+ static void disable() {
+ if (sWindowOrientationListener != null) {
+ sWindowOrientationListener.disable();
+ } else {
+ sOrientationListener.disable();
+ }
+ }
+
+ /**
+ * Query current display rotation and publish the change if any.
+ */
+ static void updateOrientation() {
+ // Even though we're responding to device orientation events,
+ // use display rotation so audio stays in sync with video/dialogs
+ int newRotation = ((WindowManager) sContext.getSystemService(
+ Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+ synchronized(sRotationLock) {
+ if (newRotation != sDeviceRotation) {
+ sDeviceRotation = newRotation;
+ publishRotation(sDeviceRotation);
+ }
+ }
+ }
+
+ private static void publishRotation(int rotation) {
+ Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)");
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ AudioSystem.setParameters("rotation=0");
+ break;
+ case Surface.ROTATION_90:
+ AudioSystem.setParameters("rotation=90");
+ break;
+ case Surface.ROTATION_180:
+ AudioSystem.setParameters("rotation=180");
+ break;
+ case Surface.ROTATION_270:
+ AudioSystem.setParameters("rotation=270");
+ break;
+ default:
+ Log.e(TAG, "Unknown device rotation");
+ }
+ }
+
+ /**
+ * Uses android.view.OrientationEventListener
+ */
+ final static class AudioOrientationListener extends OrientationEventListener {
+ AudioOrientationListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ updateOrientation();
+ }
+ }
+
+ /**
+ * Uses com.android.server.policy.WindowOrientationListener
+ */
+ final static class AudioWindowOrientationListener extends WindowOrientationListener {
+ private static RotationCheckThread sRotationCheckThread;
+
+ AudioWindowOrientationListener(Context context, Handler handler) {
+ super(context, handler);
+ }
+
+ public void onProposedRotationChanged(int rotation) {
+ updateOrientation();
+ if (sRotationCheckThread != null) {
+ sRotationCheckThread.endCheck();
+ }
+ sRotationCheckThread = new RotationCheckThread();
+ sRotationCheckThread.beginCheck();
+ }
+ }
+
+ /**
+ * When com.android.server.policy.WindowOrientationListener report an orientation change,
+ * the UI may not have rotated yet. This thread polls with gradually increasing delays
+ * the new orientation.
+ */
+ final static class RotationCheckThread extends Thread {
+ // how long to wait between each rotation check
+ private final int[] WAIT_TIMES_MS = { 10, 20, 50, 100, 100, 200, 200, 500 };
+ private int mWaitCounter;
+ private final Object mCounterLock = new Object();
+
+ RotationCheckThread() {
+ super("RotationCheck");
+ }
+
+ void beginCheck() {
+ synchronized(mCounterLock) {
+ mWaitCounter = 0;
+ }
+ try {
+ start();
+ } catch (IllegalStateException e) { }
+ }
+
+ void endCheck() {
+ synchronized(mCounterLock) {
+ mWaitCounter = WAIT_TIMES_MS.length;
+ }
+ }
+
+ public void run() {
+ int newRotation;
+ while (mWaitCounter < WAIT_TIMES_MS.length) {
+ updateOrientation();
+ int waitTimeMs;
+ synchronized(mCounterLock) {
+ waitTimeMs = WAIT_TIMES_MS[mWaitCounter];
+ mWaitCounter++;
+ }
+ try {
+ sleep(waitTimeMs);
+ } catch (InterruptedException e) { }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0366fff..cf09b84 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -140,6 +140,7 @@
private static final String ATTR_APP_ICON = "appIcon";
private static final String ATTR_APP_LABEL = "appLabel";
private static final String ATTR_ORIGINATING_URI = "originatingUri";
+ private static final String ATTR_ORIGINATING_UID = "originatingUid";
private static final String ATTR_REFERRER_URI = "referrerUri";
private static final String ATTR_ABI_OVERRIDE = "abiOverride";
private static final String ATTR_VOLUME_UUID = "volumeUuid";
@@ -405,6 +406,8 @@
params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
+ params.originatingUid =
+ readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
@@ -477,6 +480,7 @@
writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
+ writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4a473fd..a441cb2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -222,11 +222,17 @@
// waived if the installer is the device owner.
DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
+ final boolean isPermissionGranted =
+ (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
+ == PackageManager.PERMISSION_GRANTED);
+ final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
+ final boolean forcePermissionPrompt =
+ (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(installerPackageName);
- if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED)
- || (installerUid == Process.ROOT_UID)
- || mIsInstallerDeviceOwner) {
+ if ((isPermissionGranted
+ || isInstallerRoot
+ || mIsInstallerDeviceOwner)
+ && !forcePermissionPrompt) {
mPermissionsAccepted = true;
} else {
mPermissionsAccepted = false;
@@ -955,7 +961,9 @@
if (accepted) {
// Mark and kick off another install pass
- mPermissionsAccepted = true;
+ synchronized (mLock) {
+ mPermissionsAccepted = true;
+ }
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
} else {
destroyInternal();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c729e28..2009ccf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9686,7 +9686,8 @@
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user) {
final VerificationParams verifParams = new VerificationParams(
- null, sessionParams.originatingUri, sessionParams.referrerUri, installerUid, null);
+ null, sessionParams.originatingUri, sessionParams.referrerUri,
+ sessionParams.originatingUid, null);
verifParams.setInstallerUid(installerUid);
final OriginInfo origin;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index de106a1..6386a916 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
@@ -64,7 +65,7 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
-import com.android.server.accounts.AccountManagerService;
+import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -242,13 +243,15 @@
synchronized (mPackagesLock) {
// Prune out any partially created/partially removed users.
ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
if ((ui.partial || ui.guestToRemove) && i != 0) {
partials.add(ui);
}
}
- for (int i = 0; i < partials.size(); i++) {
+ final int partialsSize = partials.size();
+ for (int i = 0; i < partialsSize; i++) {
UserInfo ui = partials.get(i);
Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+ " (name=" + ui.name + ")");
@@ -272,7 +275,8 @@
public UserInfo getPrimaryUser() {
checkManageUsersPermission("query users");
synchronized (mPackagesLock) {
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
if (ui.isPrimary()) {
return ui;
@@ -287,7 +291,8 @@
checkManageUsersPermission("query users");
synchronized (mPackagesLock) {
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
if (ui.partial) {
continue;
@@ -323,7 +328,8 @@
// Probably a dying user
return users;
}
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
UserInfo profile = mUsers.valueAt(i);
if (!isProfileOf(user, profile)) {
continue;
@@ -1010,7 +1016,8 @@
serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
writeRestrictionsLocked(serializer, mGuestRestrictions);
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
UserInfo user = mUsers.valueAt(i);
serializer.startTag(null, TAG_USER);
serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
@@ -1587,6 +1594,9 @@
}
new Thread() {
public void run() {
+ // Clean up any ActivityManager state
+ LocalServices.getService(ActivityManagerInternal.class)
+ .onUserRemoved(userHandle);
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
removeUserStateLocked(userHandle);
@@ -1951,14 +1961,15 @@
*/
private void updateUserIdsLocked() {
int num = 0;
- for (int i = 0; i < mUsers.size(); i++) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
if (!mUsers.valueAt(i).partial) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
- for (int i = 0; i < mUsers.size(); i++) {
+ for (int i = 0; i < userSize; i++) {
if (!mUsers.valueAt(i).partial) {
newUsers[n++] = mUsers.keyAt(i);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 745874c..6b5ecdc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -584,12 +584,7 @@
}
TaskStack getDockedStackLocked() {
- for (int i = mStacks.size() - 1; i >= 0; i--) {
- TaskStack stack = mStacks.get(i);
- if (stack.mStackId == DOCKED_STACK_ID && stack.isVisibleLocked()) {
- return stack;
- }
- }
- return null;
+ final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ return (stack != null && stack.isVisibleLocked()) ? stack : null;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index df2e5e8..9da7406 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -19,8 +19,8 @@
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.RESIZE_MODE_FORCED;
import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING;
@@ -179,7 +179,7 @@
// We were using fullscreen surface during resizing. Request
// resizeTask() one last time to restore surface to window size.
mService.mActivityManager.resizeTask(
- mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_FORCED);
+ mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
}
if (mCurrentDimSide != CTRL_NONE) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 10ea4e2..f030b9a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -25,7 +25,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.RemoteException;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -59,7 +58,7 @@
/** For comparison with DisplayContent bounds. */
private Rect mTmpRect = new Rect();
- private Rect TmpRect2 = new Rect();
+ private Rect mTmpRect2 = new Rect();
/** Content limits relative to the DisplayContent this sits in. */
private Rect mBounds = new Rect();
@@ -226,10 +225,10 @@
} else if (mFullscreen) {
setBounds(null);
} else {
- TmpRect2.set(mBounds);
+ mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(
- mRotation, mDisplayContent.getDisplayInfo().rotation, TmpRect2);
- if (setBounds(TmpRect2)) {
+ mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2);
+ if (setBounds(mTmpRect2)) {
// Post message to inform activity manager of the bounds change simulating
// a one-way call. We do this to prevent a deadlock between window manager
// lock and activity manager lock been held.
@@ -383,14 +382,18 @@
mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId());
Rect bounds = null;
- final boolean dockedStackExists = mService.mStackIdToStack.get(DOCKED_STACK_ID) != null;
- if (mStackId == DOCKED_STACK_ID || (dockedStackExists
+ final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ if (mStackId == DOCKED_STACK_ID || (dockedStack != null
&& mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
// The existence of a docked stack affects the size of any static stack created since
// the docked stack occupies a dedicated region on screen.
bounds = new Rect();
displayContent.getLogicalDisplayRect(mTmpRect);
- getInitialDockedStackBounds(mTmpRect, bounds, mStackId,
+ mTmpRect2.setEmpty();
+ if (dockedStack != null) {
+ dockedStack.getRawBounds(mTmpRect2);
+ }
+ getInitialDockedStackBounds(mTmpRect, bounds, mStackId, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getWidthAdjustment());
}
@@ -400,7 +403,7 @@
// Attaching a docked stack to the display affects the size of all other static
// stacks since the docked stack occupies a dedicated region on screen.
// Resize existing static stacks so they are pushed to the side of the docked stack.
- resizeNonDockedStacks(!FULLSCREEN);
+ resizeNonDockedStacks(!FULLSCREEN, mBounds);
}
}
@@ -410,29 +413,49 @@
* @param displayRect The bounds of the display the docked stack is on.
* @param outBounds Output bounds that should be used for the stack.
* @param stackId Id of stack we are calculating the bounds for.
+ * @param dockedBounds Bounds of the docked stack.
* @param adjustment
*/
- private static void getInitialDockedStackBounds(Rect displayRect, Rect outBounds, int stackId,
- int adjustment) {
- // Docked stack start off occupying half the screen space.
+ private static void getInitialDockedStackBounds(
+ Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment) {
final boolean dockedStack = stackId == DOCKED_STACK_ID;
final boolean splitHorizontally = displayRect.width() > displayRect.height();
final boolean topOrLeftCreateMode =
WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
- final boolean placeTopOrLeft = (dockedStack && topOrLeftCreateMode)
- || (!dockedStack && !topOrLeftCreateMode);
+
outBounds.set(displayRect);
- if (placeTopOrLeft) {
- if (splitHorizontally) {
- outBounds.right = displayRect.centerX() - adjustment;
+ if (dockedStack) {
+ // The initial bounds of the docked stack when it is created half the screen space and
+ // its bounds can be adjusted after that. The bounds of all other stacks are adjusted
+ // to occupy whatever screen space the docked stack isn't occupying.
+ if (topOrLeftCreateMode) {
+ if (splitHorizontally) {
+ outBounds.right = displayRect.centerX() - adjustment;
+ } else {
+ outBounds.bottom = displayRect.centerY() - adjustment;
+ }
} else {
- outBounds.bottom = displayRect.centerY() - adjustment;
+ if (splitHorizontally) {
+ outBounds.left = displayRect.centerX() + adjustment;
+ } else {
+ outBounds.top = displayRect.centerY() + adjustment;
+ }
+ }
+ return;
+ }
+
+ // Other stacks occupy whatever space is left by the docked stack.
+ if (!topOrLeftCreateMode) {
+ if (splitHorizontally) {
+ outBounds.right = dockedBounds.left - adjustment;
+ } else {
+ outBounds.bottom = dockedBounds.top - adjustment;
}
} else {
if (splitHorizontally) {
- outBounds.left = displayRect.centerX() + adjustment;
+ outBounds.left = dockedBounds.right + adjustment;
} else {
- outBounds.top = displayRect.centerY() + adjustment;
+ outBounds.top = dockedBounds.bottom + adjustment;
}
}
}
@@ -441,11 +464,14 @@
* based on the presence of a docked stack.
* @param fullscreen If true the stacks will be resized to fullscreen, else they will be
* resized to the appropriate size based on the presence of a docked stack.
+ * @param dockedBounds Bounds of the docked stack.
*/
- private void resizeNonDockedStacks(boolean fullscreen) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ private void resizeNonDockedStacks(boolean fullscreen, Rect dockedBounds) {
+ // Not using mTmpRect because we are posting the object in a message.
+ final Rect bounds = new Rect();
+ mDisplayContent.getLogicalDisplayRect(bounds);
if (!fullscreen) {
- getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID,
+ getInitialDockedStackBounds(bounds, bounds, FULLSCREEN_WORKSPACE_STACK_ID, dockedBounds,
mDisplayContent.mDividerControllerLocked.getWidth());
}
@@ -456,11 +482,8 @@
if (otherStackId != DOCKED_STACK_ID
&& otherStackId >= FIRST_STATIC_STACK_ID
&& otherStackId <= LAST_STATIC_STACK_ID) {
- try {
- mService.mActivityManager.resizeStack(otherStackId, mTmpRect);
- } catch (RemoteException e) {
- // This will not happen since we are in the same process.
- }
+ mService.mH.sendMessage(
+ mService.mH.obtainMessage(RESIZE_STACK, otherStackId, -1, bounds));
}
}
}
@@ -488,7 +511,7 @@
if (mStackId == DOCKED_STACK_ID) {
// Docked stack was detached from the display, so we no longer need to restrict the
// region of the screen other static stacks occupy. Go ahead and make them fullscreen.
- resizeNonDockedStacks(FULLSCREEN);
+ resizeNonDockedStacks(FULLSCREEN, null);
}
close();
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 982bae0..53f55cd 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -423,7 +423,7 @@
try {
byteBuffer = recvKernelReply();
} catch (ErrnoException e) {
- Log.w(TAG, "ErrnoException: ", e);
+ if (stillRunning()) { Log.w(TAG, "ErrnoException: ", e); }
break;
}
final long whenMs = SystemClock.elapsedRealtime();
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 c6c7497..f4ffe2e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -50,11 +50,11 @@
private final File mDeviceOwnerFile;
private final File mProfileOwnerBase;
- public OwnersTestable(Context context, File dataDir) {
+ public OwnersTestable(DpmMockContext context) {
super(context);
- mLegacyFile = new File(dataDir, LEGACY_FILE);
- mDeviceOwnerFile = new File(dataDir, DEVICE_OWNER_FILE);
- mProfileOwnerBase = new File(dataDir, PROFILE_OWNER_FILE_BASE);
+ mLegacyFile = new File(context.dataDir, LEGACY_FILE);
+ mDeviceOwnerFile = new File(context.dataDir, DEVICE_OWNER_FILE);
+ mProfileOwnerBase = new File(context.dataDir, PROFILE_OWNER_FILE_BASE);
}
@Override
@@ -90,27 +90,15 @@
public final File dataDir;
- public final File systemUserDataDir;
- public final File secondUserDataDir;
-
private MockInjector(DpmMockContext context, File dataDir) {
super(context);
this.context = context;
this.dataDir = dataDir;
-
- systemUserDataDir = new File(dataDir, "user0");
- DpmTestUtils.clearDir(dataDir);
-
- secondUserDataDir = new File(dataDir, "user" + DpmMockContext.CALLER_USER_HANDLE);
- DpmTestUtils.clearDir(secondUserDataDir);
-
- when(context.environment.getUserSystemDirectory(
- eq(DpmMockContext.CALLER_USER_HANDLE))).thenReturn(secondUserDataDir);
}
@Override
Owners newOwners() {
- return new OwnersTestable(context, dataDir);
+ return new OwnersTestable(context);
}
@Override
@@ -165,7 +153,7 @@
@Override
String getDevicePolicyFilePathForSystemUser() {
- return systemUserDataDir.getAbsolutePath();
+ return context.systemUserDataDir.getAbsolutePath();
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0072f52..5b23798 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -91,11 +91,13 @@
admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
- setUpPackageManagerForAdmin(admin1);
- setUpPackageManagerForAdmin(admin2);
- setUpPackageManagerForAdmin(admin3);
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+ setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
+ setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
- setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+ setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ DpmMockContext.CALLER_UID);
+
setUpPackageInfo();
setUpUserManager();
}
@@ -105,7 +107,7 @@
* the actual ResolveInfo for the admin component, but we need to mock PM so it'll return
* it for user {@link DpmMockContext#CALLER_USER_HANDLE}.
*/
- private void setUpPackageManagerForAdmin(ComponentName admin) {
+ private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) {
final Intent resolveIntent = new Intent();
resolveIntent.setComponent(admin);
final List<ResolveInfo> realResolveInfo =
@@ -115,32 +117,36 @@
assertNotNull(realResolveInfo);
assertEquals(1, realResolveInfo.size());
+ // We need to change AI, so set a clone.
+ realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
+
// We need to rewrite the UID in the activity info.
- realResolveInfo.get(0).activityInfo.applicationInfo.uid = DpmMockContext.CALLER_UID;
+ realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers(
MockUtils.checkIntentComponent(admin),
eq(PackageManager.GET_META_DATA
| PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
- eq(DpmMockContext.CALLER_USER_HANDLE)
- );
+ eq(UserHandle.getUserId(packageUid)));
}
/**
* Set up a mock result for {@link IPackageManager#getApplicationInfo} for user
* {@link DpmMockContext#CALLER_USER_HANDLE}.
*/
- private void setUpApplicationInfo(int enabledSetting) throws Exception {
- final ApplicationInfo ai = mRealTestContext.getPackageManager().getApplicationInfo(
- admin1.getPackageName(),
- PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
+ private void setUpApplicationInfo(int enabledSetting, int packageUid) throws Exception {
+ final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+ mRealTestContext.getPackageManager().getApplicationInfo(
+ admin1.getPackageName(),
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
ai.enabledSetting = enabledSetting;
+ ai.uid = packageUid;
doReturn(ai).when(mContext.ipackageManager).getApplicationInfo(
eq(admin1.getPackageName()),
eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
- eq(DpmMockContext.CALLER_USER_HANDLE));
+ eq(UserHandle.getUserId(packageUid)));
}
/**
@@ -193,16 +199,8 @@
}).when(mContext.userManager).getApplicationRestrictions(
anyString(), any(UserHandle.class));
- // System user is always running.
- when(mContext.userManager.isUserRunning(MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)))
- .thenReturn(true);
-
- // Set up (default) UserInfo for CALLER_USER_HANDLE.
- final UserInfo uh = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
- "user" + DpmMockContext.CALLER_USER_HANDLE, 0);
-
- when(mContext.userManager.getUserInfo(eq(DpmMockContext.CALLER_USER_HANDLE)))
- .thenReturn(uh);
+ // Add the first secondary user.
+ mContext.addUser(DpmMockContext.CALLER_USER_HANDLE, 0);
}
private void setAsProfileOwner(ComponentName admin) {
@@ -309,7 +307,8 @@
// Next, add one more admin.
// Before doing so, update the application info, now it's enabled.
- setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ DpmMockContext.CALLER_UID);
dpm.setActiveAdmin(admin2, /* replace =*/ false);
@@ -354,6 +353,35 @@
mContext.callerPermissions.remove("android.permission.INTERACT_ACROSS_USERS_FULL");
}
+ public void testSetActiveAdmin_multiUsers() throws Exception {
+
+ final int ANOTHER_USER_ID = 100;
+ final int ANOTHER_ADMIN_UID = UserHandle.getUid(ANOTHER_USER_ID, 20456);
+
+ mMockContext.addUser(ANOTHER_USER_ID, 0); // Add one more user.
+
+ // Set up pacakge manager for the other user.
+ setUpPackageManagerForAdmin(admin2, ANOTHER_ADMIN_UID);
+ setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ ANOTHER_ADMIN_UID);
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+ dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+ mMockContext.binder.callingUid = ANOTHER_ADMIN_UID;
+ dpm.setActiveAdmin(admin2, /* replace =*/ false);
+
+
+ mMockContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ assertTrue(dpm.isAdminActive(admin1));
+ assertFalse(dpm.isAdminActive(admin2));
+
+ mMockContext.binder.callingUid = ANOTHER_ADMIN_UID;
+ assertFalse(dpm.isAdminActive(admin1));
+ assertTrue(dpm.isAdminActive(admin2));
+ }
+
/**
* Test for:
* {@link DevicePolicyManager#setActiveAdmin}
@@ -400,9 +428,11 @@
// having MANAGE_DEVICE_ADMINS.
mContext.callerPermissions.clear();
+ // Change the caller, and call into DPMS directly with a different user-id.
+
mContext.binder.callingUid = 1234567;
try {
- dpm.removeActiveAdmin(admin1);
+ dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
fail("Didn't throw SecurityException");
} catch (SecurityException expected) {
}
@@ -412,7 +442,7 @@
* Test for:
* {@link DevicePolicyManager#removeActiveAdmin}
*/
- public void testRemoveActiveAdmin_fromDifferentUserWithMINTERACT_ACROSS_USERS_FULL() {
+ public void testRemoveActiveAdmin_fromDifferentUserWithINTERACT_ACROSS_USERS_FULL() {
mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
// Add admin1.
@@ -424,8 +454,11 @@
// Different user, but should work, because caller has proper permissions.
mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+ // Change the caller, and call into DPMS directly with a different user-id.
mContext.binder.callingUid = 1234567;
- dpm.removeActiveAdmin(admin1);
+
+ dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
@@ -498,9 +531,14 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
- // Call from a process on the system user.
+ // In this test, change the caller user to "system".
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ // Make sure admin1 is installed on system user.
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ DpmMockContext.CALLER_SYSTEM_USER_UID);
+
// DO needs to be an DA.
dpm.setActiveAdmin(admin1, /* replace =*/ false);
@@ -536,8 +574,6 @@
// Call from a process on the system user.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- // DO needs to be an DA.
- dpm.setActiveAdmin(admin1, /* replace =*/ false);
try {
dpm.setDeviceOwner("a.b.c");
fail("Didn't throw IllegalArgumentException");
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
index 325bf9f..b80f3bf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
import android.app.admin.DevicePolicyManager;
+import android.os.UserHandle;
/**
* Overrides {@link #DevicePolicyManager} for dependency injection.
@@ -31,6 +32,6 @@
@Override
public int myUserId() {
- return DpmMockContext.CALLER_USER_HANDLE;
+ return UserHandle.getUserId(dpms.context.binder.callingUid);
}
}
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 3b30a37..7b36e88 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -27,6 +27,7 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.media.IAudioService;
import android.os.Bundle;
import android.os.Handler;
@@ -43,8 +44,10 @@
import java.util.ArrayList;
import java.util.List;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
/**
* Context used throughout DPMS tests.
@@ -58,12 +61,12 @@
/**
* UID corresponding to {@link #CALLER_USER_HANDLE}.
*/
- public static final int CALLER_UID = UserHandle.PER_USER_RANGE * CALLER_USER_HANDLE + 123;
+ public static final int CALLER_UID = UserHandle.getUid(CALLER_USER_HANDLE, 20123);
/**
* UID used when a caller is on the system user.
*/
- public static final int CALLER_SYSTEM_USER_UID = 123;
+ public static final int CALLER_SYSTEM_USER_UID = 20321;
/**
* PID of the caller.
@@ -164,6 +167,9 @@
*/
public final Context spiedContext;
+ public final File dataDir;
+ public final File systemUserDataDir;
+
public final MockBinder binder;
public final EnvironmentForMock environment;
public final SystemPropertiesForMock systemProperties;
@@ -184,8 +190,14 @@
public final List<String> callerPermissions = new ArrayList<>();
- public DpmMockContext(Context context) {
+ private final ArrayList<UserInfo> mUserInfos = new ArrayList<>();
+
+ public DpmMockContext(Context context, File dataDir) {
realTestContext = context;
+
+ this.dataDir = dataDir;
+ DpmTestUtils.clearDir(dataDir);
+
binder = new MockBinder();
environment = mock(EnvironmentForMock.class);
systemProperties= mock(SystemPropertiesForMock.class);
@@ -205,6 +217,39 @@
packageManager = spy(context.getPackageManager());
spiedContext = mock(Context.class);
+
+ // Add the system user
+ systemUserDataDir = addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY);
+
+ // System user is always running.
+ when(userManager.isUserRunning(MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+ }
+
+ public File addUser(int userId, int flags) {
+
+ // Set up (default) UserInfo for CALLER_USER_HANDLE.
+ final UserInfo uh = new UserInfo(userId, "user" + userId, flags);
+ when(userManager.getUserInfo(eq(userId))).thenReturn(uh);
+
+ mUserInfos.add(uh);
+ when(userManager.getUsers()).thenReturn(mUserInfos);
+
+ // Create a data directory.
+ final File dir = new File(dataDir, "user" + userId);
+ DpmTestUtils.clearDir(dir);
+
+ when(environment.getUserSystemDirectory(eq(userId))).thenReturn(dir);
+ return dir;
+ }
+
+ /**
+ * Add multiple users at once. They'll all have flag 0.
+ */
+ public void addUsers(int... userIds) {
+ for (int userId : userIds) {
+ addUser(userId, 0);
+ }
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 6f9f6ab..63bf125 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -34,10 +34,9 @@
super.setUp();
mRealTestContext = super.getContext();
- mMockContext = new DpmMockContext(super.getContext());
- dataDir = new File(mRealTestContext.getCacheDir(), "test-data");
- DpmTestUtils.clearDir(dataDir);
+ mMockContext = new DpmMockContext(
+ mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index 44a851a..7506273 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -17,6 +17,8 @@
package com.android.server.devicepolicy;
import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
import android.util.Printer;
@@ -41,6 +43,15 @@
return list == null ? 0 : list.size();
}
+ public static <T extends Parcelable> T cloneParcelable(T source) {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(source, 0);
+ p.setDataPosition(0);
+ final T clone = p.readParcelable(DpmTestUtils.class.getClassLoader());
+ p.recycle();
+ return clone;
+ }
+
public static Printer LOG_PRINTER = new Printer() {
@Override
public void println(String x) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index a07d615..4a39614 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -69,22 +69,12 @@
}
}
- private void addUsersToUserManager(int... userIds) {
- final ArrayList<UserInfo> userInfos = new ArrayList<>();
- for (int userId : userIds) {
- final UserInfo ui = new UserInfo();
- ui.id = userId;
- userInfos.add(ui);
- }
- when(getContext().userManager.getUsers()).thenReturn(userInfos);
- }
-
public void testUpgrade01() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test01/input.xml"));
@@ -111,7 +101,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -123,11 +113,11 @@
}
public void testUpgrade02() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test02/input.xml"));
@@ -156,7 +146,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertTrue(owners.hasDeviceOwner());
@@ -171,11 +161,11 @@
}
public void testUpgrade03() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test03/input.xml"));
@@ -212,7 +202,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -235,11 +225,11 @@
}
public void testUpgrade04() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test04/input.xml"));
@@ -281,7 +271,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertTrue(owners.hasDeviceOwner());
@@ -309,11 +299,11 @@
}
public void testUpgrade05() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test05/input.xml"));
@@ -341,7 +331,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -356,11 +346,11 @@
}
public void testUpgrade06() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
readAsset("OwnersTest/test06/input.xml"));
@@ -387,7 +377,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -401,9 +391,9 @@
}
public void testRemoveExistingFiles() throws Exception {
- addUsersToUserManager(10, 11, 20, 21);
+ getContext().addUsers(10, 11, 20, 21);
- final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+ final OwnersTestable owners = new OwnersTestable(getContext());
// First, migrate to create new-style config files.
createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
index b458d9b..7628c5c 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
@@ -188,7 +188,7 @@
// This call should be done while the rendernode's displaylist is produced.
// For simplicity of this test we do this before we kick off the draw.
mContent.getLocationInSurface(surfaceOrigin);
- mRenderer.setContentOverdrawProtectionBounds(surfaceOrigin[0], surfaceOrigin[1],
+ mRenderer.setContentDrawBounds(surfaceOrigin[0], surfaceOrigin[1],
surfaceOrigin[0] + mContent.getWidth(),
surfaceOrigin[1] + mContent.getHeight());
// Determine new position for frame.