Merge "Split PhoneWindowManager.beginLayoutLw."
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index f70423f..b44aab7 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -63,6 +63,7 @@
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
+ final boolean mHidden;
Bundle mSavedFragmentState;
@@ -78,6 +79,7 @@
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
+ mHidden = frag.mHidden;
}
public FragmentState(Parcel in) {
@@ -90,6 +92,7 @@
mRetainInstance = in.readInt() != 0;
mDetached = in.readInt() != 0;
mArguments = in.readBundle();
+ mHidden = in.readInt() != 0;
mSavedFragmentState = in.readBundle();
}
@@ -117,6 +120,7 @@
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
+ mInstance.mHidden = mHidden;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
@@ -138,6 +142,7 @@
dest.writeInt(mRetainInstance ? 1 : 0);
dest.writeInt(mDetached ? 1 : 0);
dest.writeBundle(mArguments);
+ dest.writeInt(mHidden ? 1 : 0);
dest.writeBundle(mSavedFragmentState);
}
@@ -460,6 +465,9 @@
// If set this fragment is being retained across the current config change.
boolean mRetaining;
+ // If set this fragment's loaders are being retained across the current config change.
+ boolean mRetainLoader;
+
// If set this fragment has menu items to contribute.
boolean mHasMenu;
@@ -2407,7 +2415,7 @@
mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
- if (mRetaining) {
+ if (mRetainLoader) {
mLoaderManager.doRetain();
} else {
mLoaderManager.doStop();
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 28dadfa..1b45137 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -341,6 +341,7 @@
*/
public void doLoaderStop(boolean retain) {
mHost.doLoaderStop(retain);
+ mHost.mFragmentManager.setRetainLoader(retain);
}
/**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 132ffef..51d6132 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -869,6 +869,17 @@
}
}
+ void setRetainLoader(boolean retain) {
+ if (mActive != null) {
+ for (int i=0; i<mActive.size(); i++) {
+ Fragment f = mActive.get(i);
+ if (f != null) {
+ f.mRetainLoader = retain;
+ }
+ }
+ }
+ }
+
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
if (DEBUG && false) Log.v(TAG, "moveToState: " + f
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d7ecbfe..04b1a3b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -114,6 +114,12 @@
/** File name in an APK for the Android manifest. */
private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+ /**
+ * File name in an APK for bytecode. There may be additional bytecode files
+ * but this one is always required for an APK that has code.
+ */
+ private static final String BYTECODE_FILENAME = "classes.dex";
+
/** Path prefix for apps on expanded storage */
private static final String MNT_EXPAND = "/mnt/expand/";
@@ -615,6 +621,7 @@
public final static int PARSE_IS_PRIVILEGED = 1<<7;
public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
+ public final static int PARSE_ENFORCE_CODE = 1<<10;
private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
@@ -1066,8 +1073,11 @@
private static void collectCertificates(Package pkg, File apkFile, int flags)
throws PackageParserException {
+ final boolean requireCode = ((flags & PARSE_ENFORCE_CODE) != 0)
+ && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0);
final String apkPath = apkFile.getAbsolutePath();
+ boolean codeFound = false;
StrictJarFile jarFile = null;
try {
jarFile = new StrictJarFile(apkPath);
@@ -1089,13 +1099,23 @@
final ZipEntry entry = i.next();
if (entry.isDirectory()) continue;
- if (entry.getName().startsWith("META-INF/")) continue;
- if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;
+
+ final String entryName = entry.getName();
+ if (entryName.startsWith("META-INF/")) continue;
+ if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue;
+ if (entryName.equals(BYTECODE_FILENAME)) {
+ codeFound = true;
+ }
toVerify.add(entry);
}
}
+ if (!codeFound && requireCode) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Package " + apkPath + " code is missing");
+ }
+
// Verify that entries are signed consistently with the first entry
// we encountered. Note that for splits, certificates may have
// already been populated during an earlier parse of a base APK.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 21ba7bd..121a187 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -359,6 +359,14 @@
}
}
+ public void requestColorTransform(int displayId, int colorTransformId) {
+ try {
+ mDm.requestColorTransform(displayId, colorTransformId);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to request color transform.", ex);
+ }
+ }
+
public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
String name, int width, int height, int densityDpi, Surface surface, int flags,
VirtualDisplay.Callback callback, Handler handler) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 4486dd4..8a1abf1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -59,6 +59,9 @@
// No permissions required.
WifiDisplayStatus getWifiDisplayStatus();
+ // Requires CONFIGURE_DISPLAY_COLOR_TRANSFORM
+ void requestColorTransform(int displayId, int colorTransformId);
+
// Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
// MediaProjection token for certain combinations of flags.
int createVirtualDisplay(in IVirtualDisplayCallback callback,
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index bad94fc..8e86a53 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -463,13 +463,15 @@
public abstract long getCpuPowerMaUs(int which);
/**
- * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
+ * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed for a
+ * given CPU cluster.
+ * @param cluster the index of the CPU cluster.
* @param step the index of the CPU speed. This is not the actual speed of the CPU.
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
- * @see BatteryStats#getCpuSpeedSteps()
+ * @see PowerProfile.getNumCpuClusters()
+ * @see PowerProfile.getNumSpeedStepsInCpuCluster(int)
*/
- @Deprecated
- public abstract long getTimeAtCpuSpeed(int step, int which);
+ public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);
public static abstract class Sensor {
/*
@@ -2276,9 +2278,6 @@
public abstract Map<String, ? extends Timer> getKernelWakelockStats();
- /** Returns the number of different speeds that the CPU can run at */
- public abstract int getCpuSpeedSteps();
-
public abstract void writeToParcelWithoutUids(Parcel out, int flags);
private final static void formatTimeRaw(StringBuilder out, long seconds) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 35c4192..1269ad9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,7 +16,10 @@
package android.view;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.content.res.CompatibilityInfo;
+import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -30,6 +33,8 @@
import java.util.Arrays;
+import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM;
+
/**
* Provides information about the size and density of a logical display.
* <p>
@@ -679,6 +684,49 @@
}
/**
+ * Request the display applies a color transform.
+ * @hide
+ */
+ @RequiresPermission(CONFIGURE_DISPLAY_COLOR_TRANSFORM)
+ public void requestColorTransform(ColorTransform colorTransform) {
+ mGlobal.requestColorTransform(mDisplayId, colorTransform.getId());
+ }
+
+ /**
+ * Returns the active color transform of this display
+ * @hide
+ */
+ public ColorTransform getColorTransform() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.getColorTransform();
+ }
+ }
+
+ /**
+ * Returns the default color transform of this display
+ * @hide
+ */
+ public ColorTransform getDefaultColorTransform() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.getDefaultColorTransform();
+ }
+ }
+
+ /**
+ * Gets the supported color transforms of this device.
+ * @hide
+ */
+ public ColorTransform[] getSupportedColorTransforms() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ ColorTransform[] transforms = mDisplayInfo.supportedColorTransforms;
+ return Arrays.copyOf(transforms, transforms.length);
+ }
+ }
+
+ /**
* Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating
* the phase offset of the VSYNC events provided by Choreographer relative to the
* display refresh. For example, if Choreographer reports that the refresh occurred
@@ -1054,4 +1102,89 @@
}
};
}
+
+ /**
+ * A color transform supported by a given display.
+ *
+ * @see Display#getSupportedColorTransforms()
+ * @hide
+ */
+ public static final class ColorTransform implements Parcelable {
+ public static final ColorTransform[] EMPTY_ARRAY = new ColorTransform[0];
+
+ private final int mId;
+ private final int mColorTransform;
+
+ public ColorTransform(int id, int colorTransform) {
+ mId = id;
+ mColorTransform = colorTransform;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public int getColorTransform() {
+ return mColorTransform;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof ColorTransform)) {
+ return false;
+ }
+ ColorTransform that = (ColorTransform) other;
+ return mId == that.mId
+ && mColorTransform == that.mColorTransform;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 1;
+ hash = hash * 17 + mId;
+ hash = hash * 17 + mColorTransform;
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("{")
+ .append("id=").append(mId)
+ .append(", colorTransform=").append(mColorTransform)
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private ColorTransform(Parcel in) {
+ this(in.readInt(), in.readInt());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int parcelableFlags) {
+ out.writeInt(mId);
+ out.writeInt(mColorTransform);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<ColorTransform> CREATOR
+ = new Parcelable.Creator<ColorTransform>() {
+ @Override
+ public ColorTransform createFromParcel(Parcel in) {
+ return new ColorTransform(in);
+ }
+
+ @Override
+ public ColorTransform[] newArray(int size) {
+ return new ColorTransform[size];
+ }
+ };
+ }
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index cf17990..ee76274 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -169,6 +169,15 @@
*/
public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
+ /** The active color transform. */
+ public int colorTransformId;
+
+ /** The default color transform. */
+ public int defaultColorTransformId;
+
+ /** The list of supported color transforms */
+ public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY;
+
/**
* The logical display density which is the basis for density-independent
* pixels.
@@ -279,6 +288,8 @@
&& rotation == other.rotation
&& modeId == other.modeId
&& defaultModeId == other.defaultModeId
+ && colorTransformId == other.colorTransformId
+ && defaultColorTransformId == other.defaultColorTransformId
&& logicalDensityDpi == other.logicalDensityDpi
&& physicalXDpi == other.physicalXDpi
&& physicalYDpi == other.physicalYDpi
@@ -317,6 +328,10 @@
modeId = other.modeId;
defaultModeId = other.defaultModeId;
supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
+ colorTransformId = other.colorTransformId;
+ defaultColorTransformId = other.defaultColorTransformId;
+ supportedColorTransforms = Arrays.copyOf(
+ other.supportedColorTransforms, other.supportedColorTransforms.length);
logicalDensityDpi = other.logicalDensityDpi;
physicalXDpi = other.physicalXDpi;
physicalYDpi = other.physicalYDpi;
@@ -353,6 +368,13 @@
for (int i = 0; i < nModes; i++) {
supportedModes[i] = Display.Mode.CREATOR.createFromParcel(source);
}
+ colorTransformId = source.readInt();
+ defaultColorTransformId = source.readInt();
+ int nColorTransforms = source.readInt();
+ supportedColorTransforms = new Display.ColorTransform[nColorTransforms];
+ for (int i = 0; i < nColorTransforms; i++) {
+ supportedColorTransforms[i] = Display.ColorTransform.CREATOR.createFromParcel(source);
+ }
logicalDensityDpi = source.readInt();
physicalXDpi = source.readFloat();
physicalYDpi = source.readFloat();
@@ -390,6 +412,12 @@
for (int i = 0; i < supportedModes.length; i++) {
supportedModes[i].writeToParcel(dest, flags);
}
+ dest.writeInt(colorTransformId);
+ dest.writeInt(defaultColorTransformId);
+ dest.writeInt(supportedColorTransforms.length);
+ for (int i = 0; i < supportedColorTransforms.length; i++) {
+ supportedColorTransforms[i].writeToParcel(dest, flags);
+ }
dest.writeInt(logicalDensityDpi);
dest.writeFloat(physicalXDpi);
dest.writeFloat(physicalYDpi);
@@ -461,6 +489,24 @@
return result;
}
+ public Display.ColorTransform getColorTransform() {
+ return findColorTransform(colorTransformId);
+ }
+
+ public Display.ColorTransform getDefaultColorTransform() {
+ return findColorTransform(defaultColorTransformId);
+ }
+
+ private Display.ColorTransform findColorTransform(int colorTransformId) {
+ for (int i = 0; i < supportedColorTransforms.length; i++) {
+ Display.ColorTransform colorTransform = supportedColorTransforms[i];
+ if (colorTransform.getId() == colorTransformId) {
+ return colorTransform;
+ }
+ }
+ throw new IllegalStateException("Unable to locate color transform: " + colorTransformId);
+ }
+
public void getAppMetrics(DisplayMetrics outMetrics) {
getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
@@ -562,6 +608,12 @@
sb.append(defaultModeId);
sb.append(", modes ");
sb.append(Arrays.toString(supportedModes));
+ sb.append(", colorTransformId ");
+ sb.append(colorTransformId);
+ sb.append(", defaultColorTransformId ");
+ sb.append(defaultColorTransformId);
+ sb.append(", supportedColorTransforms ");
+ sb.append(Arrays.toString(supportedColorTransforms));
sb.append(", rotation ");
sb.append(rotation);
sb.append(", density ");
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 420f7a1..dcef142 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -108,6 +108,11 @@
private Choreographer mChoreographer;
private boolean mRootNodeNeedsUpdate;
+ // In case of multi threaded render nodes, these bounds indicate the content bounds against
+ // which the backdrop needs to be cropped against.
+ private final Rect mCurrentContentBounds = new Rect();
+ private final Rect mStagedContentBounds = new Rect();
+
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -307,6 +312,47 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ /**
+ * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+ * rendernode of the UI thread.
+ * @param node The node to add.
+ * @param placeFront If true, the render node will be placed in front of the content node,
+ * otherwise behind the content node.
+ */
+ public void addRenderNode(RenderNode node, boolean placeFront) {
+ nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+ }
+
+ /**
+ * Only especially added render nodes can be removed.
+ * @param node The node which was added via addRenderNode which should get removed again.
+ */
+ public void removeRenderNode(RenderNode node) {
+ nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * Draws a particular render node. If the node is not the content node, only the additional
+ * nodes will get drawn and the content remains untouched.
+ * @param node The node to be drawn.
+ */
+ public void drawRenderNode(RenderNode node) {
+ nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+ * will be prevented to overdraw this area. It will be synchronized with the draw call.
+ * This should be updated in the content view's draw call.
+ * @param left The left side of the protected bounds.
+ * @param top The top side of the protected bounds.
+ * @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) {
+ mStagedContentBounds.set(left, top, right, bottom);
+ }
+
@Override
void invalidateRoot() {
mRootNodeNeedsUpdate = true;
@@ -320,6 +366,14 @@
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
+ // The main content view was updating the content bounds and we transfer them to the
+ // renderer.
+ if (!mCurrentContentBounds.equals(mStagedContentBounds)) {
+ mCurrentContentBounds.set(mStagedContentBounds);
+ nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left,
+ mCurrentContentBounds.top, mCurrentContentBounds.right,
+ mCurrentContentBounds.bottom);
+ }
attachInfo.mIgnoreDirtyState = false;
@@ -541,4 +595,11 @@
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
@DumpFlags int dumpFlags);
private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
+
+ private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+ 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,
+ int top, int right, int bottom);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 98c0494..2a1d757 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4706,7 +4706,7 @@
out.append(" #");
out.append(Integer.toHexString(id));
final Resources r = mResources;
- if (Resources.resourceHasPackage(id) && r != null) {
+ if (id > 0 && Resources.resourceHasPackage(id) && r != null) {
try {
String pkgname;
switch (id&0xff000000) {
@@ -7373,6 +7373,23 @@
}
/**
+ * Compute the view's coordinate within the surface.
+ *
+ * <p>Computes the coordinates of this view in its surface. The argument
+ * must be an array of two integers. After the method returns, the array
+ * contains the x and y location in that order.</p>
+ * @hide
+ * @param location an array of two integers in which to hold the coordinates
+ */
+ public void getLocationInSurface(@Size(2) int[] location) {
+ getLocationInWindow(location);
+ if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+ location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+ location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+ }
+ }
+
+ /**
* Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
* only available if the view is attached.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2ddf8ad..8403c46 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -169,6 +169,16 @@
boolean mAppVisible = true;
int mOrigWindowType = -1;
+ /** Whether the window had focus during the most recent traversal. */
+ boolean mHadWindowFocus;
+
+ /**
+ * Whether the window lost focus during a previous traversal and has not
+ * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
+ * accessibility events should be sent during traversal.
+ */
+ boolean mLostWindowFocus;
+
// Set to true if the owner of this window is in the stopped state,
// so the window should no longer be active.
boolean mStopped = false;
@@ -1522,10 +1532,11 @@
boolean insetsPending = false;
int relayoutResult = 0;
+ boolean isViewVisible = viewVisibility == View.VISIBLE;
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
- if (viewVisibility == View.VISIBLE) {
+ if (isViewVisible) {
// If this window is giving internal insets to the window
// manager, and it is being added or changing its visibility,
// then we want to first give the window manager "fake"
@@ -1776,10 +1787,6 @@
|| mHeight != hardwareRenderer.getHeight()) {
hardwareRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
- if (!hwInitialized) {
- hardwareRenderer.invalidate(mSurface);
- mFullRedrawNeeded = true;
- }
}
}
@@ -1964,19 +1971,33 @@
mRemainingFrameCount--;
}
+ final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
+ final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
+ final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
+ if (regainedFocus) {
+ mLostWindowFocus = false;
+ } else if (!hasWindowFocus && mHadWindowFocus) {
+ mLostWindowFocus = true;
+ }
+
+ if (changedVisibility || regainedFocus) {
+ host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
+ mHadWindowFocus = hasWindowFocus;
- if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) {
+ if (hasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
- imm.onPreWindowFocus(mView, true /* hasWindowFocus */);
+ imm.onPreWindowFocus(mView, hasWindowFocus);
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
@@ -1989,8 +2010,7 @@
mReportNextDraw = true;
}
- boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
- viewVisibility != View.VISIBLE;
+ boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
@@ -2004,7 +2024,7 @@
performDraw();
}
} else {
- if (viewVisibility == View.VISIBLE) {
+ if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -3313,13 +3333,6 @@
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
}
-
- if (mView != null && mAccessibilityManager.isEnabled()) {
- if (hasWindowFocus) {
- mView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
- }
}
} break;
case MSG_DIE:
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 5b19edf..2172b5c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -647,7 +647,8 @@
@Override
public CharSequence getExtendedInfo() {
- return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null;
+ // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
+ return null;
}
@Override
@@ -740,9 +741,8 @@
@Override
public boolean showsExtendedInfo(TargetInfo info) {
- // Reserve space to show extended info if any one of the items in the adapter has
- // extended info. This keeps grid item sizes uniform.
- return hasExtendedInfo();
+ // We have badges so we don't need this text shown.
+ return false;
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9272193..ef9d1ce 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -896,6 +896,7 @@
private final ResolveInfo mResolveInfo;
private final CharSequence mDisplayLabel;
private Drawable mDisplayIcon;
+ private Drawable mBadge;
private final CharSequence mExtendedInfo;
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
@@ -940,7 +941,25 @@
}
public Drawable getBadgeIcon() {
- return null;
+ // We only expose a badge if we have extended info.
+ // The badge is a higher-priority disambiguation signal
+ // but we don't need one if we wouldn't show extended info at all.
+ if (TextUtils.isEmpty(getExtendedInfo())) {
+ return null;
+ }
+
+ if (mBadge == null && mResolveInfo != null && mResolveInfo.activityInfo != null
+ && mResolveInfo.activityInfo.applicationInfo != null) {
+ if (mResolveInfo.activityInfo.icon == 0 || mResolveInfo.activityInfo.icon
+ == mResolveInfo.activityInfo.applicationInfo.icon) {
+ // Badging an icon with exactly the same icon is silly.
+ // If the activityInfo icon resid is 0 it will fall back
+ // to the application's icon, making it a match.
+ return null;
+ }
+ mBadge = mResolveInfo.activityInfo.applicationInfo.loadIcon(mPm);
+ }
+ return mBadge;
}
@Override
@@ -1378,8 +1397,8 @@
} else {
mHasExtendedInfo = true;
boolean usePkg = false;
- CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo
- .loadLabel(mPm);
+ final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo;
+ final CharSequence startApp = ai.loadLabel(mPm);
if (startApp == null) {
usePkg = true;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 4f4d3e0..f178c8c 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -338,7 +338,7 @@
}
if (mCpuPowerCalculator == null) {
- mCpuPowerCalculator = new CpuPowerCalculator();
+ mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4ff7869..6ccdd08 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 130 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 131 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -118,8 +118,6 @@
// in to one common name.
private static final int MAX_WAKELOCKS_PER_UID = 100;
- private static int sNumSpeedSteps;
-
private final JournaledFile mFile;
public final AtomicFile mCheckinFile;
public final AtomicFile mDailyFile;
@@ -133,7 +131,7 @@
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
- private final KernelCpuSpeedReader mKernelCpuSpeedReader = new KernelCpuSpeedReader();
+ private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
public interface BatteryCallback {
public void batteryNeedsCpuUpdate();
@@ -4411,7 +4409,7 @@
LongSamplingCounter mUserCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
LongSamplingCounter mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
LongSamplingCounter mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase);
- LongSamplingCounter[] mSpeedBins;
+ LongSamplingCounter[][] mCpuClusterSpeed;
/**
* The statistics we have collected for this uid's wake locks.
@@ -4470,7 +4468,6 @@
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
mWifiMulticastTimers, mOnBatteryTimeBase);
mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
- mSpeedBins = new LongSamplingCounter[getCpuSpeedSteps()];
}
@Override
@@ -5008,10 +5005,18 @@
}
@Override
- public long getTimeAtCpuSpeed(int step, int which) {
- if (step >= 0 && step < mSpeedBins.length) {
- if (mSpeedBins[step] != null) {
- return mSpeedBins[step].getCountLocked(which);
+ public long getTimeAtCpuSpeed(int cluster, int step, int which) {
+ if (mCpuClusterSpeed != null) {
+ if (cluster >= 0 && cluster < mCpuClusterSpeed.length) {
+ final LongSamplingCounter[] cpuSpeeds = mCpuClusterSpeed[cluster];
+ if (cpuSpeeds != null) {
+ if (step >= 0 && step < cpuSpeeds.length) {
+ final LongSamplingCounter c = cpuSpeeds[step];
+ if (c != null) {
+ return c.getCountLocked(which);
+ }
+ }
+ }
}
}
return 0;
@@ -5128,10 +5133,16 @@
mUserCpuTime.reset(false);
mSystemCpuTime.reset(false);
mCpuPower.reset(false);
- for (int i = 0; i < mSpeedBins.length; i++) {
- LongSamplingCounter c = mSpeedBins[i];
- if (c != null) {
- c.reset(false);
+
+ if (mCpuClusterSpeed != null) {
+ for (LongSamplingCounter[] speeds : mCpuClusterSpeed) {
+ if (speeds != null) {
+ for (LongSamplingCounter speed : speeds) {
+ if (speed != null) {
+ speed.reset(false);
+ }
+ }
+ }
}
}
@@ -5280,10 +5291,16 @@
mUserCpuTime.detach();
mSystemCpuTime.detach();
mCpuPower.detach();
- for (int i = 0; i < mSpeedBins.length; i++) {
- LongSamplingCounter c = mSpeedBins[i];
- if (c != null) {
- c.detach();
+
+ if (mCpuClusterSpeed != null) {
+ for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+ if (cpuSpeeds != null) {
+ for (LongSamplingCounter c : cpuSpeeds) {
+ if (c != null) {
+ c.detach();
+ }
+ }
+ }
}
}
}
@@ -5461,15 +5478,27 @@
mSystemCpuTime.writeToParcel(out);
mCpuPower.writeToParcel(out);
- out.writeInt(mSpeedBins.length);
- for (int i = 0; i < mSpeedBins.length; i++) {
- LongSamplingCounter c = mSpeedBins[i];
- if (c != null) {
- out.writeInt(1);
- c.writeToParcel(out);
- } else {
- out.writeInt(0);
+ if (mCpuClusterSpeed != null) {
+ out.writeInt(1);
+ out.writeInt(mCpuClusterSpeed.length);
+ for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+ if (cpuSpeeds != null) {
+ out.writeInt(1);
+ out.writeInt(cpuSpeeds.length);
+ for (LongSamplingCounter c : cpuSpeeds) {
+ if (c != null) {
+ out.writeInt(1);
+ c.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ } else {
+ out.writeInt(0);
+ }
}
+ } else {
+ out.writeInt(0);
}
}
@@ -5653,13 +5682,32 @@
mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mCpuPower = new LongSamplingCounter(mOnBatteryTimeBase, in);
- int bins = in.readInt();
- int steps = getCpuSpeedSteps();
- mSpeedBins = new LongSamplingCounter[bins >= steps ? bins : steps];
- for (int i = 0; i < bins; i++) {
- if (in.readInt() != 0) {
- mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ if (in.readInt() != 0) {
+ int numCpuClusters = in.readInt();
+ if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numCpuClusters) {
+ throw new ParcelFormatException("Incompatible number of cpu clusters");
}
+
+ mCpuClusterSpeed = new LongSamplingCounter[numCpuClusters][];
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ if (in.readInt() != 0) {
+ int numSpeeds = in.readInt();
+ if (mPowerProfile != null &&
+ mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
+ throw new ParcelFormatException("Incompatible number of cpu speeds");
+ }
+
+ final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
+ mCpuClusterSpeed[cluster] = cpuSpeeds;
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ if (in.readInt() != 0) {
+ cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+ }
+ }
+ }
+ } else {
+ mCpuClusterSpeed = null;
}
}
@@ -6874,6 +6922,19 @@
public void setPowerProfile(PowerProfile profile) {
synchronized (this) {
mPowerProfile = profile;
+
+ // We need to initialize the KernelCpuSpeedReaders to read from
+ // the first cpu of each core. Once we have the PowerProfile, we have access to this
+ // information.
+ final int numClusters = mPowerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
+ }
}
}
@@ -6881,10 +6942,6 @@
mCallback = cb;
}
- public void setNumSpeedSteps(int steps) {
- if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
- }
-
public void setRadioScanningTimeout(long timeout) {
if (mPhoneSignalScanningTimer != null) {
mPhoneSignalScanningTimer.setTimeout(timeout);
@@ -7997,9 +8054,11 @@
// If no app is holding a wakelock, then the distribution is normal.
final int wakelockWeight = 50;
- // Read the time spent at various cpu frequencies.
- final int cpuSpeedSteps = getCpuSpeedSteps();
- final long[] cpuSpeeds = mKernelCpuSpeedReader.readDelta();
+ // Read the time spent for each cluster at various cpu frequencies.
+ final long[][] clusterSpeeds = new long[mKernelCpuSpeedReaders.length][];
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ clusterSpeeds[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
+ }
int numWakelocks = 0;
@@ -8072,11 +8131,23 @@
// Add the cpu speeds to this UID. These are used as a ratio
// for computing the power this UID used.
- for (int i = 0; i < cpuSpeedSteps; i++) {
- if (u.mSpeedBins[i] == null) {
- u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ if (u.mCpuClusterSpeed == null) {
+ u.mCpuClusterSpeed = new LongSamplingCounter[clusterSpeeds.length][];
+ }
+
+ for (int cluster = 0; cluster < clusterSpeeds.length; cluster++) {
+ if (u.mCpuClusterSpeed[cluster] == null) {
+ u.mCpuClusterSpeed[cluster] =
+ new LongSamplingCounter[clusterSpeeds[cluster].length];
}
- u.mSpeedBins[i].addCountLocked(cpuSpeeds[i]);
+
+ final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeed[cluster];
+ for (int speed = 0; speed < clusterSpeeds[cluster].length; speed++) {
+ if (cpuSpeeds[speed] == null) {
+ cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
+ }
+ cpuSpeeds[speed].addCountLocked(clusterSpeeds[cluster][speed]);
+ }
}
}
});
@@ -8776,11 +8847,6 @@
}
}
- @Override
- public int getCpuSpeedSteps() {
- return sNumSpeedSteps;
- }
-
/**
* Retrieve the statistics object for a particular uid, creating if needed.
*/
@@ -9216,11 +9282,6 @@
}
}
- sNumSpeedSteps = in.readInt();
- if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) {
- throw new ParcelFormatException("Bad speed steps in data: " + sNumSpeedSteps);
- }
-
final int NU = in.readInt();
if (NU > 10000) {
throw new ParcelFormatException("File corrupt: too many uids " + NU);
@@ -9304,17 +9365,33 @@
u.mSystemCpuTime.readSummaryFromParcelLocked(in);
u.mCpuPower.readSummaryFromParcelLocked(in);
- int NSB = in.readInt();
- if (NSB > 100) {
- throw new ParcelFormatException("File corrupt: too many speed bins " + NSB);
- }
-
- u.mSpeedBins = new LongSamplingCounter[NSB];
- for (int i=0; i<NSB; i++) {
- if (in.readInt() != 0) {
- u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
- u.mSpeedBins[i].readSummaryFromParcelLocked(in);
+ if (in.readInt() != 0) {
+ final int numClusters = in.readInt();
+ if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) {
+ throw new ParcelFormatException("Incompatible cpu cluster arrangement");
}
+
+ u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
+ for (int cluster = 0; cluster < numClusters; cluster++) {
+ int NSB = in.readInt();
+ if (mPowerProfile != null &&
+ mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) {
+ throw new ParcelFormatException("File corrupt: too many speed bins " + NSB);
+ }
+
+ if (in.readInt() != 0) {
+ u.mCpuClusterSpeed[cluster] = new LongSamplingCounter[NSB];
+ for (int speed = 0; speed < NSB; speed++) {
+ if (in.readInt() != 0) {
+ u.mCpuClusterSpeed[cluster][speed] = new LongSamplingCounter(
+ mOnBatteryTimeBase);
+ u.mCpuClusterSpeed[cluster][speed].readSummaryFromParcelLocked(in);
+ }
+ }
+ }
+ }
+ } else {
+ u.mCpuClusterSpeed = null;
}
int NW = in.readInt();
@@ -9531,7 +9608,6 @@
}
}
- out.writeInt(sNumSpeedSteps);
final int NU = mUidStats.size();
out.writeInt(NU);
for (int iu = 0; iu < NU; iu++) {
@@ -9640,15 +9716,27 @@
u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
u.mCpuPower.writeSummaryFromParcelLocked(out);
- out.writeInt(u.mSpeedBins.length);
- for (int i = 0; i < u.mSpeedBins.length; i++) {
- LongSamplingCounter speedBin = u.mSpeedBins[i];
- if (speedBin != null) {
- out.writeInt(1);
- speedBin.writeSummaryFromParcelLocked(out);
- } else {
- out.writeInt(0);
+ if (u.mCpuClusterSpeed != null) {
+ out.writeInt(1);
+ out.writeInt(u.mCpuClusterSpeed.length);
+ for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeed) {
+ if (cpuSpeeds != null) {
+ out.writeInt(1);
+ out.writeInt(cpuSpeeds.length);
+ for (LongSamplingCounter c : cpuSpeeds) {
+ if (c != null) {
+ out.writeInt(1);
+ c.writeSummaryFromParcelLocked(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ } else {
+ out.writeInt(0);
+ }
}
+ } else {
+ out.writeInt(0);
}
final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
@@ -9897,8 +9985,6 @@
mFlashlightTurnedOnTimers.clear();
mCameraTurnedOnTimers.clear();
- sNumSpeedSteps = in.readInt();
-
int numUids = in.readInt();
mUidStats.clear();
for (int i = 0; i < numUids; i++) {
@@ -10037,8 +10123,6 @@
out.writeInt(0);
}
- out.writeInt(sNumSpeedSteps);
-
if (inclUids) {
int size = mUidStats.size();
out.writeInt(size);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index d62f7a6..8417856 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -22,12 +22,47 @@
public class CpuPowerCalculator extends PowerCalculator {
private static final String TAG = "CpuPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private final PowerProfile mProfile;
+
+ public CpuPowerCalculator(PowerProfile profile) {
+ mProfile = profile;
+ }
@Override
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
+
app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
- app.cpuPowerMah = (double) u.getCpuPowerMaUs(statsType) / (60.0 * 60.0 * 1000.0 * 1000.0);
+
+ // Aggregate total time spent on each cluster.
+ long totalTime = 0;
+ final int numClusters = mProfile.getNumCpuClusters();
+ for (int cluster = 0; cluster < numClusters; cluster++) {
+ final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int speed = 0; speed < speedsForCluster; speed++) {
+ totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType);
+ }
+ }
+ totalTime = Math.max(totalTime, 1);
+
+ double cpuPowerMaMs = 0;
+ for (int cluster = 0; cluster < numClusters; cluster++) {
+ final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int speed = 0; speed < speedsForCluster; speed++) {
+ final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) /
+ totalTime;
+ final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
+ mProfile.getAveragePowerForCpu(cluster, speed);
+ if (DEBUG && ratio != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+ + speed + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
+ + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+ }
+ cpuPowerMaMs += cpuSpeedStepPower;
+ }
+ }
+ app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);
+
if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
+ BatteryStatsHelper.makemAh(app.cpuPowerMah));
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index c30df28..5b776ac 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -24,8 +24,8 @@
import java.util.Arrays;
/**
- * Reads CPU time spent at various frequencies and provides a delta from the last call to
- * {@link #readDelta}. Each line in the proc file has the format:
+ * Reads CPU time of a specific core spent at various frequencies and provides a delta from the
+ * last call to {@link #readDelta}. Each line in the proc file has the format:
*
* freq time
*
@@ -33,12 +33,20 @@
*/
public class KernelCpuSpeedReader {
private static final String TAG = "KernelCpuSpeedReader";
- private static final String sProcFile =
- "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
- private static final int MAX_SPEEDS = 60;
- private long[] mLastSpeedTimes = new long[MAX_SPEEDS];
- private long[] mDeltaSpeedTimes = new long[MAX_SPEEDS];
+ private final String mProcFile;
+ private final long[] mLastSpeedTimes;
+ private final long[] mDeltaSpeedTimes;
+
+ /**
+ * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
+ */
+ public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
+ mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
+ cpuNumber);
+ mLastSpeedTimes = new long[numSpeedSteps];
+ mDeltaSpeedTimes = new long[numSpeedSteps];
+ }
/**
* The returned array is modified in subsequent calls to {@link #readDelta}.
@@ -46,22 +54,28 @@
* {@link #readDelta}.
*/
public long[] readDelta() {
- try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
String line;
int speedIndex = 0;
- while ((line = reader.readLine()) != null) {
+ while (speedIndex < mLastSpeedTimes.length && (line = reader.readLine()) != null) {
splitter.setString(line);
Long.parseLong(splitter.next());
// The proc file reports time in 1/100 sec, so convert to milliseconds.
long time = Long.parseLong(splitter.next()) * 10;
- mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+ if (time < mLastSpeedTimes[speedIndex]) {
+ // The stats reset when the cpu hotplugged. That means that the time
+ // we read is offset from 0, so the time is the delta.
+ mDeltaSpeedTimes[speedIndex] = time;
+ } else {
+ mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+ }
mLastSpeedTimes[speedIndex] = time;
speedIndex++;
}
} catch (IOException e) {
- Slog.e(TAG, "Failed to read cpu-freq", e);
+ Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
Arrays.fill(mDeltaSpeedTimes, 0);
}
return mDeltaSpeedTimes;
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index 0df78ed..5d3043c 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -137,7 +137,7 @@
mLastPowerMaUs.put(uid, powerMaUs);
}
} catch (IOException e) {
- Slog.e(TAG, "Failed to read uid_cputime", e);
+ Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
}
mLastTimeReadUs = nowUs;
}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 4ede8dda..aaa9f73 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -59,6 +59,7 @@
/**
* Power consumption when CPU is in power collapse mode.
*/
+ @Deprecated
public static final String POWER_CPU_ACTIVE = "cpu.active";
/**
@@ -163,6 +164,7 @@
*/
public static final String POWER_CAMERA = "camera.avg";
+ @Deprecated
public static final String POWER_CPU_SPEEDS = "cpu.speeds";
/**
@@ -191,6 +193,7 @@
if (sPowerMap.size() == 0) {
readPowerValuesFromXml(context);
}
+ initCpuClusters();
}
private void readPowerValuesFromXml(Context context) {
@@ -249,7 +252,7 @@
}
// Now collect other config variables.
- int[] configResIds = new int[] {
+ int[] configResIds = new int[]{
com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
@@ -260,7 +263,7 @@
com.android.internal.R.integer.config_wifi_operating_voltage_mv,
};
- String[] configResIdKeys = new String[] {
+ String[] configResIdKeys = new String[]{
POWER_BLUETOOTH_CONTROLLER_IDLE,
POWER_BLUETOOTH_CONTROLLER_RX,
POWER_BLUETOOTH_CONTROLLER_TX,
@@ -279,6 +282,69 @@
}
}
+ private CpuClusterKey[] mCpuClusters;
+
+ private static final String POWER_CPU_CLUSTER_CORE_COUNT = "cpu.clusters.cores";
+ private static final String POWER_CPU_CLUSTER_SPEED_PREFIX = "cpu.speeds.cluster";
+ private static final String POWER_CPU_CLUSTER_ACTIVE_PREFIX = "cpu.active.cluster";
+
+ @SuppressWarnings("deprecated")
+ private void initCpuClusters() {
+ // Figure out how many CPU clusters we're dealing with
+ final Object obj = sPowerMap.get(POWER_CPU_CLUSTER_CORE_COUNT);
+ if (obj == null || !(obj instanceof Double[])) {
+ // Default to single.
+ mCpuClusters = new CpuClusterKey[1];
+ mCpuClusters[0] = new CpuClusterKey(POWER_CPU_SPEEDS, POWER_CPU_ACTIVE, 1);
+
+ } else {
+ final Double[] array = (Double[]) obj;
+ mCpuClusters = new CpuClusterKey[array.length];
+ for (int cluster = 0; cluster < array.length; cluster++) {
+ int numCpusInCluster = (int) Math.round(array[cluster]);
+ mCpuClusters[cluster] = new CpuClusterKey(
+ POWER_CPU_CLUSTER_SPEED_PREFIX + cluster,
+ POWER_CPU_CLUSTER_ACTIVE_PREFIX + cluster,
+ numCpusInCluster);
+ }
+ }
+ }
+
+ public static class CpuClusterKey {
+ private final String timeKey;
+ private final String powerKey;
+ private final int numCpus;
+
+ private CpuClusterKey(String timeKey, String powerKey, int numCpus) {
+ this.timeKey = timeKey;
+ this.powerKey = powerKey;
+ this.numCpus = numCpus;
+ }
+ }
+
+ public int getNumCpuClusters() {
+ return mCpuClusters.length;
+ }
+
+ public int getNumCoresInCpuCluster(int index) {
+ return mCpuClusters[index].numCpus;
+ }
+
+ public int getNumSpeedStepsInCpuCluster(int index) {
+ Object value = sPowerMap.get(mCpuClusters[index].timeKey);
+ if (value != null && value instanceof Double[]) {
+ return ((Double[])value).length;
+ }
+ return 1; // Only one speed
+ }
+
+ public double getAveragePowerForCpu(int cluster, int step) {
+ if (cluster >= 0 && cluster < mCpuClusters.length) {
+ return getAveragePower(mCpuClusters[cluster].powerKey, step);
+ }
+ return 0;
+ }
+
/**
* Returns the average current in mA consumed by the subsystem, or the given
* default value if the subsystem has no recorded value.
@@ -344,16 +410,4 @@
public double getBatteryCapacity() {
return getAveragePower(POWER_BATTERY_CAPACITY);
}
-
- /**
- * Returns the number of speeds that the CPU can be run at.
- * @return
- */
- public int getNumSpeedSteps() {
- Object value = sPowerMap.get(POWER_CPU_SPEEDS);
- if (value != null && value instanceof Double[]) {
- return ((Double[])value).length;
- }
- return 1; // Only one speed
- }
}
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 8535e6a..1bbbb089 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -47,7 +47,7 @@
fHeight = height;
}
~SkBitmapRegionDecoder() {
- SkDELETE(fDecoder);
+ delete fDecoder;
}
bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
@@ -72,7 +72,7 @@
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
int width, height;
if (NULL == decoder) {
- SkDELETE(stream);
+ delete stream;
doThrowIOE(env, "Image format not supported");
return nullObjectReturn("SkImageDecoder::Factory returned null");
}
@@ -87,7 +87,7 @@
snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
decoder->getFormatName());
doThrowIOE(env, msg);
- SkDELETE(decoder);
+ delete decoder;
return nullObjectReturn("decoder->buildTileIndex returned false");
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 6c3676b..9a2703b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -440,6 +440,32 @@
}
}
+static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->addRenderNode(renderNode, placeFront);
+}
+
+static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->removeRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->drawRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(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);
+}
// ----------------------------------------------------------------------------
// Shaders
@@ -447,7 +473,6 @@
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath) {
-
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
egl_cache_t::get()->setCacheFilename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -494,6 +519,11 @@
{ "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
+ { "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},
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 921385d..664e621 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2175,6 +2175,13 @@
<permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
android:protectionLevel="signature" />
+ <!-- Allows an application to control the color transforms applied to
+ displays system-wide.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
diff --git a/core/res/res/values-mcc240-mnc01/config.xml b/core/res/res/values-mcc240-mnc01/config.xml
new file mode 100644
index 0000000..7fb5c46
--- /dev/null
+++ b/core/res/res/values-mcc240-mnc01/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Do not set the system language as value of EF LI/EF PL -->
+ <bool name="config_use_sim_language_file">false</bool>
+
+</resources>
diff --git a/core/res/res/values-mcc240-mnc05/config.xml b/core/res/res/values-mcc240-mnc05/config.xml
new file mode 100644
index 0000000..7fb5c46
--- /dev/null
+++ b/core/res/res/values-mcc240-mnc05/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Do not set the system language as value of EF LI/EF PL -->
+ <bool name="config_use_sim_language_file">false</bool>
+
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a5960a9..a19bc20 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1759,6 +1759,28 @@
-->
<bool name="config_enableWifiDisplay">false</bool>
+ <!-- The color transform values that correspond to each respective configuration mode for the
+ built-in display, or -1 if the mode is unsupported by the device. The possible
+ configuration modes are:
+ 1. Wide-gamut ("Vibrant")
+ 2. Adobe RGB ("Natural")
+ 3. sRGB ("Standard")
+
+ For example, if a device had Wide-gamut as color transform mode 1, sRGB mode as color
+ transform mode 7, and did not support Adobe RGB at all this would look like:
+
+ <integer-array name="config_colorTransforms">
+ <item>1</item>
+ <item>-1</item>
+ <item>7</item>
+ </integer-array>
+ -->
+ <integer-array name="config_colorTransforms">
+ <item>-1</item>
+ <item>-1</item>
+ <item>-1</item>
+ </integer-array>
+
<!-- When true use the linux /dev/input/event subsystem to detect the switch changes
on the headphone/microphone jack. When false use the older uevent framework. -->
<bool name="config_useDevInputEventForAudioJack">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 16e44b86..4edc8471 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1133,6 +1133,7 @@
<java-symbol type="array" name="config_telephonyHardware" />
<java-symbol type="array" name="config_keySystemUuidMapping" />
<java-symbol type="array" name="config_gpsParameters" />
+ <java-symbol type="array" name="config_colorTransforms" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="indicator_input_error" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 28d99d8..ddd0ca2 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -47,17 +47,38 @@
<value>0.2</value> <!-- ~2mA -->
<value>0.1</value> <!-- ~1mA -->
</array>
- <!-- Different CPU speeds as reported in
- /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state -->
- <array name="cpu.speeds">
+
+ <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
+ number of CPU cores for that cluster.
+
+ Ex:
+ <array name="cpu.clusters.cores">
+ <value>4</value> // cluster 0 has cpu0, cpu1, cpu2, cpu3
+ <value>2</value> // cluster 1 has cpu4, cpu5
+ </array> -->
+ <array name="cpu.clusters.cores">
+ <value>1</value> <!-- cluster 0 has cpu0 -->
+ </array>
+
+ <!-- Different CPU speeds for cluster 0 as reported in
+ /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state.
+
+ There must be one of these for each cluster, labeled:
+ cpu.speeds.cluster0, cpu.speeds.cluster1, etc... -->
+ <array name="cpu.speeds.cluster0">
<value>400000</value> <!-- 400 MHz CPU speed -->
</array>
- <!-- Current when CPU is idle -->
- <item name="cpu.idle">0.1</item>
- <!-- Current at each CPU speed, as per 'cpu.speeds' -->
- <array name="cpu.active">
+
+ <!-- Current at each CPU speed for cluster 0, as per 'cpu.speeds.cluster0'.
+ Like cpu.speeds.cluster0, there must be one of these present for
+ each heterogeneous CPU cluster. -->
+ <array name="cpu.active.cluster0">
<value>0.1</value> <!-- ~100mA -->
</array>
+
+ <!-- Current when CPU is idle -->
+ <item name="cpu.idle">0.1</item>
+
<!-- This is the battery capacity in mAh (measured at nominal voltage) -->
<item name="battery.capacity">1000</item>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 3eb13ab..cc0943f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -24,6 +24,7 @@
utils/GLUtils.cpp \
utils/LinearAllocator.cpp \
utils/NinePatchImpl.cpp \
+ utils/StringUtils.cpp \
AmbientShadow.cpp \
AnimationContext.cpp \
Animator.cpp \
@@ -168,7 +169,8 @@
unit_tests/CanvasStateTests.cpp \
unit_tests/ClipAreaTests.cpp \
unit_tests/DamageAccumulatorTests.cpp \
- unit_tests/LinearAllocatorTests.cpp
+ unit_tests/LinearAllocatorTests.cpp \
+ unit_tests/StringUtilsTests.cpp
include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 5808aac..e98fa04 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -20,9 +20,6 @@
// Turn on to check for OpenGL errors on each frame
#define DEBUG_OPENGL 1
-// Turn on to display informations about the GPU
-#define DEBUG_EXTENSIONS 0
-
// Turn on to enable initialization information
#define DEBUG_INIT 0
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 3d350c9..06c8a21 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -18,23 +18,14 @@
#include "Debug.h"
#include "Properties.h"
+#include "utils/StringUtils.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
#include <GLES2/gl2ext.h>
#include <utils/Log.h>
namespace android {
-
-using namespace uirenderer;
-ANDROID_SINGLETON_STATIC_INSTANCE(Extensions);
-
namespace uirenderer {
-///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
// Debug
#if DEBUG_EXTENSIONS
#define EXT_LOGD(...) ALOGD(__VA_ARGS__)
@@ -42,20 +33,16 @@
#define EXT_LOGD(...)
#endif
-///////////////////////////////////////////////////////////////////////////////
-// Constructors
-///////////////////////////////////////////////////////////////////////////////
Extensions::Extensions() {
- // Query GL extensions
- findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList);
- mHasNPot = hasGlExtension("GL_OES_texture_npot");
- mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch");
- mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer");
- mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker");
- mHas1BitStencil = hasGlExtension("GL_OES_stencil1");
- mHas4BitStencil = hasGlExtension("GL_OES_stencil4");
- mHasUnpackSubImage = hasGlExtension("GL_EXT_unpack_subimage");
+ StringCollection extensions((const char*) glGetString(GL_EXTENSIONS));
+ mHasNPot = extensions.has("GL_OES_texture_npot");
+ mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
+ mHasDiscardFramebuffer = extensions.has("GL_EXT_discard_framebuffer");
+ mHasDebugMarker = extensions.has("GL_EXT_debug_marker");
+ mHas1BitStencil = extensions.has("GL_OES_stencil1");
+ mHas4BitStencil = extensions.has("GL_OES_stencil4");
+ mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
const char* version = (const char*) glGetString(GL_VERSION);
@@ -78,40 +65,5 @@
}
}
-///////////////////////////////////////////////////////////////////////////////
-// Methods
-///////////////////////////////////////////////////////////////////////////////
-
-bool Extensions::hasGlExtension(const char* extension) const {
- const String8 s(extension);
- return mGlExtensionList.indexOf(s) >= 0;
-}
-
-bool Extensions::hasEglExtension(const char* extension) const {
- const String8 s(extension);
- return mEglExtensionList.indexOf(s) >= 0;
-}
-
-void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const {
- const char* current = extensions;
- const char* head = current;
- EXT_LOGD("Available extensions:");
- do {
- head = strchr(current, ' ');
- String8 s(current, head ? head - current : strlen(current));
- if (s.length()) {
- list.add(s);
- EXT_LOGD(" %s", s.string());
- }
- current = head + 1;
- } while (head);
-}
-
-void Extensions::dump() const {
- ALOGD("%s", (const char*) glGetString(GL_VERSION));
- ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS));
- ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS));
-}
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 636b631..0a30d16 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -50,17 +50,7 @@
inline int getMajorGlVersion() const { return mVersionMajor; }
inline int getMinorGlVersion() const { return mVersionMinor; }
- bool hasGlExtension(const char* extension) const;
- bool hasEglExtension(const char* extension) const;
-
- void dump() const;
-
private:
- void findExtensions(const char* extensions, SortedVector<String8>& list) const;
-
- SortedVector<String8> mGlExtensionList;
- SortedVector<String8> mEglExtensionList;
-
bool mHasNPot;
bool mHasFramebufferFetch;
bool mHasDiscardFramebuffer;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index e27b26b..69559a7 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -435,7 +435,6 @@
mOutGlop->fill.texture = { &texture,
GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
- mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
@@ -449,7 +448,6 @@
mOutGlop->fill.texture = { &(layer.getTexture()),
layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
- mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
nullptr, layer.getColorFilter());
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index ed853f7..98e6146 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -77,7 +77,7 @@
, canvasContext(clone.canvasContext)
{}
- const TraversalMode mode;
+ TraversalMode mode;
// TODO: Remove this? Currently this is used to signal to stop preparing
// textures if we run out of cache space.
bool prepareTextures;
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index b21e15e..93f787d 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -98,20 +98,6 @@
// gl blending off by default
}
-void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) {
- GLenum srcMode;
- GLenum dstMode;
- getFactors(mode, modeUsage, &srcMode, &dstMode);
- setFactors(srcMode, dstMode);
-}
-
-void Blend::disable() {
- if (mEnabled) {
- glDisable(GL_BLEND);
- mEnabled = false;
- }
-}
-
void Blend::invalidate() {
syncEnabled();
mSrcMode = mDstMode = GL_ZERO;
@@ -132,8 +118,13 @@
void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
if (srcMode == GL_ZERO && dstMode == GL_ZERO) {
- disable();
+ // disable blending
+ if (mEnabled) {
+ glDisable(GL_BLEND);
+ mEnabled = false;
+ }
} else {
+ // enable blending
if (!mEnabled) {
glEnable(GL_BLEND);
mEnabled = true;
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index dcc681d..df9e5a8 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -34,9 +34,6 @@
NoSwap,
Swap,
};
-
- void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage);
- void disable();
void syncEnabled();
static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b74b508..9dc5b45 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -60,9 +60,10 @@
, mEglManager(thread.eglManager())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
- , mRootRenderNode(rootRenderNode)
, mJankTracker(thread.timeLord().frameIntervalNanos())
- , mProfiler(mFrames) {
+ , mProfiler(mFrames)
+ , mContentOverdrawProtectionBounds(0, 0, 0, 0) {
+ mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
@@ -172,7 +173,8 @@
return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
}
-void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target) {
mRenderThread.removeFrameCallback(this);
// If the previous frame was dropped we don't need to hold onto it, so
@@ -189,7 +191,13 @@
info.canvasContext = this;
mAnimationContext->startFrame(info.mode);
- mRootRenderNode->prepareTree(info);
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ // Only the primary target node will be drawn full - all other nodes would get drawn in
+ // real time mode. In case of a window, the primary node is the window content and the other
+ // node(s) are non client / filler nodes.
+ info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
+ node->prepareTree(info);
+ }
mAnimationContext->runRemainingAnimations(info);
freePrefetechedLayers();
@@ -299,7 +307,95 @@
dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
Rect outBounds;
- mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+ // It there are multiple render nodes, they are as follows:
+ // #0 - backdrop
+ // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds)
+ // #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
+ // backdrop against the content and draw the remaining part of it. It will then crop the content
+ // 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;
+ // If there is no content bounds we ignore the layering as stated above and start with 2.
+ int layer = mContentOverdrawProtectionBounds.isEmpty() ? 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.
+ const RenderProperties& properties = node->properties();
+ Rect targetBounds(properties.getLeft(), properties.getTop(),
+ properties.getRight(), properties.getBottom());
+ // Remember the intersection of the target bounds and the intersection bounds against
+ // which we have to crop the content.
+ backdropBounds.intersect(targetBounds);
+ // Check if we have to draw something on the left side ...
+ if (targetBounds.left < mContentOverdrawProtectionBounds.left) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
+ mContentOverdrawProtectionBounds.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);
+ mCanvas->restore();
+ }
+ // ... or on the right side ...
+ if (targetBounds.right > mContentOverdrawProtectionBounds.right &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(mContentOverdrawProtectionBounds.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);
+ mCanvas->restore();
+ }
+ // ... or at the top ...
+ if (targetBounds.top < mContentOverdrawProtectionBounds.top &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
+ mContentOverdrawProtectionBounds.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);
+ mCanvas->restore();
+ }
+ // ... or at the bottom.
+ if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left,
+ mContentOverdrawProtectionBounds.bottom, targetBounds.right,
+ targetBounds.bottom, SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ }
+ } 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->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ } else { // draw the rest on top at will!
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ layer++;
+ }
profiler().draw(mCanvas);
@@ -343,7 +439,10 @@
if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
return;
}
+ prepareAndDraw(nullptr);
+}
+void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -353,7 +452,7 @@
mRenderThread.timeLord().latestVsync());
TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
- prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
+ prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
if (info.out.canDrawThisFrame) {
draw();
}
@@ -423,7 +522,9 @@
stopDrawing();
if (mEglManager.hasEglContext()) {
freePrefetechedLayers();
- mRootRenderNode->destroyHardwareResources();
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ node->destroyHardwareResources();
+ }
Caches& caches = Caches::getInstance();
// Make sure to release all the textures we were owning as there won't
// be another draw
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 6a79320..1c3845c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -34,6 +34,7 @@
#include <set>
#include <string>
+#include <vector>
namespace android {
namespace uirenderer {
@@ -77,12 +78,14 @@
void setOpaque(bool opaque);
void makeCurrent();
void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
- void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued);
+ void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target);
void draw();
void destroy();
// IFrameCallback, Chroreographer-driven frame callback entry point
virtual void doFrame() override;
+ void prepareAndDraw(RenderNode* node);
void buildLayer(RenderNode* node);
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
@@ -113,6 +116,20 @@
void serializeDisplayListTree();
+ void addRenderNode(RenderNode* node, bool placeFront) {
+ int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+ mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+ }
+
+ void removeRenderNode(RenderNode* node) {
+ mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+ mRenderNodes.end());
+ }
+
+ void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ mContentOverdrawProtectionBounds.set(left, top, right, bottom);
+ }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -138,7 +155,7 @@
DamageAccumulator mDamageAccumulator;
std::unique_ptr<AnimationContext> mAnimationContext;
- const sp<RenderNode> mRootRenderNode;
+ std::vector< sp<RenderNode> > mRenderNodes;
FrameInfo* mCurrentFrameInfo = nullptr;
// Ring buffer large enough for 2 seconds worth of frames
@@ -148,6 +165,9 @@
FrameInfoVisualizer mProfiler;
std::set<RenderNode*> mPrefetechedLayers;
+
+ // Stores the bounds of the main content.
+ Rect mContentOverdrawProtectionBounds;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 198906c..a47c9ec 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -38,9 +38,11 @@
DrawFrameTask::~DrawFrameTask() {
}
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+ RenderNode* targetNode) {
mRenderThread = thread;
mContext = context;
+ mTargetNode = targetNode;
}
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -118,7 +120,7 @@
mContext->processLayerUpdate(mLayers[i].get());
}
mLayers.clear();
- mContext->prepareTree(info, mFrameInfo, mSyncQueued);
+ mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index ebefcba..68ee897 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -57,7 +57,7 @@
DrawFrameTask();
virtual ~DrawFrameTask();
- void setContext(RenderThread* thread, CanvasContext* context);
+ void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
@@ -78,6 +78,7 @@
RenderThread* mRenderThread;
CanvasContext* mContext;
+ RenderNode* mTargetNode = nullptr;
/*********************************************
* Single frame data
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index d2ce49f..c9b9637 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -20,6 +20,7 @@
#include "Properties.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
+#include "utils/StringUtils.h"
#include <cutils/log.h>
#include <cutils/properties.h>
@@ -133,12 +134,9 @@
}
void EglManager::initExtensions() {
- std::string extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
- auto has = [&](const char* ext) {
- return extensions.find(ext) != std::string::npos;
- };
- EglExtensions.bufferAge = has("EGL_EXT_buffer_age");
- EglExtensions.setDamage = has("EGL_KHR_partial_update");
+ StringCollection extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+ EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
+ EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
}
bool EglManager::hasEglContext() {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b838811..f43a769 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -74,7 +74,7 @@
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
- mDrawFrameTask.setContext(&mRenderThread, mContext);
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
RenderProxy::~RenderProxy() {
@@ -91,7 +91,7 @@
SETUP_TASK(destroyContext);
args->context = mContext;
mContext = nullptr;
- mDrawFrameTask.setContext(nullptr, nullptr);
+ mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
postAndWait(task);
@@ -461,7 +461,8 @@
staticPostAndWait(task);
}
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
+ size_t size) {
CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
args->buffer->decStrong(nullptr);
return nullptr;
@@ -490,6 +491,61 @@
post(task);
}
+CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
+ args->context->addRenderNode(args->node, args->placeFront);
+ return nullptr;
+}
+
+void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
+ SETUP_TASK(addRenderNode);
+ args->context = mContext;
+ args->node = node;
+ args->placeFront = placeFront;
+ post(task);
+}
+
+CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->removeRenderNode(args->node);
+ return nullptr;
+}
+
+void RenderProxy::removeRenderNode(RenderNode* node) {
+ SETUP_TASK(removeRenderNode);
+ args->context = mContext;
+ args->node = node;
+ post(task);
+}
+
+CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->prepareAndDraw(args->node);
+ return nullptr;
+}
+
+void RenderProxy::drawRenderNode(RenderNode* node) {
+ SETUP_TASK(drawRenderNode);
+ args->context = mContext;
+ args->node = node;
+ // Be pseudo-thread-safe and don't use any member variables
+ staticPostAndWait(task);
+}
+
+CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top,
+ int right, int bottom) {
+ args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right,
+ args->bottom);
+ return nullptr;
+}
+
+void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ SETUP_TASK(setContentOverdrawProtectionBounds);
+ args->context = mContext;
+ args->left = left;
+ args->top = top;
+ args->right = right;
+ args->bottom = bottom;
+ staticPostAndWait(task);
+}
+
CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
args->context->serializeDisplayListTree();
return nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e7356db..046f24a 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -106,6 +106,11 @@
ANDROID_API void serializeDisplayListTree();
+ 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);
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/libs/hwui/unit_tests/StringUtilsTests.cpp b/libs/hwui/unit_tests/StringUtilsTests.cpp
new file mode 100644
index 0000000..5174ae9
--- /dev/null
+++ b/libs/hwui/unit_tests/StringUtilsTests.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "utils/StringUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+TEST(StringUtils, simpleBuildSet) {
+ StringCollection collection("a b c");
+
+ EXPECT_TRUE(collection.has("a"));
+ EXPECT_TRUE(collection.has("b"));
+ EXPECT_TRUE(collection.has("c"));
+ EXPECT_FALSE(collection.has("d"));
+}
+
+TEST(StringUtils, advancedBuildSet) {
+ StringCollection collection("GL_ext1 GL_ext2 GL_ext3");
+
+ EXPECT_TRUE(collection.has("GL_ext1"));
+ EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
+}
+
+};
+};
diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp
new file mode 100644
index 0000000..a1df0e7
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StringUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+StringCollection::StringCollection(const char* spacedList) {
+ const char* current = spacedList;
+ const char* head = current;
+ do {
+ head = strchr(current, ' ');
+ std::string s(current, head ? head - current : strlen(current));
+ if (s.length()) {
+ mSet.insert(s);
+ }
+ current = head + 1;
+ } while (head);
+}
+
+bool StringCollection::has(const char* s) {
+ return mSet.find(std::string(s)) != mSet.end();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
new file mode 100644
index 0000000..ef2a6d5
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef STRING_UTILS_H
+#define STRING_UTILS_H
+
+#include <string>
+#include <unordered_set>
+
+namespace android {
+namespace uirenderer {
+
+class StringCollection {
+public:
+ StringCollection(const char* spacedList);
+ bool has(const char* string);
+private:
+ std::unordered_set<std::string> mSet;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* GLUTILS_H */
diff --git a/packages/DocumentsUI/res/layout/directory_cluster.xml b/packages/DocumentsUI/res/layout/directory_cluster.xml
index e47e196..8245e53 100644
--- a/packages/DocumentsUI/res/layout/directory_cluster.xml
+++ b/packages/DocumentsUI/res/layout/directory_cluster.xml
@@ -18,6 +18,13 @@
android:layout_height="match_parent"
android:orientation="vertical">
+ <FrameLayout
+ android:id="@+id/container_message_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="8dp"
+ android:background="@color/material_grey_50"/>
+
<com.android.documentsui.DirectoryContainerView
android:id="@+id/container_directory"
android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 7aff497..f5c73d5 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -34,18 +34,42 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerView"
- android:scrollbars="vertical"
+ <LinearLayout
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/grid_padding_horiz"
- android:paddingEnd="@dimen/grid_padding_horiz"
- android:paddingTop="@dimen/grid_padding_vert"
- android:paddingBottom="@dimen/grid_padding_vert"
- android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
- android:drawSelectorOnTop="true"
- android:background="@color/directory_background" />
+ android:orientation="vertical"
+ android:animateLayoutChanges="true">
+
+ <FrameLayout
+ android:id="@+id/container_message_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="8dp"
+ android:background="@color/material_grey_50"
+ android:visibility="gone"/>
+
+ <!-- This FrameLayout works around b/24189541 -->
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/grid_padding_horiz"
+ android:paddingEnd="@dimen/grid_padding_horiz"
+ android:paddingTop="@dimen/grid_padding_vert"
+ android:paddingBottom="@dimen/grid_padding_vert"
+ android:clipToPadding="false"
+ android:scrollbarStyle="outsideOverlay"
+ android:drawSelectorOnTop="true"
+ android:background="@color/directory_background" />
+
+ </FrameLayout>
+
+ </LinearLayout>
</com.android.documentsui.DirectoryView>
diff --git a/packages/DocumentsUI/res/layout/fragment_message_bar.xml b/packages/DocumentsUI/res/layout/fragment_message_bar.xml
new file mode 100644
index 0000000..47e4e77
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_message_bar.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:animateLayoutChanges="true"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="vertical"
+ android:paddingLeft="@dimen/list_item_padding"
+ android:paddingRight="@dimen/list_item_padding"
+ android:paddingTop="@dimen/list_item_padding">
+
+ <LinearLayout
+ android:id="@+id/container_info"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_height="@dimen/icon_size"
+ android:layout_width="@dimen/icon_size">
+
+ <ImageView
+ android:contentDescription="@null"
+ android:id="@+id/icon_info"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/textview_info"
+ android:layout_gravity="center_vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:selectAllOnFocus="true"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/container_error"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_height="@dimen/icon_size"
+ android:layout_width="@dimen/icon_size">
+
+ <ImageView
+ android:contentDescription="@null"
+ android:id="@+id/icon_error"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:scaleType="centerInside"/>
+
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/textview_error"
+ android:layout_gravity="center_vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:selectAllOnFocus="true"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_gravity="right"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button_dismiss"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/button_dismiss"
+ style="?android:attr/buttonBarPositiveButtonStyle"/>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_loading_grid.xml b/packages/DocumentsUI/res/layout/item_loading_grid.xml
deleted file mode 100644
index 147dfd4..0000000
--- a/packages/DocumentsUI/res/layout/item_loading_grid.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.documentsui.GridItem xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/grid_height"
- android:orientation="horizontal">
-
- <ProgressBar
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:indeterminate="true"
- style="?android:attr/progressBarStyle" />
-
-</com.android.documentsui.GridItem>
diff --git a/packages/DocumentsUI/res/layout/item_loading_list.xml b/packages/DocumentsUI/res/layout/item_loading_list.xml
deleted file mode 100644
index 6f214ed..0000000
--- a/packages/DocumentsUI/res/layout/item_loading_list.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/list_item_height">
-
- <ProgressBar
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:indeterminate="true"
- style="?android:attr/progressBarStyle" />
-
-</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_message_grid.xml b/packages/DocumentsUI/res/layout/item_message_grid.xml
deleted file mode 100644
index 45d61a5..0000000
--- a/packages/DocumentsUI/res/layout/item_message_grid.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.documentsui.GridItem xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/grid_height"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dp"
- android:paddingBottom="8dp">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription="@null" />
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:maxLines="4"
- android:ellipsize="end"
- android:paddingTop="8dp"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Body1"
- android:textColor="?android:attr/textColorPrimary" />
-
- </LinearLayout>
-
-</com.android.documentsui.GridItem>
diff --git a/packages/DocumentsUI/res/layout/item_message_list.xml b/packages/DocumentsUI/res/layout/item_message_list.xml
deleted file mode 100644
index 44c8baf..0000000
--- a/packages/DocumentsUI/res/layout/item_message_list.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/list_item_height"
- android:paddingStart="@dimen/list_item_padding"
- android:paddingEnd="@dimen/list_item_padding"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:baselineAligned="false">
-
- <FrameLayout
- android:layout_width="@dimen/icon_size"
- android:layout_height="@dimen/icon_size"
- android:layout_marginEnd="16dp">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:scaleType="centerInside"
- android:contentDescription="@null" />
-
- </FrameLayout>
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:maxLines="2"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Body1"
- android:textColor="?android:attr/textColorPrimary" />
-
-</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 4b44d7c..28018f8e 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -79,6 +79,8 @@
<string name="button_copy">Copy</string>
<!-- Button label that moves files to the current directory [CHAR LIMIT=24] -->
<string name="button_move">Move</string>
+ <!-- Button label that hides the error bar [CHAR LIMIT=24] -->
+ <string name="button_dismiss">Dismiss</string>
<!-- Mode that sorts documents by their display name alphabetically [CHAR LIMIT=24] -->
<string name="sort_name">By name</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 3202876..c2821e1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -159,6 +159,8 @@
private GridLayoutManager mGridLayout;
private int mColumnCount = 1; // This will get updated when layout changes.
+ private MessageBar mMessageBar;
+
public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
show(fm, TYPE_NORMAL, root, doc, null, anim);
}
@@ -220,6 +222,8 @@
final Resources res = context.getResources();
final View view = inflater.inflate(R.layout.fragment_directory, container, false);
+ mMessageBar = MessageBar.create(getChildFragmentManager());
+
mEmptyView = view.findViewById(android.R.id.empty);
mRecView = (RecyclerView) view.findViewById(R.id.recyclerView);
@@ -611,7 +615,7 @@
@Override
public boolean onBeforeItemStateChange(int position, boolean selected) {
- // Directories and footer items cannot be checked
+ // Directories cannot be checked
if (selected) {
final Cursor cursor = mModel.getItem(position);
checkNotNull(cursor, "Cursor cannot be null.");
@@ -880,79 +884,6 @@
return ((BaseActivity) fragment.getActivity()).getDisplayState();
}
- private static abstract class Footer {
- private final int mItemViewType;
-
- public Footer(int itemViewType) {
- mItemViewType = itemViewType;
- }
-
- public abstract View getView(View convertView, ViewGroup parent);
-
- public int getItemViewType() {
- return mItemViewType;
- }
- }
-
- private class LoadingFooter extends Footer {
- public LoadingFooter() {
- super(1);
- }
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- final State state = getDisplayState(DirectoryFragment.this);
-
- if (convertView == null) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- if (state.derivedMode == MODE_LIST) {
- convertView = inflater.inflate(R.layout.item_loading_list, parent, false);
- } else if (state.derivedMode == MODE_GRID) {
- convertView = inflater.inflate(R.layout.item_loading_grid, parent, false);
- } else {
- throw new IllegalStateException();
- }
- }
-
- return convertView;
- }
- }
-
- private class MessageFooter extends Footer {
- private final int mIcon;
- private final String mMessage;
-
- public MessageFooter(int itemViewType, int icon, String message) {
- super(itemViewType);
- mIcon = icon;
- mMessage = message;
- }
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- final State state = getDisplayState(DirectoryFragment.this);
-
- if (convertView == null) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- if (state.derivedMode == MODE_LIST) {
- convertView = inflater.inflate(R.layout.item_message_list, parent, false);
- } else if (state.derivedMode == MODE_GRID) {
- convertView = inflater.inflate(R.layout.item_message_grid, parent, false);
- } else {
- throw new IllegalStateException();
- }
- }
-
- final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- icon.setImageResource(mIcon);
- title.setText(mMessage);
- return convertView;
- }
- }
-
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
@@ -971,24 +902,18 @@
private final Context mContext;
private final LayoutInflater mInflater;
- // TODO: Bring back support for footers.
- private final List<Footer> mFooters = new ArrayList<>();
public DocumentsAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(context);
}
+ @Override
public void onModelUpdate(Model model) {
- mFooters.clear();
- if (model.info != null) {
- mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, model.info));
- }
- if (model.error != null) {
- mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, model.error));
- }
- if (model.isLoading()) {
- mFooters.add(new LoadingFooter());
+ if (model.info != null || model.error != null) {
+ mMessageBar.setInfo(model.info);
+ mMessageBar.setError(model.error);
+ mMessageBar.show();
}
if (model.isEmpty()) {
@@ -1000,9 +925,10 @@
notifyDataSetChanged();
}
+ @Override
public void onModelUpdateFailed(Exception e) {
+ // TODO: deal with catastrophic update failures
String error = getString(R.string.query_error);
- mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
notifyDataSetChanged();
}
@@ -1222,19 +1148,8 @@
@Override
public int getItemCount() {
return mModel.getItemCount();
- // return mCursorCount + mFooters.size();
}
- @Override
- public int getItemViewType(int position) {
- final int itemCount = mModel.getItemCount();
- if (position < itemCount) {
- return 0;
- } else {
- position -= itemCount;
- return mFooters.get(position).getItemViewType();
- }
- }
}
private static String formatTime(Context context, long when) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
new file mode 100644
index 0000000..a48fd5c
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
@@ -0,0 +1,129 @@
+/*
+ * 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.annotation.Nullable;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A message bar displaying some info/error messages and a Dismiss button.
+ */
+public class MessageBar extends Fragment {
+ private View mView;
+ private ViewGroup mContainer;
+
+ /**
+ * Creates an instance of a MessageBar. Note that the new MessagBar is not visible by default,
+ * and has to be shown by calling MessageBar.show.
+ */
+ public static MessageBar create(FragmentManager fm) {
+ final MessageBar fragment = new MessageBar();
+
+ final FragmentTransaction ft = fm.beginTransaction();
+ ft.replace(R.id.container_message_bar, fragment);
+ ft.commitAllowingStateLoss();
+
+ return fragment;
+ }
+
+ /**
+ * Sets the info message. Can be null, in which case no info message will be displayed. The
+ * message bar layout will be adjusted accordingly.
+ */
+ public void setInfo(@Nullable String info) {
+ View infoContainer = mView.findViewById(R.id.container_info);
+ if (info != null) {
+ TextView infoText = (TextView) mView.findViewById(R.id.textview_info);
+ infoText.setText(info);
+ infoContainer.setVisibility(View.VISIBLE);
+ } else {
+ infoContainer.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Sets the error message. Can be null, in which case no error message will be displayed. The
+ * message bar layout will be adjusted accordingly.
+ */
+ public void setError(@Nullable String error) {
+ View errorView = mView.findViewById(R.id.container_message_bar);
+ if (error != null) {
+ TextView errorText = (TextView) mView.findViewById(R.id.textview_error);
+ errorText.setText(error);
+ errorView.setVisibility(View.VISIBLE);
+ } else {
+ errorView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ mView = inflater.inflate(R.layout.fragment_message_bar, container, false);
+
+ ImageView infoIcon = (ImageView) mView.findViewById(R.id.icon_info);
+ infoIcon.setImageResource(R.drawable.ic_dialog_info);
+
+ ImageView errorIcon = (ImageView) mView.findViewById(R.id.icon_error);
+ errorIcon.setImageResource(R.drawable.ic_dialog_alert);
+
+ Button dismiss = (Button) mView.findViewById(R.id.button_dismiss);
+ dismiss.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hide();
+ }
+ });
+
+ mContainer = container;
+
+ return mView;
+ }
+
+ void hide() {
+ // The container view is used to show/hide the error bar. If a container is not provided,
+ // fall back to showing/hiding the error bar View, which also works, but does not provide
+ // the same animated transition.
+ if (mContainer != null) {
+ mContainer.setVisibility(View.GONE);
+ } else {
+ mView.setVisibility(View.GONE);
+ }
+ }
+
+ void show() {
+ // The container view is used to show/hide the error bar. If a container is not provided,
+ // fall back to showing/hiding the error bar View, which also works, but does not provide
+ // the same animated transition.
+ if (mContainer != null) {
+ mContainer.setVisibility(View.VISIBLE);
+ } else {
+ mView.setVisibility(View.VISIBLE);
+ }
+ }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e4fc075..93dc889 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -205,6 +205,7 @@
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:screenOrientation="behind"
+ android:resizeableActivity="true"
android:theme="@style/config_recents_activity_theme">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a4acf83..e1a8815 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -45,8 +45,6 @@
public static final boolean EnableSearchLayout = true;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
- // Enables all system stacks to show up in the same recents stack
- public static final boolean EnableMultiStackToSingleStack = true;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the simulated task affiliations
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0cfa7a1..308e7cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -116,7 +116,7 @@
/** Preloads the next task */
public void run() {
// Temporarily skip this if multi stack is enabled
- if (mConfig.multiStackEnabled) return;
+ if (mConfig.multiWindowEnabled) return;
RecentsConfiguration config = RecentsConfiguration.getInstance();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
@@ -176,7 +176,6 @@
// Task launching
RecentsConfiguration mConfig;
- Rect mWindowRect = new Rect();
Rect mTaskStackBounds = new Rect();
Rect mSystemInsets = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -372,9 +371,9 @@
if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
- TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
- if (top.getTaskCount() > 0) {
- preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
+ if (stack.getTaskCount() > 0) {
+ preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView,
topTaskHome.value);
}
}
@@ -388,16 +387,10 @@
void showRelativeAffiliatedTask(boolean showNextTask) {
// Return early if there is no focused stack
int focusedStackId = mSystemServicesProxy.getFocusedStack();
- TaskStack focusedStack = null;
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
- if (mConfig.multiStackEnabled) {
- if (focusedStackId < 0) return;
- focusedStack = plan.getTaskStack(focusedStackId);
- } else {
- focusedStack = plan.getAllTaskStacks().get(0);
- }
+ TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
@@ -502,7 +495,8 @@
/** Prepares the header bar layout. */
void reloadHeaderBarLayout() {
Resources res = mContext.getResources();
- mWindowRect = mSystemServicesProxy.getWindowRect();
+ Rect windowRect = mSystemServicesProxy.getWindowRect();
+
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
@@ -513,10 +507,10 @@
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
- mConfig.getSearchBarBounds(mWindowRect.width(), mWindowRect.height(),
+ mConfig.getSearchBarBounds(windowRect,
mStatusBarHeight, searchBarBounds);
}
- mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(),
+ mConfig.getAvailableTaskStackBounds(windowRect,
mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), searchBarBounds,
mTaskStackBounds);
if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
@@ -531,7 +525,7 @@
TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mSystemInsets.bottom;
- algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
+ algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
Rect taskViewSize = algo.getUntransformedTaskViewSize();
int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
synchronized (mHeaderBarLock) {
@@ -540,6 +534,7 @@
mHeaderBar.measure(
View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
+ // TODO: may not be needed
mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
}
}
@@ -749,10 +744,9 @@
// Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
// For multi-stack we need to figure out where each of the tasks are going.
- if (mConfig.multiStackEnabled) {
+ if (mConfig.multiWindowEnabled) {
loader.preloadTasks(sInstanceLoadPlan, true);
- ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
- TaskStack stack = stacks.get(0);
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
@@ -765,8 +759,7 @@
if (!sInstanceLoadPlan.hasTasks()) {
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
}
- ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
- TaskStack stack = stacks.get(0);
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d0876fa..4bb3e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -205,10 +206,10 @@
loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
loader.loadTasks(this, plan, loadOpts);
- ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
+ TaskStack stack = plan.getTaskStack();
mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
if (!mConfig.launchedWithNoRecentTasks) {
- mRecentsView.setTaskStacks(stacks);
+ mRecentsView.setTaskStack(stack);
}
// Create the home intent runnable
@@ -224,20 +225,16 @@
R.anim.recents_to_launcher_exit));
// Mark the task that is the launch target
- int taskStackCount = stacks.size();
int launchTaskIndexInStack = 0;
if (mConfig.launchedToTaskId != -1) {
- for (int i = 0; i < taskStackCount; i++) {
- TaskStack stack = stacks.get(i);
- ArrayList<Task> tasks = stack.getTasks();
- int taskCount = tasks.size();
- for (int j = 0; j < taskCount; j++) {
- Task t = tasks.get(j);
- if (t.key.id == mConfig.launchedToTaskId) {
- t.isLaunchTarget = true;
- launchTaskIndexInStack = tasks.size() - j - 1;
- break;
- }
+ ArrayList<Task> tasks = stack.getTasks();
+ int taskCount = tasks.size();
+ for (int j = 0; j < taskCount; j++) {
+ Task t = tasks.get(j);
+ if (t.key.id == mConfig.launchedToTaskId) {
+ t.isLaunchTarget = true;
+ launchTaskIndexInStack = tasks.size() - j - 1;
+ break;
}
}
}
@@ -278,11 +275,7 @@
MetricsLogger.count(this, "overview_source_home", 1);
}
// Keep track of the total stack task count
- int taskCount = 0;
- for (int i = 0; i < stacks.size(); i++) {
- TaskStack stack = stacks.get(i);
- taskCount += stack.getTaskCount();
- }
+ int taskCount = stack.getTaskCount();
MetricsLogger.histogram(this, "overview_task_count", taskCount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index dfe7e96..b41b5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -134,7 +134,7 @@
public boolean fakeShadows;
/** Dev options and global settings */
- public boolean multiStackEnabled;
+ public boolean multiWindowEnabled;
public boolean lockToAppEnabled;
public boolean developerOptionsEnabled;
public boolean debugModeEnabled;
@@ -283,7 +283,7 @@
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0;
lockToAppEnabled = ssp.getSystemSetting(context,
Settings.System.LOCK_TO_APP_ENABLED) != 0;
- multiStackEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
+ multiWindowEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
}
/** Called when the configuration has changed, and we want to reset any configuration specific
@@ -320,14 +320,16 @@
* Returns the task stack bounds in the current orientation. These bounds do not account for
* the system insets.
*/
- public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset,
+ public void getAvailableTaskStackBounds(Rect windowBounds, int topInset,
int rightInset, Rect searchBarBounds, Rect taskStackBounds) {
if (isLandscape && hasTransposedSearchBar) {
// In landscape, the search bar appears on the left, but we overlay it on top
- taskStackBounds.set(0, topInset, windowWidth - rightInset, windowHeight);
+ taskStackBounds.set(windowBounds.left, windowBounds.top + topInset,
+ windowBounds.right - rightInset, windowBounds.bottom);
} else {
// In portrait, the search bar appears on the top (which already has the inset)
- taskStackBounds.set(0, searchBarBounds.bottom, windowWidth, windowHeight);
+ taskStackBounds.set(windowBounds.left, searchBarBounds.bottom,
+ windowBounds.right, windowBounds.bottom);
}
}
@@ -335,16 +337,17 @@
* Returns the search bar bounds in the current orientation. These bounds do not account for
* the system insets.
*/
- public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
- Rect searchBarSpaceBounds) {
+ public void getSearchBarBounds(Rect windowBounds, int topInset, Rect searchBarSpaceBounds) {
// Return empty rects if search is not enabled
int searchBarSize = searchBarSpaceHeightPx;
if (isLandscape && hasTransposedSearchBar) {
// In landscape, the search bar appears on the left
- searchBarSpaceBounds.set(0, topInset, searchBarSize, windowHeight);
+ searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
+ windowBounds.left + searchBarSize, windowBounds.bottom);
} else {
// In portrait, the search bar appears on the top
- searchBarSpaceBounds.set(0, topInset, windowWidth, topInset + searchBarSize);
+ searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
+ windowBounds.right, windowBounds.top + topInset + searchBarSize);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 300ea2a..703c7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -24,7 +24,6 @@
import android.content.DialogInterface;
import android.graphics.Rect;
import android.os.Bundle;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -32,11 +31,8 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.views.RecentsView;
-import java.util.ArrayList;
-
/**
* A helper for the dialogs that show when task debugging is on.
*/
@@ -216,7 +212,7 @@
// In debug mode, we force all task to be resizeable regardless of the
// current app configuration.
- if (RecentsConfiguration.getInstance().multiStackEnabled) {
+ if (RecentsConfiguration.getInstance().multiWindowEnabled) {
for (int i = additionalTasks; i >= 0; --i) {
if (mTasks[i] != null) {
mSsp.setTaskResizeable(mTasks[i].key.id);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index bead1b0..5790ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -641,12 +641,19 @@
*/
public Rect getWindowRect() {
Rect windowRect = new Rect();
- if (mWm == null) return windowRect;
+ if (mIam == null) return windowRect;
- Point p = new Point();
- mWm.getDefaultDisplay().getRealSize(p);
- windowRect.set(0, 0, p.x, p.y);
- return windowRect;
+ try {
+ // Use the home stack bounds
+ ActivityManager.StackInfo stackInfo = mIam.getStackInfo(ActivityManager.HOME_STACK_ID);
+ if (stackInfo != null) {
+ windowRect.set(stackInfo.bounds);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } finally {
+ return windowRect;
+ }
}
/** Starts an activity from recents. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index b8015c0..649cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -20,12 +20,10 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -63,7 +61,7 @@
SystemServicesProxy mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> mRawTasks;
- SparseArray<TaskStack> mStacks = new SparseArray<>();
+ TaskStack mStack;
HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
@@ -96,11 +94,8 @@
// This activity info cache will be used for both preloadPlan() and executePlan()
mActivityInfoCache.clear();
- // TODO (multi-display): Currently assume the primary display
- Rect displayBounds = mSystemServicesProxy.getWindowRect();
-
Resources res = mContext.getResources();
- SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>();
+ ArrayList<Task> stackTasks = new ArrayList<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
@@ -152,37 +147,13 @@
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
- if (!mConfig.multiStackEnabled ||
- Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
- int firstStackId = 0;
- ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
- if (stackTasks == null) {
- stackTasks = new ArrayList<>();
- stacksTasks.put(firstStackId, stackTasks);
- }
- stackTasks.add(task);
- } else {
- ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
- if (stackTasks == null) {
- stackTasks = new ArrayList<>();
- stacksTasks.put(t.stackId, stackTasks);
- }
- stackTasks.add(task);
- }
+ stackTasks.add(task);
}
// Initialize the stacks
- mStacks.clear();
- int stackCount = stacksTasks.size();
- for (int i = 0; i < stackCount; i++) {
- int stackId = stacksTasks.keyAt(i);
- ArrayList<Task> stackTasks = stacksTasks.valueAt(i);
- TaskStack stack = new TaskStack(stackId);
- stack.setBounds(displayBounds, displayBounds);
- stack.setTasks(stackTasks);
- stack.createAffiliatedGroupings(mConfig);
- mStacks.put(stackId, stack);
- }
+ mStack = new TaskStack();
+ mStack.setTasks(stackTasks);
+ mStack.createAffiliatedGroupings(mConfig);
}
/**
@@ -197,92 +168,70 @@
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
- int stackCount = mStacks.size();
- for (int j = 0; j < stackCount; j++) {
- ArrayList<Task> tasks = mStacks.valueAt(j).getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
- Task task = tasks.get(i);
- Task.TaskKey taskKey = task.key;
+ ArrayList<Task> tasks = mStack.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+ Task task = tasks.get(i);
+ Task.TaskKey taskKey = task.key;
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hadCachedActivityInfo = false;
- if (mActivityInfoCache.containsKey(cnKey)) {
- infoHandle = mActivityInfoCache.get(cnKey);
- hadCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
+ // Get an existing activity info handle if possible
+ Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
+ ActivityInfoHandle infoHandle;
+ boolean hadCachedActivityInfo = false;
+ if (mActivityInfoCache.containsKey(cnKey)) {
+ infoHandle = mActivityInfoCache.get(cnKey);
+ hadCachedActivityInfo = true;
+ } else {
+ infoHandle = new ActivityInfoHandle();
+ }
+
+ boolean isRunningTask = (task.key.id == opts.runningTaskId);
+ boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+ boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+
+ // If requested, skip the running task
+ if (opts.onlyLoadPausedActivities && isRunningTask) {
+ continue;
+ }
+
+ if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+ if (task.activityIcon == null) {
+ if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
+ task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
+ t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
}
-
- boolean isRunningTask = (task.key.id == opts.runningTaskId);
- boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
- boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
- // If requested, skip the running task
- if (opts.onlyLoadPausedActivities && isRunningTask) {
- continue;
- }
-
- if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
- if (task.activityIcon == null) {
- if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
- task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
- t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+ }
+ if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
+ if (task.thumbnail == null || isRunningTask) {
+ if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
+ if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ mSystemServicesProxy, true);
+ } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+ loadQueue.addTask(task);
}
}
- if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
- if (task.thumbnail == null || isRunningTask) {
- if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
- if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
- mSystemServicesProxy, true);
- } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
- loadQueue.addTask(task);
- }
- }
- }
+ }
- // Update the activity info cache
- if (!hadCachedActivityInfo && infoHandle.info != null) {
- mActivityInfoCache.put(cnKey, infoHandle);
- }
+ // Update the activity info cache
+ if (!hadCachedActivityInfo && infoHandle.info != null) {
+ mActivityInfoCache.put(cnKey, infoHandle);
}
}
}
/**
- * Returns all TaskStacks from the preloaded list of recent tasks.
+ * Returns the TaskStack from the preloaded list of recent tasks.
*/
- public ArrayList<TaskStack> getAllTaskStacks() {
- ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
- int stackCount = mStacks.size();
- for (int i = 0; i < stackCount; i++) {
- stacks.add(mStacks.valueAt(i));
- }
- // Ensure that we have at least one stack
- if (stacks.isEmpty()) {
- stacks.add(new TaskStack());
- }
- return stacks;
- }
-
- /**
- * Returns a specific TaskStack from the preloaded list of recent tasks.
- */
- public TaskStack getTaskStack(int stackId) {
- return mStacks.get(stackId);
+ public TaskStack getTaskStack() {
+ return mStack;
}
/** Returns whether there are any tasks in any stacks. */
public boolean hasTasks() {
- int stackCount = mStacks.size();
- for (int i = 0; i < stackCount; i++) {
- if (mStacks.valueAt(i).getTaskCount() > 0) {
- return true;
- }
+ if (mStack != null) {
+ return mStack.getTaskCount() > 0;
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 5aaea15..a760a41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -177,35 +177,17 @@
// The task offset to apply to a task id as a group affiliation
static final int IndividualTaskIdOffset = 1 << 16;
- public final int id;
- public final Rect stackBounds = new Rect();
- public final Rect displayBounds = new Rect();
-
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
- public TaskStack() {
- this(0);
- }
-
- public TaskStack(int stackId) {
- id = stackId;
- }
-
/** Sets the callbacks for this task stack. */
public void setCallbacks(TaskStackCallbacks cb) {
mCb = cb;
}
- /** Sets the bounds of this stack. */
- public void setBounds(Rect stackBounds, Rect displayBounds) {
- this.stackBounds.set(stackBounds);
- this.displayBounds.set(displayBounds);
- }
-
/** Resets this TaskStack. */
public void reset() {
mCb = null;
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 651b29a..c7d1dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -79,10 +79,9 @@
RecentsConfiguration mConfig;
LayoutInflater mInflater;
DebugOverlayView mDebugOverlay;
- RecentsViewLayoutAlgorithm mLayoutAlgorithm;
ArrayList<TaskStack> mStacks;
- List<TaskStackView> mTaskStackViews = new ArrayList<>();
+ TaskStackView mTaskStackView;
RecentsAppWidgetHostView mSearchBar;
RecentsViewCallbacks mCb;
@@ -102,7 +101,6 @@
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig);
}
/** Sets the callbacks */
@@ -116,61 +114,41 @@
}
/** Set/get the bsp root node */
- public void setTaskStacks(ArrayList<TaskStack> stacks) {
- int numStacks = stacks.size();
-
- // Remove all/extra stack views
- int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout
+ public void setTaskStack(TaskStack stack) {
if (mConfig.launchedReuseTaskStackViews) {
- numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks);
- }
- for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
- removeView(mTaskStackViews.remove(i));
- }
-
- // Update the stack views that we are keeping
- for (int i = 0; i < numTaskStacksToKeep; i++) {
- TaskStackView tsv = mTaskStackViews.get(i);
- // If onRecentsHidden is not triggered, we need to the stack view again here
- tsv.reset();
- tsv.setStack(stacks.get(i));
- }
-
- // Add remaining/recreate stack views
- mStacks = stacks;
- for (int i = mTaskStackViews.size(); i < numStacks; i++) {
- TaskStack stack = stacks.get(i);
- TaskStackView stackView = new TaskStackView(getContext(), stack);
- stackView.setCallbacks(this);
- addView(stackView);
- mTaskStackViews.add(stackView);
+ if (mTaskStackView != null) {
+ // If onRecentsHidden is not triggered, we need to the stack view again here
+ mTaskStackView.reset();
+ mTaskStackView.setStack(stack);
+ } else {
+ mTaskStackView = new TaskStackView(getContext(), stack);
+ mTaskStackView.setCallbacks(this);
+ addView(mTaskStackView);
+ }
+ } else {
+ if (mTaskStackView != null) {
+ removeView(mTaskStackView);
+ }
+ mTaskStackView = new TaskStackView(getContext(), stack);
+ mTaskStackView.setCallbacks(this);
+ addView(mTaskStackView);
}
// Enable debug mode drawing on all the stacks if necessary
if (mConfig.debugModeEnabled) {
- for (int i = mTaskStackViews.size() - 1; i >= 0; i--) {
- TaskStackView stackView = mTaskStackViews.get(i);
- stackView.setDebugOverlay(mDebugOverlay);
- }
+ mTaskStackView.setDebugOverlay(mDebugOverlay);
}
// Trigger a new layout
requestLayout();
}
- /** Gets the list of task views */
- List<TaskStackView> getTaskStackViews() {
- return mTaskStackViews;
- }
-
/** Gets the next task in the stack - or if the last - the top task */
public Task getNextTaskOrTopTask(Task taskToSearch) {
Task returnTask = null;
boolean found = false;
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = stackCount - 1; i >= 0; --i) {
- TaskStack stack = stackViews.get(i).getStack();
+ if (mTaskStackView != null) {
+ TaskStack stack = mTaskStackView.getStack();
ArrayList<Task> taskList = stack.getTasks();
// Iterate the stack views and try and find the focused task
for (int j = taskList.size() - 1; j >= 0; --j) {
@@ -190,20 +168,16 @@
/** Launches the focused task from the first stack if possible */
public boolean launchFocusedTask() {
- // Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- TaskStack stack = stackView.getStack();
+ if (mTaskStackView != null) {
+ TaskStack stack = mTaskStackView.getStack();
// Iterate the stack views and try and find the focused task
- List<TaskView> taskViews = stackView.getTaskViews();
+ List<TaskView> taskViews = mTaskStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int j = 0; j < taskViewCount; j++) {
TaskView tv = taskViews.get(j);
Task task = tv.getTask();
if (tv.isFocusedTask()) {
- onTaskViewClicked(stackView, tv, stack, task, false, false, null);
+ onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
return true;
}
}
@@ -213,19 +187,15 @@
/** Launches a given task. */
public boolean launchTask(Task task, Rect taskBounds) {
- // Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- TaskStack stack = stackView.getStack();
+ if (mTaskStackView != null) {
+ TaskStack stack = mTaskStackView.getStack();
// Iterate the stack views and try and find the given task.
- List<TaskView> taskViews = stackView.getTaskViews();
+ List<TaskView> taskViews = mTaskStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int j = 0; j < taskViewCount; j++) {
TaskView tv = taskViews.get(j);
if (tv.getTask() == task) {
- onTaskViewClicked(stackView, tv, stack, task, false, true, taskBounds);
+ onTaskViewClicked(mTaskStackView, tv, stack, task, false, true, taskBounds);
return true;
}
}
@@ -235,12 +205,8 @@
/** Launches the task that Recents was launched from, if possible */
public boolean launchPreviousTask() {
- // Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- TaskStack stack = stackView.getStack();
+ if (mTaskStackView != null) {
+ TaskStack stack = mTaskStackView.getStack();
ArrayList<Task> tasks = stack.getTasks();
// Find the launch task in the stack
@@ -249,8 +215,8 @@
for (int j = 0; j < taskCount; j++) {
if (tasks.get(j).isLaunchTarget) {
Task task = tasks.get(j);
- TaskView tv = stackView.getChildViewForTask(task);
- onTaskViewClicked(stackView, tv, stack, task, false, false, null);
+ TaskView tv = mTaskStackView.getChildViewForTask(task);
+ onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
return true;
}
}
@@ -264,12 +230,8 @@
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
-
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- stackView.startEnterRecentsAnimation(ctx);
+ if (mTaskStackView != null) {
+ mTaskStackView.startEnterRecentsAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
}
@@ -279,11 +241,8 @@
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- stackView.startExitToHomeAnimation(ctx);
+ if (mTaskStackView != null) {
+ mTaskStackView.startExitToHomeAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
@@ -329,31 +288,19 @@
// Get the search bar bounds and measure the search bar layout
Rect searchBarSpaceBounds = new Rect();
if (mSearchBar != null) {
- mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
+ mConfig.getSearchBarBounds(new Rect(0, 0, width, height), mConfig.systemInsets.top,
+ searchBarSpaceBounds);
mSearchBar.measure(
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
}
Rect taskStackBounds = new Rect();
- mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top,
+ mConfig.getAvailableTaskStackBounds(new Rect(0, 0, width, height), mConfig.systemInsets.top,
mConfig.systemInsets.right, searchBarSpaceBounds, taskStackBounds);
-
- // Measure each TaskStackView with the full width and height of the window since the
- // transition view is a child of that stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews,
- taskStackBounds);
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- if (stackView.getVisibility() != GONE) {
- // We are going to measure the TaskStackView with the whole RecentsView dimensions,
- // but the actual stack is going to be inset to the bounds calculated by the layout
- // algorithm
- stackView.setStackInsetRect(stackViewsBounds.get(i));
- stackView.measure(widthMeasureSpec, heightMeasureSpec);
- }
+ if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
+ mTaskStackView.setTaskStackBounds(taskStackBounds);
+ mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
}
setMeasuredDimension(width, height);
@@ -365,24 +312,17 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Get the search bar bounds so that we lay it out
+ Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ Rect searchBarSpaceBounds = new Rect();
if (mSearchBar != null) {
- Rect searchBarSpaceBounds = new Rect();
- mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
+ mConfig.getSearchBarBounds(measuredRect,
mConfig.systemInsets.top, searchBarSpaceBounds);
mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
}
- // Layout each TaskStackView with the full width and height of the window since the
- // transition view is a child of that stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- if (stackView.getVisibility() != GONE) {
- stackView.layout(left, top, left + stackView.getMeasuredWidth(),
- top + stackView.getMeasuredHeight());
- }
+ if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
+ mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
}
}
@@ -397,29 +337,24 @@
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- stackView.onUserInteraction();
+ if (mTaskStackView != null) {
+ mTaskStackView.onUserInteraction();
}
}
/** Focuses the next task in the first stack view */
public void focusNextTask(boolean forward) {
// Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- if (!stackViews.isEmpty()) {
- stackViews.get(0).focusNextTask(forward, true);
+ if (mTaskStackView != null) {
+ mTaskStackView.focusNextTask(forward, true);
}
}
/** Dismisses the focused task. */
public void dismissFocusedTask() {
// Get the first stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- if (!stackViews.isEmpty()) {
- stackViews.get(0).dismissFocusedTask();
+ if (mTaskStackView != null) {
+ mTaskStackView.dismissFocusedTask();
}
}
@@ -442,9 +377,8 @@
}
public void disableLayersForOneFrame() {
- List<TaskStackView> stackViews = getTaskStackViews();
- for (int i = 0; i < stackViews.size(); i++) {
- stackViews.get(i).disableLayersForOneFrame();
+ if (mTaskStackView != null) {
+ mTaskStackView.disableLayersForOneFrame();
}
}
@@ -655,7 +589,7 @@
}
postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
animStartedListener);
- if (mConfig.multiStackEnabled) {
+ if (mConfig.multiWindowEnabled) {
opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit,
@@ -767,11 +701,8 @@
/** Final callback after Recents is finally hidden. */
public void onRecentsHidden() {
// Notify each task stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- stackView.onRecentsHidden();
+ if (mTaskStackView != null) {
+ mTaskStackView.onRecentsHidden();
}
}
@@ -815,11 +746,8 @@
@Override
public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
// Propagate this event down to each task stack view
- List<TaskStackView> stackViews = getTaskStackViews();
- int stackCount = stackViews.size();
- for (int i = 0; i < stackCount; i++) {
- TaskStackView stackView = stackViews.get(i);
- stackView.onPackagesChanged(monitor, packageName, userId);
+ if (mTaskStackView != null) {
+ mTaskStackView.onPackagesChanged(monitor, packageName, userId);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
deleted file mode 100644
index eea273c..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.views;
-
-import android.graphics.Rect;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.model.TaskStack;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/* The layout logic for the RecentsView. */
-public class RecentsViewLayoutAlgorithm {
-
- RecentsConfiguration mConfig;
-
- public RecentsViewLayoutAlgorithm(RecentsConfiguration config) {
- mConfig = config;
- }
-
- /** Return the relative coordinate given coordinates in another size. */
- private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) {
- float relPos = (float) otherCoord / otherSize;
- return availableOffset + (int) (relPos * availableSize);
- }
-
- /**
- * Computes and returns the bounds that each of the stack views should take up.
- */
- List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) {
- ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size());
- int stackViewsCount = stackViews.size();
- for (int i = 0; i < stackViewsCount; i++) {
- TaskStack stack = stackViews.get(i).getStack();
- Rect sb = stack.stackBounds;
- Rect db = stack.displayBounds;
- Rect ab = availableBounds;
- bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()),
- getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()),
- getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()),
- getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height())));
- }
- return bounds;
- }
-}
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 4e82c8a..8058c5e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -80,7 +80,6 @@
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
DozeTrigger mUIDozeTrigger;
DebugOverlayView mDebugOverlay;
- Rect mTaskStackBounds = new Rect();
DismissView mDismissAllButton;
boolean mDismissAllButtonAnimating;
int mFocusedTaskIndex = -1;
@@ -93,6 +92,7 @@
boolean mStartEnterAnimationRequestedAfterLayout;
boolean mStartEnterAnimationCompleted;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
+ Rect mTaskStackBounds = new Rect();
int[] mTmpVisibleRange = new int[2];
float[] mTmpCoord = new float[2];
Matrix mTmpMatrix = new Matrix();
@@ -477,11 +477,6 @@
mStackViewsClipDirty = false;
}
- /** The stack insets to apply to the stack contents */
- public void setStackInsetRect(Rect r) {
- mTaskStackBounds.set(r);
- }
-
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
boolean launchedFromHome) {
@@ -692,11 +687,6 @@
return mTouchHandler.onGenericMotionEvent(ev);
}
- /** Returns the region that touch gestures can be started in. */
- Rect getTouchableRegion() {
- return mTaskStackBounds;
- }
-
@Override
public void computeScroll() {
mStackScroller.computeScroll();
@@ -736,6 +726,10 @@
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
+ public void setTaskStackBounds(Rect taskStackBounds) {
+ mTaskStackBounds.set(taskStackBounds);
+ }
+
/**
* This is called with the full window width and height to allow stack view children to
* perform the full screen transition down.
@@ -746,9 +740,7 @@
int height = MeasureSpec.getSize(heightMeasureSpec);
// Compute our stack/task rects
- Rect taskStackBounds = new Rect(mTaskStackBounds);
- taskStackBounds.bottom -= mConfig.systemInsets.bottom;
- computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
+ computeRects(width, height, mTaskStackBounds, mConfig.launchedWithAltTab,
mConfig.launchedFromHome);
// If this is the first layout, then scroll to the front of the stack and synchronize the
@@ -875,7 +867,7 @@
}
// Start dozing
- if (!mConfig.multiStackEnabled) {
+ if (!mConfig.multiWindowEnabled) {
mUIDozeTrigger.startDozing();
}
}
@@ -1299,7 +1291,7 @@
RecentsTaskLoader.getInstance().loadTaskData(task);
// If the doze trigger has already fired, then update the state for this task view
- if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
+ if (mConfig.multiWindowEnabled || mUIDozeTrigger.hasTriggered()) {
tv.setNoUserInteractionState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 2e0b80a..7d079d9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -128,16 +128,6 @@
return false;
}
- int action = ev.getAction();
- if (mConfig.multiStackEnabled) {
- // Check if we are within the bounds of the stack view contents
- if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
- if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
- return false;
- }
- }
- }
-
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
@@ -146,6 +136,7 @@
boolean wasScrolling = mScroller.isScrolling() ||
(mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
+ int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -212,16 +203,6 @@
return false;
}
- int action = ev.getAction();
- if (mConfig.multiStackEnabled) {
- // Check if we are within the bounds of the stack view contents
- if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
- if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
- return false;
- }
- }
- }
-
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
@@ -230,6 +211,7 @@
// Update the velocity tracker
initVelocityTrackerIfNotExists();
+ int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -372,7 +354,7 @@
// Shift the tap position toward the center of the task stack and check to see if it would
// have hit a view. The user might have tried to tap on a task and missed slightly.
int shiftedX = x;
- if (x > mSv.getTouchableRegion().centerX()) {
+ if (x > (mSv.getRight() - mSv.getLeft()) / 2) {
shiftedX -= mWindowTouchSlop;
} else {
shiftedX += mWindowTouchSlop;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index cbfe842..373fe7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -682,7 +682,7 @@
mHeaderView.mApplicationIcon.setOnClickListener(this);
}
mHeaderView.mDismissButton.setOnClickListener(this);
- if (mConfig.multiStackEnabled) {
+ if (mConfig.multiWindowEnabled) {
mHeaderView.mMoveTaskButton.setOnClickListener(this);
}
mActionButtonView.setOnClickListener(this);
@@ -701,7 +701,7 @@
// Unbind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(null);
mHeaderView.mDismissButton.setOnClickListener(null);
- if (mConfig.multiStackEnabled) {
+ if (mConfig.multiWindowEnabled) {
mHeaderView.mMoveTaskButton.setOnClickListener(null);
}
mActionButtonView.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 353bcbe..3e9410e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -215,8 +215,8 @@
mLightDismissDrawable : mDarkDismissDrawable);
mDismissButton.setContentDescription(String.format(mDismissContentDescription,
t.contentDescription));
- mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
- if (mConfig.multiStackEnabled) {
+ mMoveTaskButton.setVisibility((mConfig.multiWindowEnabled) ? View.VISIBLE : View.INVISIBLE);
+ if (mConfig.multiWindowEnabled) {
updateResizeTaskBarIcon(t);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index 2e6136f..8989625 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -16,9 +16,16 @@
package com.android.server.accessibility;
+import android.annotation.NonNull;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -46,16 +53,17 @@
* the class should handle cases where multiple mouse devices are present.
*/
public class AutoclickController implements EventStreamTransformation {
+
+ public static final int DEFAULT_CLICK_DELAY_MS = 600;
+
private static final String LOG_TAG = AutoclickController.class.getSimpleName();
- // TODO: Control click delay via settings.
- private static final int CLICK_DELAY_MS = 600;
-
private EventStreamTransformation mNext;
- private Context mContext;
+ private final Context mContext;
// Lazily created on the first mouse motion event.
private ClickScheduler mClickScheduler;
+ private ClickDelayObserver mClickDelayObserver;
public AutoclickController(Context context) {
mContext = context;
@@ -66,7 +74,9 @@
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (mClickScheduler == null) {
Handler handler = new Handler(mContext.getMainLooper());
- mClickScheduler = new ClickScheduler(handler, CLICK_DELAY_MS);
+ mClickScheduler = new ClickScheduler(handler, DEFAULT_CLICK_DELAY_MS);
+ mClickDelayObserver = new ClickDelayObserver(handler);
+ mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler);
}
handleMouseMotion(event, policyFlags);
@@ -119,8 +129,13 @@
@Override
public void onDestroy() {
+ if (mClickDelayObserver != null) {
+ mClickDelayObserver.stop();
+ mClickDelayObserver = null;
+ }
if (mClickScheduler != null) {
mClickScheduler.cancel();
+ mClickScheduler = null;
}
}
@@ -143,6 +158,80 @@
}
/**
+ * Observes setting value for autoclick delay, and updates ClickScheduler delay whenever the
+ * setting value changes.
+ */
+ final private static class ClickDelayObserver extends ContentObserver {
+ /** URI used to identify the autoclick delay setting with content resolver. */
+ private final Uri mAutoclickDelaySettingUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY);
+
+ private ContentResolver mContentResolver;
+ private ClickScheduler mClickScheduler;
+
+ public ClickDelayObserver(Handler handler) {
+ super(handler);
+ }
+
+ /**
+ * Starts the observer. And makes sure up-to-date autoclick delay is propagated to
+ * |clickScheduler|.
+ *
+ * @param contentResolver Content resolver that should be observed for setting's value
+ * changes.
+ * @param clickScheduler ClickScheduler that should be updated when click delay changes.
+ * @throws IllegalStateException If internal state is already setup when the method is
+ * called.
+ * @throws NullPointerException If any of the arguments is a null pointer.
+ */
+ public void start(@NonNull ContentResolver contentResolver,
+ @NonNull ClickScheduler clickScheduler) {
+ if (mContentResolver != null || mClickScheduler != null) {
+ throw new IllegalStateException("Observer already started.");
+ }
+ if (contentResolver == null) {
+ throw new NullPointerException("contentResolver not set.");
+ }
+ if (clickScheduler == null) {
+ throw new NullPointerException("clickScheduler not set.");
+ }
+
+ mContentResolver = contentResolver;
+ mClickScheduler = clickScheduler;
+ mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
+ UserHandle.USER_ALL);
+
+ // Initialize mClickScheduler's initial delay value.
+ onChange(true, mAutoclickDelaySettingUri);
+ }
+
+ /**
+ * Stops the the observer. Should only be called if the observer has been started.
+ *
+ * @throws IllegalStateException If internal state hasn't yet been initialized by calling
+ * {@link #start}.
+ */
+ public void stop() {
+ if (mContentResolver == null || mClickScheduler == null) {
+ throw new IllegalStateException("ClickDelayObserver not started.");
+ }
+
+ mContentResolver.unregisterContentObserver(this);
+ }
+
+ @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(
+ mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ DEFAULT_CLICK_DELAY_MS);
+ mClickScheduler.updateDelay(delay);
+ }
+ }
+ }
+
+ /**
* Schedules and performs click event sequence that should be initiated when mouse pointer stops
* moving. The click is first scheduled when a mouse movement is detected, and then further
* delayed on every sufficient mouse movement.
@@ -242,6 +331,15 @@
}
/**
+ * Updates delay that should be used when scheduling clicks. The delay will be used only for
+ * clicks scheduled after this point (pending click tasks are not affected).
+ * @param delay New delay value.
+ */
+ public void updateDelay(int delay) {
+ mDelay = delay;
+ }
+
+ /**
* Updates the time at which click sequence should occur.
*
* @param delay Delay (from now) after which click should occur.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b21e9027..372a5fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -321,6 +321,9 @@
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10*1000;
+ // How long we wait for an attached process to publish its content providers
+ // before we decide it must be hung.
+ static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real, when the process was
@@ -1378,6 +1381,7 @@
static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
static final int APP_BOOST_DEACTIVATE_MSG = 58;
+ static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1714,6 +1718,12 @@
processStartTimedOutLocked(app);
}
} break;
+ case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
+ ProcessRecord app = (ProcessRecord)msg.obj;
+ synchronized (ActivityManagerService.this) {
+ processContentProviderPublishTimedOutLocked(app);
+ }
+ } break;
case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
synchronized (ActivityManagerService.this) {
mStackSupervisor.doPendingActivityLaunchesLocked(true);
@@ -5962,6 +5972,11 @@
return needRestart;
}
+ private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
+ cleanupAppInLaunchingProvidersLocked(app, true);
+ removeProcessLocked(app, false, true, "timeout publishing content providers");
+ }
+
private final void processStartTimedOutLocked(ProcessRecord app) {
final int pid = app.pid;
boolean gone = false;
@@ -5988,7 +6003,7 @@
mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
}
// Take care of any launching providers waiting for this process.
- checkAppInLaunchingProvidersLocked(app, true);
+ cleanupAppInLaunchingProvidersLocked(app, true);
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
app.kill("start timeout", true);
@@ -6084,6 +6099,12 @@
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
+ if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
+ Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
+ msg.obj = app;
+ mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
+ }
+
if (!normalMode) {
Slog.i(TAG, "Launching preboot mode app: " + app);
}
@@ -10049,7 +10070,7 @@
final long origId = Binder.clearCallingIdentity();
final int N = providers.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
continue;
@@ -10064,15 +10085,20 @@
mProviderMap.putProviderByName(names[j], dst);
}
- int NL = mLaunchingProviders.size();
+ int launchingCount = mLaunchingProviders.size();
int j;
- for (j=0; j<NL; j++) {
+ boolean wasInLaunchingProviders = false;
+ for (j = 0; j < launchingCount; j++) {
if (mLaunchingProviders.get(j) == dst) {
mLaunchingProviders.remove(j);
+ wasInLaunchingProviders = true;
j--;
- NL--;
+ launchingCount--;
}
}
+ if (wasInLaunchingProviders) {
+ mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
+ }
synchronized (dst) {
dst.provider = src.provider;
dst.proc = r;
@@ -15743,7 +15769,7 @@
app.pubProviders.clear();
// Take care of any launching providers waiting for this process.
- if (checkAppInLaunchingProvidersLocked(app, false)) {
+ if (cleanupAppInLaunchingProvidersLocked(app, false)) {
restart = true;
}
@@ -15865,7 +15891,17 @@
return false;
}
- boolean checkAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
+ boolean checkAppInLaunchingProvidersLocked(ProcessRecord app) {
+ for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
+ ContentProviderRecord cpr = mLaunchingProviders.get(i);
+ if (cpr.launchingApp == app) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
// Look through the content providers we are waiting to have launched,
// and if any run in this process then either schedule a restart of
// the process or kill the client waiting for it if this process has
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d3cea8d..0a3391c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3061,7 +3061,12 @@
return;
}
- if (task.mBounds != null && task.mBounds.equals(bounds)) {
+ // TODO: change resizedByUser to an enum (or bitmask?) to indicate the origin of
+ // this resize (eg. systemResize, userResize, forcedResized).
+ // If the resize is a drag-resize by user, 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).
+ if (task.mBounds != null && task.mBounds.equals(bounds) && !resizedByUser) {
// Nothing to do here...
return;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 335288d..62768c3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -171,12 +171,11 @@
public void publish(Context context) {
mContext = context;
- ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
- mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
* 1000L);
mStats.setPowerProfile(new PowerProfile(context));
+ ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 6ba25a5..701b9f1 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -49,6 +49,13 @@
*/
private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode.
+ /**
+ * Used to generate globally unique color transform ids.
+ *
+ * Valid IDs start at 1 with 0 as the sentinel value for the default mode.
+ */
+ private static final AtomicInteger NEXT_COLOR_TRANSFORM_ID = new AtomicInteger(1);
+
// Called with SyncRoot lock held.
public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener, String name) {
@@ -134,6 +141,11 @@
NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate);
}
+ public static Display.ColorTransform createColorTransform(int colorTransform) {
+ return new Display.ColorTransform(
+ NEXT_COLOR_TRANSFORM_ID.getAndIncrement(), colorTransform);
+ }
+
public interface Listener {
public void onDisplayDeviceEvent(DisplayDevice device, int event);
public void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 93bda46..7af0bdb 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -135,7 +135,7 @@
/**
* Sets the mode, if supported.
*/
- public void requestModeInTransactionLocked(int id) {
+ public void requestColorTransformAndModeInTransactionLocked(int colorTransformId, int modeId) {
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 97ada15..55ba302 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -155,6 +155,15 @@
*/
public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
+ /** The active color transform of the display */
+ public int colorTransformId;
+
+ /** The default color transform of the display */
+ public int defaultColorTransformId;
+
+ /** The supported color transforms of the display */
+ public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY;
+
/**
* The nominal apparent density of the display in DPI used for layout calculations.
* This density is sensitive to the viewing distance. A big TV and a tablet may have
@@ -276,6 +285,9 @@
|| modeId != other.modeId
|| defaultModeId != other.defaultModeId
|| !Arrays.equals(supportedModes, other.supportedModes)
+ || colorTransformId != other.colorTransformId
+ || defaultColorTransformId != other.defaultColorTransformId
+ || !Arrays.equals(supportedColorTransforms, other.supportedColorTransforms)
|| densityDpi != other.densityDpi
|| xDpi != other.xDpi
|| yDpi != other.yDpi
@@ -306,6 +318,9 @@
modeId = other.modeId;
defaultModeId = other.defaultModeId;
supportedModes = other.supportedModes;
+ colorTransformId = other.colorTransformId;
+ defaultColorTransformId = other.defaultColorTransformId;
+ supportedColorTransforms = other.supportedColorTransforms;
densityDpi = other.densityDpi;
xDpi = other.xDpi;
yDpi = other.yDpi;
@@ -331,6 +346,9 @@
sb.append(", modeId ").append(modeId);
sb.append(", defaultModeId ").append(defaultModeId);
sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
+ sb.append(", colorTransformId ").append(colorTransformId);
+ sb.append(", defaultColorTransformId ").append(defaultColorTransformId);
+ sb.append(", supportedColorTransforms ").append(Arrays.toString(supportedColorTransforms));
sb.append(", density ").append(densityDpi);
sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
sb.append(", appVsyncOff ").append(appVsyncOffsetNanos);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b2ab797..6a6570b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -540,6 +540,17 @@
}
}
+ private void requestColorTransformInternal(int displayId, int colorTransformId) {
+ synchronized (mSyncRoot) {
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null &&
+ display.getRequestedColorTransformIdLocked() != colorTransformId) {
+ display.setRequestedColorTransformIdLocked(colorTransformId);
+ scheduleTraversalLocked(false);
+ }
+ }
+ }
+
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
IMediaProjection projection, int callingUid, String packageName,
String name, int width, int height, int densityDpi, Surface surface, int flags) {
@@ -1340,6 +1351,19 @@
}
@Override // Binder call
+ public void requestColorTransform(int displayId, int colorTransformId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM,
+ "Permission required to change the display color transform");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ requestColorTransformInternal(displayId, colorTransformId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public int createVirtualDisplay(IVirtualDisplayCallback callback,
IMediaProjection projection, String packageName, String name,
int width, int height, int densityDpi, Surface surface, int flags) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 517a825..be37f52 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -31,6 +31,7 @@
import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.Surface;
@@ -38,6 +39,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* A display adapter for the local displays managed by Surface Flinger.
@@ -143,14 +145,22 @@
private final int mBuiltInDisplayId;
private final Light mBacklight;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
+ private final SparseArray<Display.ColorTransform> mSupportedColorTransforms =
+ new SparseArray<>();
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
private int mState = Display.STATE_UNKNOWN;
private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private int mActivePhysIndex;
private int mDefaultModeId;
private int mActiveModeId;
private boolean mActiveModeInvalid;
+ private int mDefaultColorTransformId;
+ private int mActiveColorTransformId;
+ private boolean mActiveColorTransformInvalid;
+
+ private SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
@@ -167,22 +177,73 @@
public boolean updatePhysicalDisplayInfoLocked(
SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
- // Build an updated list of all existing modes.
- boolean modesAdded = false;
- DisplayModeRecord activeRecord = null;
- ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+ mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
+ mActivePhysIndex = activeDisplayInfo;
+ ArrayList<Display.ColorTransform> colorTransforms = new ArrayList<>();
+
+ // Build an updated list of all existing color transforms.
+ boolean colorTransformsAdded = false;
+ Display.ColorTransform activeColorTransform = null;
for (int i = 0; i < physicalDisplayInfos.length; i++) {
SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
+ // First check to see if we've already added this color transform
+ boolean existingMode = false;
+ for (int j = 0; j < colorTransforms.size(); j++) {
+ if (colorTransforms.get(j).getColorTransform() == info.colorTransform) {
+ existingMode = true;
+ break;
+ }
+ }
+ if (existingMode) {
+ continue;
+ }
+ Display.ColorTransform colorTransform = findColorTransform(info);
+ if (colorTransform == null) {
+ colorTransform = createColorTransform(info.colorTransform);
+ colorTransformsAdded = true;
+ }
+ colorTransforms.add(colorTransform);
+ if (i == activeDisplayInfo) {
+ activeColorTransform = colorTransform;
+ }
+ }
+
+ // Build an updated list of all existing modes.
+ ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+ boolean modesAdded = false;
+ for (int i = 0; i < physicalDisplayInfos.length; i++) {
+ SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
+ // First, check to see if we've already added a matching mode. Since not all
+ // configuration options are exposed via Display.Mode, it's possible that we have
+ // multiple PhysicalDisplayInfos that would generate the same Display.Mode.
+ boolean existingMode = false;
+ for (int j = 0; j < records.size(); j++) {
+ if (records.get(j).hasMatchingMode(info)) {
+ existingMode = true;
+ break;
+ }
+ }
+ if (existingMode) {
+ continue;
+ }
+ // If we haven't already added a mode for this configuration to the new set of
+ // supported modes then check to see if we have one in the prior set of supported
+ // modes to reuse.
DisplayModeRecord record = findDisplayModeRecord(info);
- if (record != null) {
- record.mPhysIndex = i;
- } else {
- record = new DisplayModeRecord(info, i);
+ if (record == null) {
+ record = new DisplayModeRecord(info);
modesAdded = true;
}
records.add(record);
- if (i == activeDisplayInfo) {
+ }
+
+ // Get the currently active mode
+ DisplayModeRecord activeRecord = null;
+ for (int i = 0; i < records.size(); i++) {
+ DisplayModeRecord record = records.get(i);
+ if (record.hasMatchingMode(physicalDisplayInfos[activeDisplayInfo])){
activeRecord = record;
+ break;
}
}
// Check whether surface flinger spontaneously changed modes out from under us. Schedule
@@ -192,25 +253,48 @@
mActiveModeInvalid = true;
sendTraversalRequestLocked();
}
- // If no modes were added and we have the same number of modes as before, then nothing
- // actually changed except possibly the physical index (which we only care about when
- // setting the mode) so we're done.
- if (records.size() == mSupportedModes.size() && !modesAdded) {
+ // Check whether surface flinger spontaneously changed color transforms out from under
+ // us.
+ if (mActiveColorTransformId != 0
+ && mActiveColorTransformId != activeColorTransform.getId()) {
+ mActiveColorTransformInvalid = true;
+ sendTraversalRequestLocked();
+ }
+
+ boolean colorTransformsChanged =
+ colorTransforms.size() != mSupportedColorTransforms.size()
+ || colorTransformsAdded;
+ boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
+ // If neither the records nor the supported color transforms have changed then we're
+ // done here.
+ if (!recordsChanged && !colorTransformsChanged) {
return false;
}
// Update the index of modes.
mHavePendingChanges = true;
+
mSupportedModes.clear();
for (DisplayModeRecord record : records) {
mSupportedModes.put(record.mMode.getModeId(), record);
}
- // Update the default mode if needed.
- if (mSupportedModes.indexOfKey(mDefaultModeId) < 0) {
+ mSupportedColorTransforms.clear();
+ for (Display.ColorTransform colorTransform : colorTransforms) {
+ mSupportedColorTransforms.put(colorTransform.getId(), colorTransform);
+ }
+
+ // Update the default mode and color transform if needed. This needs to be done in
+ // tandem so we always have a default state to fall back to.
+ if (findDisplayInfoIndexLocked(mDefaultColorTransformId, mDefaultModeId) < 0) {
if (mDefaultModeId != 0) {
- Slog.w(TAG, "Default display mode no longer available, using currently active"
- + " mode as default.");
+ Slog.w(TAG, "Default display mode no longer available, using currently"
+ + " active mode as default.");
}
mDefaultModeId = activeRecord.mMode.getModeId();
+ if (mDefaultColorTransformId != 0) {
+ Slog.w(TAG, "Default color transform no longer available, using currently"
+ + " active color transform as default");
+ }
+ mDefaultColorTransformId = activeColorTransform.getId();
}
// Determine whether the active mode is still there.
if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
@@ -221,6 +305,16 @@
mActiveModeId = mDefaultModeId;
mActiveModeInvalid = true;
}
+
+ // Determine whether the active color transform is still there.
+ if (mSupportedColorTransforms.indexOfKey(mActiveColorTransformId) < 0) {
+ if (mActiveColorTransformId != 0) {
+ Slog.w(TAG, "Active color transform no longer available, reverting"
+ + " to default transform.");
+ }
+ mActiveColorTransformId = mDefaultColorTransformId;
+ mActiveColorTransformInvalid = true;
+ }
// Schedule traversals so that we apply pending changes.
sendTraversalRequestLocked();
return true;
@@ -229,13 +323,23 @@
private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) {
for (int i = 0; i < mSupportedModes.size(); i++) {
DisplayModeRecord record = mSupportedModes.valueAt(i);
- if (record.mPhys.equals(info)) {
+ if (record.hasMatchingMode(info)) {
return record;
}
}
return null;
}
+ private Display.ColorTransform findColorTransform(SurfaceControl.PhysicalDisplayInfo info) {
+ for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+ Display.ColorTransform transform = mSupportedColorTransforms.valueAt(i);
+ if (transform.getColorTransform() == info.colorTransform) {
+ return transform;
+ }
+ }
+ return null;
+ }
+
@Override
public void applyPendingDisplayDeviceInfoChangesLocked() {
if (mHavePendingChanges) {
@@ -247,7 +351,7 @@
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
- SurfaceControl.PhysicalDisplayInfo phys = mSupportedModes.get(mActiveModeId).mPhys;
+ SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
mInfo = new DisplayDeviceInfo();
mInfo.width = phys.width;
mInfo.height = phys.height;
@@ -258,6 +362,13 @@
DisplayModeRecord record = mSupportedModes.valueAt(i);
mInfo.supportedModes[i] = record.mMode;
}
+ mInfo.colorTransformId = mActiveColorTransformId;
+ mInfo.defaultColorTransformId = mDefaultColorTransformId;
+ mInfo.supportedColorTransforms =
+ new Display.ColorTransform[mSupportedColorTransforms.size()];
+ for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+ mInfo.supportedColorTransforms[i] = mSupportedColorTransforms.valueAt(i);
+ }
mInfo.appVsyncOffsetNanos = phys.appVsyncOffsetNanos;
mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos;
mInfo.state = mState;
@@ -402,7 +513,8 @@
}
@Override
- public void requestModeInTransactionLocked(int modeId) {
+ public void requestColorTransformAndModeInTransactionLocked(
+ int colorTransformId, int modeId) {
if (modeId == 0) {
modeId = mDefaultModeId;
} else if (mSupportedModes.indexOfKey(modeId) < 0) {
@@ -410,13 +522,37 @@
+ " reverting to default display mode.");
modeId = mDefaultModeId;
}
- if (mActiveModeId == modeId && !mActiveModeInvalid) {
+
+ if (colorTransformId == 0) {
+ colorTransformId = mDefaultColorTransformId;
+ } else if (mSupportedColorTransforms.indexOfKey(colorTransformId) < 0) {
+ Slog.w(TAG, "Requested color transform " + colorTransformId + " is not supported"
+ + " by this display, reverting to the default color transform");
+ colorTransformId = mDefaultColorTransformId;
+ }
+ int physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+ if (physIndex < 0) {
+ Slog.w(TAG, "Requested color transform, mode ID pair (" + colorTransformId + ", "
+ + modeId + ") not available, trying color transform with default mode ID");
+ modeId = mDefaultModeId;
+ physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+ if (physIndex < 0) {
+ Slog.w(TAG, "Requested color transform with default mode ID still not"
+ + " available, falling back to default color transform with default"
+ + " mode.");
+ colorTransformId = mDefaultColorTransformId;
+ physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+ }
+ }
+ if (physIndex > 0 && mActivePhysIndex == physIndex) {
return;
}
- DisplayModeRecord record = mSupportedModes.get(modeId);
- SurfaceControl.setActiveConfig(getDisplayTokenLocked(), record.mPhysIndex);
+ SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
+ mActivePhysIndex = physIndex;
mActiveModeId = modeId;
mActiveModeInvalid = false;
+ mActiveColorTransformId = colorTransformId;
+ mActiveColorTransformInvalid = false;
updateDeviceInfoLocked();
}
@@ -424,10 +560,43 @@
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
+ pw.println("mActivePhysIndex=" + mActivePhysIndex);
pw.println("mActiveModeId=" + mActiveModeId);
+ pw.println("mActiveColorTransformId=" + mActiveColorTransformId);
pw.println("mState=" + Display.stateToString(mState));
pw.println("mBrightness=" + mBrightness);
pw.println("mBacklight=" + mBacklight);
+ pw.println("mDisplayInfos=");
+ for (int i = 0; i < mDisplayInfos.length; i++) {
+ pw.println(" " + mDisplayInfos[i]);
+ }
+ pw.println("mSupportedModes=");
+ for (int i = 0; i < mSupportedModes.size(); i++) {
+ pw.println(" " + mSupportedModes.valueAt(i));
+ }
+ pw.println("mSupportedColorTransforms=[");
+ for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+ if (i != 0) {
+ pw.print(", ");
+ }
+ pw.print(mSupportedColorTransforms.valueAt(i));
+ }
+ pw.println("]");
+ }
+
+ private int findDisplayInfoIndexLocked(int colorTransformId, int modeId) {
+ DisplayModeRecord record = mSupportedModes.get(modeId);
+ Display.ColorTransform transform = mSupportedColorTransforms.get(colorTransformId);
+ if (record != null && transform != null) {
+ for (int i = 0; i < mDisplayInfos.length; i++) {
+ SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
+ if (info.colorTransform == transform.getColorTransform()
+ && record.hasMatchingMode(info)){
+ return i;
+ }
+ }
+ }
+ return -1;
}
private void updateDeviceInfoLocked() {
@@ -441,13 +610,28 @@
*/
private static final class DisplayModeRecord {
public final Display.Mode mMode;
- public final SurfaceControl.PhysicalDisplayInfo mPhys;
- public int mPhysIndex;
- public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys, int physIndex) {
+ public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) {
mMode = createMode(phys.width, phys.height, phys.refreshRate);
- mPhys = phys;
- mPhysIndex = physIndex;
+ }
+
+ /**
+ * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode
+ * contained by the record modulo mode ID.
+ *
+ * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just
+ * that they generate identical modes.
+ */
+ public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) {
+ int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate());
+ int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate);
+ return mMode.getPhysicalWidth() == info.width
+ && mMode.getPhysicalHeight() == info.height
+ && modeRefreshRate == displayInfoRefreshRate;
+ }
+
+ public String toString() {
+ return "DisplayModeRecord{mMode=" + mMode + "}";
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 6efc99a..6dae397 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -74,6 +74,7 @@
private boolean mHasContent;
private int mRequestedModeId;
+ private int mRequestedColorTransformId;
// The display offsets to apply to the display projection.
private int mDisplayOffsetX;
@@ -235,6 +236,11 @@
mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
mBaseDisplayInfo.supportedModes = Arrays.copyOf(
deviceInfo.supportedModes, deviceInfo.supportedModes.length);
+ mBaseDisplayInfo.colorTransformId = deviceInfo.colorTransformId;
+ mBaseDisplayInfo.defaultColorTransformId = deviceInfo.defaultColorTransformId;
+ mBaseDisplayInfo.supportedColorTransforms = Arrays.copyOf(
+ deviceInfo.supportedColorTransforms,
+ deviceInfo.supportedColorTransforms.length);
mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
@@ -275,11 +281,12 @@
// Set the layer stack.
device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);
- // Set the mode.
+ // Set the color transform and mode.
if (device == mPrimaryDisplayDevice) {
- device.requestModeInTransactionLocked(mRequestedModeId);
+ device.requestColorTransformAndModeInTransactionLocked(
+ mRequestedColorTransformId, mRequestedModeId);
} else {
- device.requestModeInTransactionLocked(0); // Revert to default.
+ device.requestColorTransformAndModeInTransactionLocked(0, 0); // Revert to default.
}
// Only grab the display info now as it may have been changed based on the requests above.
@@ -383,6 +390,18 @@
}
/**
+ * Requests the given color transform.
+ */
+ public void setRequestedColorTransformIdLocked(int colorTransformId) {
+ mRequestedColorTransformId = colorTransformId;
+ }
+
+ /** Returns the pending requested color transform. */
+ public int getRequestedColorTransformIdLocked() {
+ return mRequestedColorTransformId;
+ }
+
+ /**
* Gets the burn-in offset in X.
*/
public int getDisplayOffsetXLocked() {
@@ -409,6 +428,7 @@
pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent);
pw.println("mRequestedMode=" + mRequestedModeId);
+ pw.println("mRequestedColorTransformId=" + mRequestedColorTransformId);
pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
mPrimaryDisplayDevice.getNameLocked() : "null"));
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0bddff0..cf6264a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -310,7 +310,7 @@
}
@Override
- public void requestModeInTransactionLocked(int id) {
+ public void requestColorTransformAndModeInTransactionLocked(int color, int id) {
int index = -1;
if (id == 0) {
// Use the default.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b7a587b..ac809f9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12380,6 +12380,7 @@
// Retrieve PackageSettings and parse package
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+ | PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
PackageParser pp = new PackageParser();
@@ -15901,14 +15902,28 @@
}
}
- private void loadPrivatePackages(VolumeInfo vol) {
+ private void loadPrivatePackages(final VolumeInfo vol) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ loadPrivatePackagesInner(vol);
+ }
+ });
+ }
+
+ private void loadPrivatePackagesInner(VolumeInfo vol) {
final ArrayList<ApplicationInfo> loaded = new ArrayList<>();
final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
- synchronized (mInstallLock) {
+
+ final VersionInfo ver;
+ final List<PackageSetting> packages;
synchronized (mPackages) {
- final VersionInfo ver = mSettings.findOrCreateVersion(vol.fsUuid);
- final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
- for (PackageSetting ps : packages) {
+ ver = mSettings.findOrCreateVersion(vol.fsUuid);
+ packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+ }
+
+ for (PackageSetting ps : packages) {
+ synchronized (mInstallLock) {
final PackageParser.Package pkg;
try {
pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0L, null);
@@ -15921,7 +15936,9 @@
deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
}
}
+ }
+ synchronized (mPackages) {
int updateFlags = UPDATE_PERMISSIONS_ALL;
if (ver.sdkVersion != mSdkVersion) {
logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
@@ -15935,13 +15952,21 @@
mSettings.writeLPr();
}
- }
if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
sendResourcesChangedBroadcast(true, false, loaded, null);
}
- private void unloadPrivatePackages(VolumeInfo vol) {
+ private void unloadPrivatePackages(final VolumeInfo vol) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ unloadPrivatePackagesInner(vol);
+ }
+ });
+ }
+
+ private void unloadPrivatePackagesInner(VolumeInfo vol) {
final ArrayList<ApplicationInfo> unloaded = new ArrayList<>();
synchronized (mInstallLock) {
synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ff58757..04d382d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1345,7 +1345,6 @@
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
// Init display burn-in protection
boolean burnInProtectionEnabled = context.getResources().getBoolean(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cc9efdb..58f0448 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -72,6 +72,12 @@
// For handling display rotations.
private Rect mTmpRect2 = new Rect();
+ // Whether the task is currently being drag-resized
+ private boolean mDragResizing;
+
+ // Whether the task is starting or ending to be drag-resized
+ private boolean mDragResizeChanging;
+
// The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer.
WindowStateAnimator mDimWinAnimator;
// Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
@@ -227,10 +233,34 @@
return boundsChange;
}
+ boolean resizeLocked(Rect bounds, Configuration configuration) {
+ int boundsChanged = setBounds(bounds, configuration);
+ if (mDragResizeChanging) {
+ boundsChanged |= BOUNDS_CHANGE_SIZE;
+ mDragResizeChanging = false;
+ }
+ if (boundsChanged == BOUNDS_CHANGE_NONE) {
+ return false;
+ }
+ if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+ resizeWindows();
+ }
+ return true;
+ }
+
void getBounds(Rect out) {
out.set(mBounds);
}
+ void setDragResizing(boolean dragResizing) {
+ mDragResizeChanging = mDragResizing != dragResizing;
+ mDragResizing = dragResizing;
+ }
+
+ boolean isDragResizing() {
+ return mDragResizing;
+ }
+
void updateDisplayInfo(final DisplayContent displayContent) {
if (displayContent == null) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 17b56ba..5487349 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -86,8 +86,7 @@
private int mMinVisibleWidth;
private int mMinVisibleHeight;
- private int mTaskId;
- private TaskStack mStack;
+ private Task mTask;
private boolean mResizing;
private final Rect mWindowOriginalBounds = new Rect();
private final Rect mWindowDragBounds = new Rect();
@@ -136,7 +135,7 @@
}
try {
mService.mActivityManager.resizeTask(
- mTaskId, mWindowDragBounds, true /* resizedByUser */);
+ mTask.mTaskId, mWindowDragBounds, true /* resizedByUser */);
} catch(RemoteException e) {}
} break;
@@ -156,21 +155,29 @@
}
if (endDrag) {
- mResizing = false;
+ synchronized (mService.mWindowMap) {
+ endDragLocked();
+ }
try {
- mService.mActivityManager.resizeTask(
- mTaskId, mWindowDragBounds, true /* resizedByUser */);
+ if (mResizing) {
+ // We were using fullscreen surface during resizing. Request
+ // resizeTask() one last time to restore surface to window size.
+ mService.mActivityManager.resizeTask(
+ mTask.mTaskId, mWindowDragBounds, true /* resizedByUser */);
+ }
+
+ if (mCurrentDimSide != CTRL_NONE) {
+ final int createMode = mCurrentDimSide == CTRL_LEFT
+ ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+ : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ mService.mActivityManager.moveTaskToDockedStack(
+ mTask.mTaskId, createMode, true /*toTop*/);
+ }
} catch(RemoteException e) {}
+
// Post back to WM to handle clean-ups. We still need the input
// event handler for the last finishInputEvent()!
mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
- if (mCurrentDimSide != CTRL_NONE) {
- final int createMode = mCurrentDimSide == CTRL_LEFT
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
- mService.mActivityManager.moveTaskToDockedStack(
- mTaskId, createMode, true /*toTop*/);
- }
}
handled = true;
} catch (Exception e) {
@@ -291,10 +298,6 @@
mService.resumeRotationLocked();
}
- boolean isTaskResizing(final Task task) {
- return mResizing && task != null && mTaskId == task.mTaskId;
- }
-
void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
if (DEBUG_TASK_POSITIONING) {
Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
@@ -318,13 +321,16 @@
mResizing = true;
}
- final Task task = win.getTask();
- mTaskId = task.mTaskId;
- mStack = task.mStack;
+ mTask = win.getTask();
mStartDragX = startX;
mStartDragY = startY;
- mService.getTaskBounds(mTaskId, mWindowOriginalBounds);
+ mService.getTaskBounds(mTask.mTaskId, mWindowOriginalBounds);
+ }
+
+ private void endDragLocked() {
+ mResizing = false;
+ mTask.setDragResizing(false);
}
/** Returns true if the move operation should be ended. */
@@ -354,11 +360,12 @@
bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
}
mWindowDragBounds.set(left, top, right, bottom);
+ mTask.setDragResizing(true);
return false;
}
// This is a moving operation.
- mStack.getBounds(mTmpRect);
+ mTask.mStack.getBounds(mTmpRect);
mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight);
if (!mTmpRect.contains((int) x, (int) y)) {
// We end the moving operation if position is outside the stack bounds.
@@ -397,13 +404,13 @@
* shouldn't be shown.
*/
private int getDimSide(int x) {
- if (mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
- || !mStack.isFullscreen()
+ if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+ || !mTask.mStack.isFullscreen()
|| mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
}
- mStack.getBounds(mTmpRect);
+ mTask.mStack.getBounds(mTmpRect);
if (x - mSideMargin <= mTmpRect.left) {
return CTRL_LEFT;
}
@@ -415,7 +422,7 @@
}
private void showDimLayer() {
- mStack.getBounds(mTmpRect);
+ mTask.mStack.getBounds(mTmpRect);
if (mCurrentDimSide == CTRL_LEFT) {
mTmpRect.right = mTmpRect.centerX();
} else if (mCurrentDimSide == CTRL_RIGHT) {
@@ -433,7 +440,7 @@
@Override /** {@link DimLayer.DimLayerUser} */
public DisplayInfo getDisplayInfo() {
- return mStack.getDisplayInfo();
+ return mTask.mStack.getDisplayInfo();
}
private int getDragLayerLocked() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 1e6fab6..571540f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -20,6 +20,7 @@
import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerService.TAG;
+import android.annotation.IntDef;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
@@ -32,6 +33,8 @@
import com.android.server.EventLogTags;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
public class TaskStack implements DimLayer.DimLayerUser {
@@ -73,6 +76,20 @@
/** Detach this stack from its display when animation completes. */
boolean mDeferDetach;
+ static final int DOCKED_INVALID = -1;
+ static final int DOCKED_LEFT = 1;
+ static final int DOCKED_TOP = 2;
+ static final int DOCKED_RIGHT = 3;
+ static final int DOCKED_BOTTOM = 4;
+ @IntDef({
+ DOCKED_INVALID,
+ DOCKED_LEFT,
+ DOCKED_TOP,
+ DOCKED_RIGHT,
+ DOCKED_BOTTOM})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DockSide {}
+
TaskStack(WindowManagerService service, int stackId) {
mService = service;
mStackId = stackId;
@@ -508,4 +525,36 @@
public String toString() {
return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
}
+
+ /**
+ * For docked workspace provides information which side of the screen was the dock anchored.
+ */
+ @DockSide
+ int getDockSide() {
+ if (mStackId != DOCKED_STACK_ID) {
+ return DOCKED_INVALID;
+ }
+ if (mDisplayContent == null) {
+ return DOCKED_INVALID;
+ }
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ final int orientation = mService.mCurConfiguration.orientation;
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // Portrait mode, docked either at the top or the bottom.
+ if (mTmpRect.top - mBounds.top < mTmpRect.bottom - mBounds.bottom) {
+ return DOCKED_TOP;
+ } else {
+ return DOCKED_BOTTOM;
+ }
+ } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ // Landscape mode, docked either on the left or on the right.
+ if (mTmpRect.left - mBounds.left < mTmpRect.right - mBounds.right) {
+ return DOCKED_LEFT;
+ } else {
+ return DOCKED_RIGHT;
+ }
+ } else {
+ return DOCKED_INVALID;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5bf296e..4e4e57b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2589,9 +2589,13 @@
}
}
- dragResizing = win.isDragResizing();
- if (win.mDragResizing != dragResizing) {
- win.mDragResizing = dragResizing;
+ // If we're starting a drag-resize, we'll be changing the surface size as well as
+ // notifying the client to render to with an offset from the surface's top-left.
+ // Do a screen freeze, and keep the old surface until the the first frame drawn to
+ // the new surface comes back, so that we avoid a flash due to mismatching surface
+ // setups on window manager side and client side.
+ if (win.isDragResizeChanged()) {
+ win.setDragResizing();
if (win.mHasSurface) {
winAnimator.mDestroyPendingSurfaceUponRedraw = true;
winAnimator.mSurfaceDestroyDeferred = true;
@@ -2600,6 +2604,7 @@
toBeDisplayed = true;
}
}
+ dragResizing = win.isDragResizing();
try {
if (!win.mHasSurface) {
surfaceChanged = true;
@@ -3670,12 +3675,8 @@
// pretend like we didn't see that.
return;
}
- final boolean windowIsTranslucentDefined = ent.array.hasValue(
- com.android.internal.R.styleable.Window_windowIsTranslucent);
final boolean windowIsTranslucent = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- final boolean windowSwipeToDismiss = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
final boolean windowIsFloating = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false);
final boolean windowShowWallpaper = ent.array.getBoolean(
@@ -3685,7 +3686,7 @@
if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Translucent=" + windowIsTranslucent
+ " Floating=" + windowIsFloating
+ " ShowWallpaper=" + windowShowWallpaper);
- if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) {
+ if (windowIsTranslucent) {
return;
}
if (windowIsFloating || windowDisableStarting) {
@@ -4688,16 +4689,10 @@
throw new IllegalArgumentException("resizeTask: taskId " + taskId
+ " not found.");
}
- final int boundsChanged = task.setBounds(bounds, configuration);
- if (boundsChanged != Task.BOUNDS_CHANGE_NONE) {
- if ((boundsChanged & Task.BOUNDS_CHANGE_SIZE) == Task.BOUNDS_CHANGE_SIZE) {
- task.resizeWindows();
- }
- if (relayout) {
- task.getDisplayContent().layoutNeeded = true;
- mWindowPlacerLocked.performSurfacePlacement();
- }
+ if (task.resizeLocked(bounds, configuration) && relayout) {
+ task.getDisplayContent().layoutNeeded = true;
+ mWindowPlacerLocked.performSurfacePlacement();
}
}
}
@@ -8480,7 +8475,7 @@
Slog.v(TAG, "Win " + w + " config changed: "
+ mCurConfiguration);
}
- final boolean dragResizingChanged = w.mDragResizing != w.isDragResizing();
+ final boolean dragResizingChanged = w.isDragResizeChanged();
if (localLOGV) Slog.v(TAG, "Resizing " + w
+ ": configChanged=" + configChanged
+ " dragResizingChanged=" + dragResizingChanged
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fa28eba..a6478a0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -129,6 +128,7 @@
boolean mAttachedHidden; // is our parent window hidden?
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
boolean mDragResizing;
+ boolean mDragResizeChanging;
RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -382,6 +382,8 @@
*/
PowerManager.WakeLock mDrawLock;
+ final private Rect mTmpRect = new Rect();
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
@@ -937,24 +939,33 @@
* bounds will be returned.
*/
void getVisibleBounds(Rect bounds, boolean forTouch) {
- final boolean useStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
+ boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
boolean isFreeform = false;
bounds.setEmpty();
- if (useStackBounds) {
+ mTmpRect.setEmpty();
+ if (intersectWithStackBounds) {
final TaskStack stack = getStack();
if (stack != null) {
- stack.getBounds(bounds);
- isFreeform = stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
- }
- } else {
- final Task task = getTask();
- if (task != null) {
- task.getBounds(bounds);
- isFreeform = task.inFreeformWorkspace();
+ stack.getBounds(mTmpRect);
+ } else {
+ intersectWithStackBounds = false;
}
}
+
+ final Task task = getTask();
+ if (task != null) {
+ task.getBounds(bounds);
+ isFreeform = task.inFreeformWorkspace();
+ if (intersectWithStackBounds) {
+ bounds.intersect(mTmpRect);
+ }
+ }
+
if (bounds.isEmpty()) {
bounds.set(mFrame);
+ if (intersectWithStackBounds) {
+ bounds.intersect(mTmpRect);
+ }
return;
}
if (forTouch && isFreeform) {
@@ -1691,9 +1702,18 @@
return task != null && task.inFreeformWorkspace();
}
- boolean isDragResizing() {
+ boolean isDragResizeChanged() {
final Task task = getTask();
- return mService.mTaskPositioner != null && mService.mTaskPositioner.isTaskResizing(task);
+ return task != null && mDragResizing != task.isDragResizing();
+ }
+
+ void setDragResizing() {
+ final Task task = getTask();
+ mDragResizing = task != null && task.isDragResizing();
+ }
+
+ boolean isDragResizing() {
+ return mDragResizing;
}
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e6fef2f..07e1fce 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -818,7 +818,7 @@
// so that we don't need to reallocate during the process. This also prevents
// buffer drops due to size mismatch.
final DisplayInfo displayInfo = w.getDisplayInfo();
- if (displayInfo != null && w.mDragResizing) {
+ if (displayInfo != null && w.isDragResizing()) {
left = 0;
top = 0;
width = displayInfo.logicalWidth;
@@ -1211,6 +1211,13 @@
return;
} else if (mIsWallpaper && mService.mWindowPlacerLocked.mWallpaperActionPending) {
return;
+ } else if (mWin.isDragResizeChanged()) {
+ // This window is awaiting a relayout because user just started (or ended)
+ // drag-resizing. The shown frame (which affects surface size and pos)
+ // should not be updated until we get next finished draw with the new surface.
+ // Otherwise one or two frames rendered with old settings would be displayed
+ // with new geometry.
+ return;
}
if (WindowManagerService.localLOGV) Slog.v(
@@ -1333,7 +1340,7 @@
final boolean fullscreen = w.isFullscreen(displayInfo.appWidth, displayInfo.appHeight);
final Rect clipRect = mTmpClipRect;
- if (w.mDragResizing) {
+ if (w.isDragResizing()) {
// When we're doing a drag-resizing, the surface is set up to cover full screen.
// Set the clip rect to be the same size so that we don't get any scaling.
clipRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
@@ -1423,7 +1430,7 @@
// so that we don't need to reallocate during the process. This also prevents
// buffer drops due to size mismatch.
final DisplayInfo displayInfo = w.getDisplayInfo();
- if (displayInfo != null && w.mDragResizing) {
+ if (displayInfo != null && w.isDragResizing()) {
left = 0;
top = 0;
width = displayInfo.logicalWidth;
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 0c3f9da..7f813ec 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -419,10 +419,9 @@
private boolean setUsbConfig(String config) {
if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
// set the new configuration
- String oldConfig = SystemProperties.get(USB_CONFIG_PROPERTY);
- if (!config.equals(oldConfig)) {
- SystemProperties.set(USB_CONFIG_PROPERTY, config);
- }
+ // we always set it due to b/23631400, where adbd was getting killed
+ // and not restarted due to property timeouts on some devices
+ SystemProperties.set(USB_CONFIG_PROPERTY, config);
return waitForState(config);
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 10cf5c1..b028ce6 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -944,5 +944,14 @@
</intent-filter>
</activity>
+ <activity
+ android:name="MultiProducerActivity"
+ android:label="Threads/Multiple Producers">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
new file mode 100644
index 0000000..b458d9b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
@@ -0,0 +1,347 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.DisplayListCanvas;
+import android.view.HardwareRenderer;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AbsoluteLayout.LayoutParams;
+
+public class MultiProducerActivity extends Activity implements OnClickListener {
+ private static final int DURATION = 800;
+ private View mBackgroundTarget = null;
+ private View mFrameTarget = null;
+ private View mContent = null;
+ // The width & height of our "output drawing".
+ private final int WIDTH = 900;
+ private final int HEIGHT = 600;
+ // A border width around the drawing.
+ private static final int BORDER_WIDTH = 20;
+ // The Gap between the content and the frame which should get filled on the right and bottom
+ // side by the backdrop.
+ final int CONTENT_GAP = 100;
+
+ // For debug purposes - disable drawing of frame / background.
+ private final boolean USE_FRAME = true;
+ private final boolean USE_BACK = true;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // To make things simple - we do a quick and dirty absolute layout.
+ final AbsoluteLayout layout = new AbsoluteLayout(this);
+
+ // Create the outer frame
+ if (USE_FRAME) {
+ mFrameTarget = new View(this);
+ LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0);
+ layout.addView(mFrameTarget, frameLP);
+ }
+
+ // Create the background which fills the gap between content and frame.
+ if (USE_BACK) {
+ mBackgroundTarget = new View(this);
+ LayoutParams backgroundLP = new LayoutParams(
+ WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH,
+ BORDER_WIDTH, BORDER_WIDTH);
+ layout.addView(mBackgroundTarget, backgroundLP);
+ }
+
+ // Create the content
+ // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get
+ // drawn by the backdrop.
+ mContent = new View(this);
+ mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null));
+ mContent.setOnClickListener(this);
+ LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP,
+ HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH);
+ layout.addView(mContent, contentLP);
+
+ setContentView(layout);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+ if (view != null) {
+ view.post(mSetup);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+ if (view != null) {
+ view.removeCallbacks(mSetup);
+ }
+ if (mBgRenderer != null) {
+ mBgRenderer.destroy();
+ mBgRenderer = null;
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ sBlockThread.run();
+ }
+
+ private Runnable mSetup = new Runnable() {
+ @Override
+ public void run() {
+ View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+ if (view == null) {
+ view.postDelayed(mSetup, 50);
+ }
+ HardwareRenderer renderer = view.getHardwareRenderer();
+ if (renderer == null || view.getWidth() == 0) {
+ view.postDelayed(mSetup, 50);
+ }
+ ThreadedRenderer threaded = (ThreadedRenderer) renderer;
+
+ mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget);
+ mBgRenderer.start();
+ }
+ };
+
+ private FakeFrame mBgRenderer;
+ private class FakeFrame extends Thread {
+ ThreadedRenderer mRenderer;
+ volatile boolean mRunning = true;
+ View mTargetFrame;
+ View mTargetBack;
+ Drawable mFrameContent;
+ Drawable mBackContent;
+ // The Z value where to place this.
+ int mZFrame;
+ int mZBack;
+ String mRenderNodeName;
+
+ FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) {
+ mRenderer = renderer;
+ mTargetFrame = targetFrame;
+
+ mTargetBack = targetBack;
+ mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT));
+ mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null);
+ }
+
+ @Override
+ public void run() {
+ Rect currentFrameBounds = new Rect();
+ Rect currentBackBounds = new Rect();
+ Rect newBounds = new Rect();
+ int[] surfaceOrigin = new int[2];
+ RenderNode nodeFrame = null;
+ RenderNode nodeBack = null;
+
+ // Since we are overriding the window painting logic we need to at least fill the
+ // surface with some window content (otherwise the world will go black).
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ }
+
+ if (mTargetBack != null) {
+ nodeBack = RenderNode.create("FakeBackdrop", null);
+ nodeBack.setClipToBounds(true);
+ mRenderer.addRenderNode(nodeBack, true);
+ }
+
+ if (mTargetFrame != null) {
+ nodeFrame = RenderNode.create("FakeFrame", null);
+ nodeFrame.setClipToBounds(true);
+ mRenderer.addRenderNode(nodeFrame, false);
+ }
+
+ while (mRunning) {
+ // Get the surface position to draw to within our surface.
+ surfaceOrigin[0] = 0;
+ surfaceOrigin[1] = 0;
+ // 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],
+ surfaceOrigin[0] + mContent.getWidth(),
+ surfaceOrigin[1] + mContent.getHeight());
+ // Determine new position for frame.
+ if (nodeFrame != null) {
+ surfaceOrigin[0] = 0;
+ surfaceOrigin[1] = 0;
+ mTargetFrame.getLocationInSurface(surfaceOrigin);
+ newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+ surfaceOrigin[0] + mTargetFrame.getWidth(),
+ surfaceOrigin[1] + mTargetFrame.getHeight());
+ if (!currentFrameBounds.equals(newBounds)) {
+ currentFrameBounds.set(newBounds);
+ nodeFrame.setLeftTopRightBottom(currentFrameBounds.left,
+ currentFrameBounds.top,
+ currentFrameBounds.right, currentFrameBounds.bottom);
+ }
+
+ // Draw frame
+ DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(),
+ currentFrameBounds.height());
+ mFrameContent.draw(canvas);
+ nodeFrame.end(canvas);
+ }
+
+ // Determine new position for backdrop
+ if (nodeBack != null) {
+ surfaceOrigin[0] = 0;
+ surfaceOrigin[1] = 0;
+ mTargetBack.getLocationInSurface(surfaceOrigin);
+ newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+ surfaceOrigin[0] + mTargetBack.getWidth(),
+ surfaceOrigin[1] + mTargetBack.getHeight());
+ if (!currentBackBounds.equals(newBounds)) {
+ currentBackBounds.set(newBounds);
+ nodeBack.setLeftTopRightBottom(currentBackBounds.left,
+ currentBackBounds.top,
+ currentBackBounds.right, currentBackBounds.bottom);
+ }
+
+ // Draw Backdrop
+ DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(),
+ currentBackBounds.height());
+ mBackContent.draw(canvas);
+ nodeBack.end(canvas);
+ }
+
+ // we need to only render one guy - the rest will happen automatically (I think).
+ if (nodeFrame != null) {
+ mRenderer.drawRenderNode(nodeFrame);
+ }
+ if (nodeBack != null) {
+ mRenderer.drawRenderNode(nodeBack);
+ }
+ try {
+ Thread.sleep(5);
+ } catch (InterruptedException e) {}
+ }
+ if (nodeFrame != null) {
+ mRenderer.removeRenderNode(nodeFrame);
+ }
+ if (nodeBack != null) {
+ mRenderer.removeRenderNode(nodeBack);
+ }
+ }
+
+ public void destroy() {
+ mRunning = false;
+ try {
+ join();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ private final static Runnable sBlockThread = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(DURATION);
+ } catch (InterruptedException e) {
+ }
+ }
+ };
+
+ static class ColorPulse extends Drawable {
+
+ private int mColorStart;
+ private int mColorEnd;
+ private int mStep;
+ private Rect mRect;
+ private Paint mPaint = new Paint();
+
+ public ColorPulse(int color1, int color2, Rect rect) {
+ mColorStart = color1;
+ mColorEnd = color2;
+ if (rect != null) {
+ mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2,
+ rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2);
+ }
+ }
+
+ static int evaluate(float fraction, int startInt, int endInt) {
+ int startA = (startInt >> 24) & 0xff;
+ int startR = (startInt >> 16) & 0xff;
+ int startG = (startInt >> 8) & 0xff;
+ int startB = startInt & 0xff;
+
+ int endA = (endInt >> 24) & 0xff;
+ int endR = (endInt >> 16) & 0xff;
+ int endG = (endInt >> 8) & 0xff;
+ int endB = endInt & 0xff;
+
+ return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+ (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+ (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+ (int)((startB + (int)(fraction * (endB - startB))));
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ float frac = mStep / 50.0f;
+ int color = evaluate(frac, mColorStart, mColorEnd);
+ if (mRect != null && !mRect.isEmpty()) {
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(BORDER_WIDTH);
+ mPaint.setColor(color);
+ canvas.drawRect(mRect, mPaint);
+ } else {
+ canvas.drawColor(color);
+ }
+
+ mStep++;
+ if (mStep >= 50) {
+ mStep = 0;
+ int tmp = mColorStart;
+ mColorStart = mColorEnd;
+ mColorEnd = tmp;
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+ }
+
+ }
+}
+