Merge "Make the image wallpaper component overlayable" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 8e10ede..93b0995 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8686,7 +8686,7 @@
method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions();
method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions();
method public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
- method public android.content.pm.PackageInstaller.Session openSession(int);
+ method public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException;
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
method public void uninstall(java.lang.String, android.content.IntentSender);
@@ -8719,16 +8719,15 @@
method public java.lang.String[] getNames() throws java.io.IOException;
method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
- method public void setProgress(float);
+ method public void setStagingProgress(float);
}
public static abstract class PackageInstaller.SessionCallback {
ctor public PackageInstaller.SessionCallback();
+ method public abstract void onActiveChanged(int, boolean);
method public abstract void onBadgingChanged(int);
- method public abstract void onClosed(int);
method public abstract void onCreated(int);
method public abstract void onFinished(int, boolean);
- method public abstract void onOpened(int);
method public abstract void onProgressChanged(int, float);
}
@@ -8741,7 +8740,7 @@
method public java.lang.String getInstallerPackageName();
method public float getProgress();
method public int getSessionId();
- method public boolean isOpen();
+ method public boolean isActive();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -14051,6 +14050,7 @@
field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final android.os.Parcelable.Creator CREATOR;
field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
@@ -14236,9 +14236,9 @@
field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
field public static final int ERROR = -1; // 0xffffffff
field public static final int ERROR_DEAD_OBJECT = -6; // 0xfffffffa
- field public static final java.lang.String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.audio_plug_state";
- field public static final java.lang.String EXTRA_ENCODINGS = "android.media.extra.encodings";
- field public static final java.lang.String EXTRA_MAX_CHANNEL_COUNT = "android.media.extra.max_channel_count";
+ field public static final java.lang.String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.AUDIO_PLUG_STATE";
+ field public static final java.lang.String EXTRA_ENCODINGS = "android.media.extra.ENCODINGS";
+ field public static final java.lang.String EXTRA_MAX_CHANNEL_COUNT = "android.media.extra.MAX_CHANNEL_COUNT";
field public static final java.lang.String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
field public static final java.lang.String EXTRA_SCO_AUDIO_PREVIOUS_STATE = "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
field public static final java.lang.String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
@@ -14710,7 +14710,7 @@
public static final class MediaCodecInfo.CodecCapabilities {
ctor public MediaCodecInfo.CodecCapabilities();
- method public static android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
+ method public static android.media.MediaCodecInfo.CodecCapabilities createFromProfileLevel(java.lang.String, int, int);
method public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities();
method public android.media.MediaFormat getDefaultFormat();
method public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities();
@@ -28231,17 +28231,19 @@
public abstract class Conference {
ctor public Conference(android.telecomm.PhoneAccountHandle);
- method public boolean addConnection(android.telecomm.Connection);
- method public void destroy();
+ method public final boolean addConnection(android.telecomm.Connection);
+ method public final void destroy();
method public final int getCapabilities();
method public final java.util.List<android.telecomm.Connection> getConnections();
method public final android.telecomm.PhoneAccountHandle getPhoneAccount();
method public final int getState();
method public void onDisconnect();
method public void onHold();
+ method public void onMerge();
method public void onSeparate(android.telecomm.Connection);
+ method public void onSwap();
method public void onUnhold();
- method public void removeConnection(android.telecomm.Connection);
+ method public final void removeConnection(android.telecomm.Connection);
method public final void setActive();
method public final void setCapabilities(int);
method public final void setDisconnected(int, java.lang.String);
@@ -28392,13 +28394,13 @@
method public static java.lang.String toString(int);
field public static final int ADD_CALL = 16; // 0x10
field public static final int ALL = 255; // 0xff
- field public static final int GENERIC_CONFERENCE = 128; // 0x80
field public static final int HOLD = 1; // 0x1
- field public static final int MERGE_CALLS = 4; // 0x4
+ field public static final int MANAGE_CONFERENCE = 128; // 0x80
+ field public static final int MERGE_CONFERENCE = 4; // 0x4
field public static final int MUTE = 64; // 0x40
field public static final int RESPOND_VIA_TEXT = 32; // 0x20
field public static final int SUPPORT_HOLD = 2; // 0x2
- field public static final int SWAP_CALLS = 8; // 0x8
+ field public static final int SWAP_CONFERENCE = 8; // 0x8
}
public class PropertyPresentation {
@@ -29007,7 +29009,6 @@
method public void enableSimplifiedNetworkSettings(boolean);
method public void enableSimplifiedNetworkSettings(long, boolean);
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
- method public int getCalculatedPreferredNetworkType();
method public int getCallState();
method public android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
@@ -29024,7 +29025,6 @@
method public java.lang.String getNetworkOperatorName();
method public int getNetworkType();
method public int getPhoneType();
- method public int getPreferredNetworkType();
method public java.lang.String getSimCountryIso();
method public java.lang.String getSimOperator();
method public java.lang.String getSimOperatorName();
@@ -29046,11 +29046,10 @@
method public boolean isSmsCapable();
method public void listen(android.telephony.PhoneStateListener, int);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
- method public boolean setCdmaSubscription(int);
+ method public boolean setGlobalPreferredNetworkType();
method public void setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public void setLine1NumberForDisplay(long, java.lang.String, java.lang.String);
- method public boolean setOperatorBrandOverride(java.lang.String, java.lang.String);
- method public boolean setPreferredNetworkType(int);
+ method public boolean setOperatorBrandOverride(java.lang.String);
field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
field public static final int CALL_STATE_IDLE = 0; // 0x0
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 9156eeb..7c13dbe 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -504,6 +504,10 @@
mStarted = true;
mPaused = false;
+ for (Node node : mNodes) {
+ node.animation.setAllowRunningAsynchronously(false);
+ }
+
if (mDuration >= 0) {
// If the duration was set on this AnimatorSet, pass it along to all child animations
for (Node node : mNodes) {
diff --git a/core/java/android/content/pm/IPackageInstallerCallback.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl
index fe98ee7..974eb1e 100644
--- a/core/java/android/content/pm/IPackageInstallerCallback.aidl
+++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl
@@ -20,8 +20,7 @@
oneway interface IPackageInstallerCallback {
void onSessionCreated(int sessionId);
void onSessionBadgingChanged(int sessionId);
- void onSessionOpened(int sessionId);
+ void onSessionActiveChanged(int sessionId, boolean active);
void onSessionProgressChanged(int sessionId, float progress);
- void onSessionClosed(int sessionId);
void onSessionFinished(int sessionId, boolean success);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 7c34a65..0a211cf 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -304,9 +304,12 @@
* Open an existing session to actively perform work. To succeed, the caller
* must be the owner of the install session.
*/
- public @NonNull Session openSession(int sessionId) {
+ public @NonNull Session openSession(int sessionId) throws IOException {
try {
return new Session(mInstaller.openSession(sessionId));
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -363,7 +366,7 @@
public @NonNull List<SessionInfo> getAllSessions() {
final ApplicationInfo info = mContext.getApplicationInfo();
if ("com.google.android.googlequicksearchbox".equals(info.packageName)
- && info.versionCode <= 300400070) {
+ && info.versionCode <= 300400110) {
Log.d(TAG, "Ignoring callback request from old prebuilt");
return Collections.EMPTY_LIST;
}
@@ -436,27 +439,32 @@
public abstract void onBadgingChanged(int sessionId);
/**
- * Session has been opened. A session is usually opened when the
- * installer is actively writing data.
+ * Active state for session has been changed.
+ * <p>
+ * A session is considered active whenever there is ongoing forward
+ * progress being made, such as the installer holding an open
+ * {@link Session} instance while streaming data into place, or the
+ * system optimizing code as the result of
+ * {@link Session#commit(IntentSender)}.
+ * <p>
+ * If the installer closes the {@link Session} without committing, the
+ * session is considered inactive until the installer opens the session
+ * again.
*/
- public abstract void onOpened(int sessionId);
+ public abstract void onActiveChanged(int sessionId, boolean active);
/**
* Progress for given session has been updated.
* <p>
* Note that this progress may not directly correspond to the value
- * reported by {@link PackageInstaller.Session#setProgress(float)}, as
- * the system may carve out a portion of the overall progress to
- * represent its own internal installation work.
+ * reported by
+ * {@link PackageInstaller.Session#setStagingProgress(float)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
*/
public abstract void onProgressChanged(int sessionId, float progress);
/**
- * Session has been closed.
- */
- public abstract void onClosed(int sessionId);
-
- /**
* Session has completely finished, either with success or failure.
*/
public abstract void onFinished(int sessionId, boolean success);
@@ -467,10 +475,9 @@
Handler.Callback {
private static final int MSG_SESSION_CREATED = 1;
private static final int MSG_SESSION_BADGING_CHANGED = 2;
- private static final int MSG_SESSION_OPENED = 3;
+ private static final int MSG_SESSION_ACTIVE_CHANGED = 3;
private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
- private static final int MSG_SESSION_CLOSED = 5;
- private static final int MSG_SESSION_FINISHED = 6;
+ private static final int MSG_SESSION_FINISHED = 5;
final SessionCallback mCallback;
final Handler mHandler;
@@ -482,24 +489,23 @@
@Override
public boolean handleMessage(Message msg) {
+ final int sessionId = msg.arg1;
switch (msg.what) {
case MSG_SESSION_CREATED:
- mCallback.onCreated(msg.arg1);
+ mCallback.onCreated(sessionId);
return true;
case MSG_SESSION_BADGING_CHANGED:
- mCallback.onBadgingChanged(msg.arg1);
+ mCallback.onBadgingChanged(sessionId);
return true;
- case MSG_SESSION_OPENED:
- mCallback.onOpened(msg.arg1);
+ case MSG_SESSION_ACTIVE_CHANGED:
+ final boolean active = msg.arg2 != 0;
+ mCallback.onActiveChanged(sessionId, active);
return true;
case MSG_SESSION_PROGRESS_CHANGED:
- mCallback.onProgressChanged(msg.arg1, (float) msg.obj);
- return true;
- case MSG_SESSION_CLOSED:
- mCallback.onClosed(msg.arg1);
+ mCallback.onProgressChanged(sessionId, (float) msg.obj);
return true;
case MSG_SESSION_FINISHED:
- mCallback.onFinished(msg.arg1, msg.arg2 != 0);
+ mCallback.onFinished(sessionId, msg.arg2 != 0);
return true;
}
return false;
@@ -516,8 +522,9 @@
}
@Override
- public void onSessionOpened(int sessionId) {
- mHandler.obtainMessage(MSG_SESSION_OPENED, sessionId, 0).sendToTarget();
+ public void onSessionActiveChanged(int sessionId, boolean active) {
+ mHandler.obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, active ? 1 : 0)
+ .sendToTarget();
}
@Override
@@ -527,11 +534,6 @@
}
@Override
- public void onSessionClosed(int sessionId) {
- mHandler.obtainMessage(MSG_SESSION_CLOSED, sessionId, 0).sendToTarget();
- }
-
- @Override
public void onSessionFinished(int sessionId, boolean success) {
mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0)
.sendToTarget();
@@ -567,7 +569,7 @@
// TODO: remove this temporary guard once we have new prebuilts
final ApplicationInfo info = mContext.getApplicationInfo();
if ("com.google.android.googlequicksearchbox".equals(info.packageName)
- && info.versionCode <= 300400070) {
+ && info.versionCode <= 300400110) {
Log.d(TAG, "Ignoring callback request from old prebuilt");
return;
}
@@ -629,10 +631,22 @@
mSession = session;
}
- /**
- * Set current progress. Valid values are anywhere between 0 and 1.
- */
+ /** {@hide} */
+ @Deprecated
public void setProgress(float progress) {
+ setStagingProgress(progress);
+ }
+
+ /**
+ * Set current progress of staging this session. Valid values are
+ * anywhere between 0 and 1.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by {@link SessionCallback#onProgressChanged(int, float)}, as
+ * the system may carve out a portion of the overall progress to
+ * represent its own internal installation work.
+ */
+ public void setStagingProgress(float progress) {
try {
mSession.setClientProgress(progress);
} catch (RemoteException e) {
@@ -986,7 +1000,7 @@
/** {@hide} */
public boolean sealed;
/** {@hide} */
- public boolean open;
+ public boolean active;
/** {@hide} */
public int mode;
@@ -1010,7 +1024,7 @@
resolvedBaseCodePath = source.readString();
progress = source.readFloat();
sealed = source.readInt() != 0;
- open = source.readInt() != 0;
+ active = source.readInt() != 0;
mode = source.readInt();
sizeBytes = source.readLong();
@@ -1036,20 +1050,37 @@
/**
* Return current overall progress of this session, between 0 and 1.
* <p>
- * Note that this progress may not directly correspond to the value reported
- * by {@link PackageInstaller.Session#setProgress(float)}, as the system may
- * carve out a portion of the overall progress to represent its own internal
- * installation work.
+ * Note that this progress may not directly correspond to the value
+ * reported by
+ * {@link PackageInstaller.Session#setStagingProgress(float)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
*/
public float getProgress() {
return progress;
}
/**
- * Return if this session is currently open.
+ * Return if this session is currently active.
+ * <p>
+ * A session is considered active whenever there is ongoing forward
+ * progress being made, such as the installer holding an open
+ * {@link Session} instance while streaming data into place, or the
+ * system optimizing code as the result of
+ * {@link Session#commit(IntentSender)}.
+ * <p>
+ * If the installer closes the {@link Session} without committing, the
+ * session is considered inactive until the installer opens the session
+ * again.
*/
+ public boolean isActive() {
+ return active;
+ }
+
+ /** {@hide} */
+ @Deprecated
public boolean isOpen() {
- return open;
+ return isActive();
}
/**
@@ -1105,7 +1136,7 @@
dest.writeString(resolvedBaseCodePath);
dest.writeFloat(progress);
dest.writeInt(sealed ? 1 : 0);
- dest.writeInt(open ? 1 : 0);
+ dest.writeInt(active ? 1 : 0);
dest.writeInt(mode);
dest.writeLong(sizeBytes);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 2684e6c..e578822 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -773,6 +773,7 @@
private native final String[] getArrayStringResource(int arrayRes);
private native final int[] getArrayStringInfo(int arrayRes);
/*package*/ native final int[] getArrayIntResource(int arrayRes);
+ /*package*/ native final int[] getStyleAttributes(int themeRes);
private native final void init(boolean isSystem);
private native final void destroy();
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 31813c10..cabe228 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1572,6 +1572,16 @@
}
/**
+ * Gets all of the attribute ids associated with this {@link Theme}. For debugging only.
+ *
+ * @return The int array containing attribute ids associated with this {@link Theme}.
+ * @hide
+ */
+ public int[] getAllAttributes() {
+ return mAssets.getStyleAttributes(getAppliedStyleResId());
+ }
+
+ /**
* Returns the resources to which this theme belongs.
*
* @return Resources to which this theme belongs.
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 9361286..bf8ac65 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -535,7 +535,7 @@
destroyed = isDestroyedLocked();
}
- if (destroyed) {
+ if (destroyed && observer != null) {
try {
observer.onDestroy();
} catch (RemoteException re) {
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 9dc9766..47f72a8 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -367,6 +367,10 @@
throw new IllegalArgumentException("Unrecognized outline?");
}
+ public boolean hasShadow() {
+ return nHasShadow(mNativeRenderNode);
+ }
+
/**
* Enables or disables clipping to the outline.
*
@@ -861,6 +865,7 @@
float alpha);
private static native boolean nSetOutlineEmpty(long renderNode);
private static native boolean nSetOutlineNone(long renderNode);
+ private static native boolean nHasShadow(long renderNode);
private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
private static native boolean nSetRevealClip(long renderNode,
boolean shouldClip, float x, float y, float radius);
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index fa4a13a..debf45d 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -453,6 +453,12 @@
throw new IllegalStateException("Cannot clone this animator");
}
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ checkMutable();
+ nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
+ }
+
private static native long nCreateAnimator(int property, float finalValue);
private static native long nCreateCanvasPropertyFloatAnimator(
long canvasProperty, float finalValue);
@@ -466,6 +472,7 @@
private static native long nGetDuration(long nativePtr);
private static native void nSetStartDelay(long nativePtr, long startDelay);
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+ private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
private static native void nStart(long animPtr, RenderNodeAnimator finishListener);
private static native void nEnd(long animPtr);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 92ad4e8..770e78c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10224,6 +10224,7 @@
*
* @return true if the content in this view might overlap, false otherwise.
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean hasOverlappingRendering() {
return true;
}
@@ -10926,6 +10927,17 @@
invalidateViewProperty(false, false);
}
+ /**
+ * HierarchyViewer only
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean hasShadow() {
+ return mRenderNode.hasShadow();
+ }
+
+
/** @hide */
public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
mRenderNode.setRevealClip(shouldClip, x, y, radius);
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 6c66eb0..a94f973 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
@@ -315,6 +316,7 @@
private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
private static final String REMOTE_COMMAND_DUMP = "DUMP";
+ private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
private static final String REMOTE_PROFILE = "PROFILE";
@@ -430,6 +432,8 @@
if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
dump(view, false, true, clientStream);
+ } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
+ dumpTheme(view, clientStream);
} else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
captureLayers(view, new DataOutputStream(clientStream));
} else {
@@ -820,6 +824,64 @@
}
}
+ /**
+ * Dumps the theme attributes from the given View.
+ * @hide
+ */
+ public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
+ String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
+ view.getContext().getTheme());
+ if (attributes != null) {
+ for (int i = 0; i < attributes.length; i += 2) {
+ if (attributes[i] != null) {
+ out.write(attributes[i] + "\n");
+ out.write(attributes[i + 1] + "\n");
+ }
+ }
+ }
+ out.write("DONE.");
+ out.newLine();
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem dumping View Theme:", e);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
+ *
+ * @param resources Resources to resolve attributes from.
+ * @param theme Theme to dump.
+ * @return a String array containing pairs of adjacent Theme attribute data: name followed by
+ * its value.
+ *
+ * @hide
+ */
+ private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
+ TypedValue outValue = new TypedValue();
+ String nullString = "null";
+ int i = 0;
+ int[] attributes = theme.getAllAttributes();
+ String[] data = new String[attributes.length * 2];
+ for (int attributeId : attributes) {
+ try {
+ data[i] = resources.getResourceName(attributeId);
+ data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
+ outValue.coerceToString().toString() : nullString;
+ i += 2;
+ } catch (Resources.NotFoundException e) {
+ // ignore resources we can't resolve
+ }
+ }
+ return data;
+ }
+
private static View findView(ViewGroup group, String className, int hashCode) {
if (isRequestedView(group, className, hashCode)) {
return group;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 974fe4e..c1e66de 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3301,6 +3301,7 @@
* @return True if the group's children will be clipped to their bounds,
* false otherwise.
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean getClipChildren() {
return ((mGroupFlags & FLAG_CLIP_CHILDREN) != 0);
}
@@ -3349,6 +3350,7 @@
*
* @attr ref android.R.styleable#ViewGroup_clipToPadding
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean getClipToPadding() {
return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
}
@@ -5722,6 +5724,28 @@
}
@Override
+ public void drawableHotspotChanged(float x, float y) {
+ super.drawableHotspotChanged(x, y);
+
+ if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
+ if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+ throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
+ + " child has duplicateParentState set to true");
+ }
+
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
+ child.drawableHotspotChanged(x, y);
+ }
+ }
+ }
+ }
+
+ @Override
protected int[] onCreateDrawableState(int extraSpace) {
if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
return super.onCreateDrawableState(extraSpace);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 5faf03d..396f3ec 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1888,6 +1888,37 @@
return array;
}
+static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz,
+ jint styleId)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return NULL;
+ }
+ const ResTable& res(am->getResources());
+
+ const ResTable::bag_entry* startOfBag;
+ const ssize_t N = res.lockBag(styleId, &startOfBag);
+ if (N < 0) {
+ return NULL;
+ }
+
+ jintArray array = env->NewIntArray(N);
+ if (array == NULL) {
+ res.unlockBag(startOfBag);
+ return NULL;
+ }
+
+ Res_value value;
+ const ResTable::bag_entry* bag = startOfBag;
+ for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
+ int resourceId = bag->map.name.ident;
+ env->SetIntArrayRegion(array, i, 1, &resourceId);
+ }
+ res.unlockBag(startOfBag);
+ return array;
+}
+
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
@@ -2038,6 +2069,8 @@
(void*) android_content_AssetManager_getArrayStringInfo },
{ "getArrayIntResource","(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
+ { "getStyleAttributes","(I)[I",
+ (void*) android_content_AssetManager_getStyleAttributes },
// Bookkeeping.
{ "init", "(Z)V",
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 949f4ff..050037e 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -173,6 +173,12 @@
return true;
}
+static jboolean android_view_RenderNode_hasShadow(JNIEnv* env,
+ jobject clazz, jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ return renderNode->stagingProperties().hasShadow();
+}
+
static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jboolean clipToOutline) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -491,6 +497,7 @@
{ "nSetOutlineConvexPath", "(JJF)Z", (void*) android_view_RenderNode_setOutlineConvexPath },
{ "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
{ "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone },
+ { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow },
{ "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline },
{ "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index 85c2a09..84b7913 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -159,6 +159,11 @@
animator->setInterpolator(interpolator);
}
+static void setAllowRunningAsync(JNIEnv* env, jobject clazz, jlong animatorPtr, jboolean mayRunAsync) {
+ BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
+ animator->setAllowRunningAsync(mayRunAsync);
+}
+
static void start(JNIEnv* env, jobject clazz, jlong animatorPtr, jobject finishListener) {
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
if (finishListener) {
@@ -191,6 +196,7 @@
{ "nGetDuration", "(J)J", (void*) getDuration },
{ "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
{ "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
+ { "nSetAllowRunningAsync", "(JZ)V", (void*) setAllowRunningAsync },
{ "nStart", "(JLandroid/view/RenderNodeAnimator;)V", (void*) start },
{ "nEnd", "(J)V", (void*) end },
#endif
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 7e6d335..a8edb77 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -158,9 +158,11 @@
// Marks the start of a frame, which will update the frame time and move all
// next frame animations into the current frame
- virtual void startFrame() {
- mRootNode->doAttachAnimatingNodes(this);
- AnimationContext::startFrame();
+ virtual void startFrame(TreeInfo::TraversalMode mode) {
+ if (mode == TreeInfo::MODE_FULL) {
+ mRootNode->doAttachAnimatingNodes(this);
+ }
+ AnimationContext::startFrame(mode);
}
// Runs any animations still left in mCurrentFrameAnimations
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index d8e14a0..939cbf1 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -19,8 +19,8 @@
<color name="background_material_dark">#ff212121</color>
<color name="background_material_light">#fffafafa</color>
- <color name="ripple_material_light">#20444444</color>
- <color name="ripple_material_dark">#20ffffff</color>
+ <color name="ripple_material_light">#40000000</color>
+ <color name="ripple_material_dark">#40ffffff</color>
<color name="button_material_dark">#ff5a595b</color>
<color name="button_material_light">#ffd6d7d7</color>
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 43922b8..6f95f91 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,6 +22,7 @@
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
@@ -58,7 +59,7 @@
private final Rect mBounds;
/** Full-opacity color for drawing this ripple. */
- private int mColor;
+ private int mColorOpaque;
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -120,7 +121,7 @@
}
public void setup(int maxRadius, int color, float density) {
- mColor = color | 0xFF000000;
+ mColorOpaque = color | 0xFF000000;
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
@@ -236,6 +237,9 @@
if (N > 0) {
cancelHardwareAnimations(false);
+ // We canceled old animations, but we're about to run new ones.
+ mHardwareAnimating = true;
+
for (int i = 0; i < N; i++) {
pendingAnimations.get(i).setTarget(c);
pendingAnimations.get(i).start();
@@ -253,9 +257,8 @@
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- // Cache the paint alpha so we can restore it later.
- final int paintAlpha = p.getAlpha();
- final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
+ p.setColor(mColorOpaque);
+ final int alpha = (int) (255 * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
if (alpha > 0 && radius > 0) {
final float x = MathUtils.lerp(
@@ -268,8 +271,6 @@
hasContent = true;
}
- p.setAlpha(paintAlpha);
-
return hasContent;
}
@@ -369,7 +370,7 @@
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final Paint paint = getTempPaint();
paint.setAntiAlias(true);
- paint.setColor(mColor);
+ paint.setColor(mColorOpaque);
paint.setAlpha((int) (255 * mOpacity + 0.5f));
paint.setStyle(Style.FILL);
mPropPaint = CanvasProperty.createPaint(paint);
@@ -402,6 +403,12 @@
mHardwareAnimating = true;
+ // Set up the software values to match the hardware end values.
+ mOpacity = 0;
+ mTweenX = 1;
+ mTweenY = 1;
+ mTweenRadius = 1;
+
invalidateSelf();
}
@@ -412,7 +419,7 @@
public void jump() {
mCanceled = true;
endSoftwareAnimations();
- endHardwareAnimations();
+ cancelHardwareAnimations(true);
mCanceled = false;
}
@@ -438,24 +445,6 @@
}
}
- private void endHardwareAnimations() {
- final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
- final int N = runningAnimations.size();
- for (int i = 0; i < N; i++) {
- runningAnimations.get(i).end();
- }
- runningAnimations.clear();
-
- // Abort any pending animations. Since we always have a completion
- // listener on a pending animation, we also need to remove ourselves.
- if (!mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- removeSelf();
- }
-
- mHardwareAnimating = false;
- }
-
private Paint getTempPaint() {
if (mTempPaint == null) {
mTempPaint = new Paint();
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 80ecea3..bc6f5fb 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -22,6 +22,7 @@
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
@@ -46,8 +47,6 @@
private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
- private static final long RIPPLE_ENTER_DELAY = 80;
-
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
@@ -60,7 +59,10 @@
private final Rect mBounds;
/** Full-opacity color for drawing this ripple. */
- private int mColor;
+ private int mColorOpaque;
+
+ /** Maximum alpha value for drawing this ripple. */
+ private int mColorAlpha;
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -103,7 +105,8 @@
}
public void setup(int maxRadius, int color, float density) {
- mColor = color | 0xFF000000;
+ mColorOpaque = color | 0xFF000000;
+ mColorAlpha = Color.alpha(color);
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
@@ -159,6 +162,11 @@
return hasContent;
}
+ public boolean shouldDraw() {
+ final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
+ return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
+ }
+
private boolean drawHardware(HardwareCanvas c) {
// If we have any pending hardware animations, cancel any running
// animations and start those now.
@@ -167,6 +175,9 @@
if (N > 0) {
cancelHardwareAnimations(false);
+ // We canceled old animations, but we're about to run new ones.
+ mHardwareAnimating = true;
+
for (int i = 0; i < N; i++) {
pendingAnimations.get(i).setTarget(c);
pendingAnimations.get(i).start();
@@ -184,10 +195,8 @@
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- // Cache the paint alpha so we can restore it later.
- final int paintAlpha = p.getAlpha();
-
- final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+ p.setColor(mColorOpaque);
+ final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
if (outerAlpha > 0 && mOuterRadius > 0) {
p.setAlpha(outerAlpha);
p.setStyle(Style.FILL);
@@ -195,8 +204,6 @@
hasContent = true;
}
- p.setAlpha(paintAlpha);
-
return hasContent;
}
@@ -248,25 +255,25 @@
// Determine at what time the inner and outer opacity intersect.
// inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
// outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
- final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
+ final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
- final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
- * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
+ final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
+ + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) {
- exitHardware(opacityDuration, outerInflection, inflectionOpacity);
+ exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
} else {
- exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
+ exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
}
}
- private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+ private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
mPendingAnimations.clear();
final Paint outerPaint = getTempPaint();
outerPaint.setAntiAlias(true);
- outerPaint.setColor(mColor);
- outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
+ outerPaint.setColor(mColorOpaque);
+ outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
outerPaint.setStyle(Style.FILL);
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
@@ -274,21 +281,21 @@
mPropOuterY = CanvasProperty.createFloat(mOuterY);
final RenderNodeAnimator outerOpacityAnim;
- if (outerInflection > 0) {
+ if (inflectionDuration > 0) {
// Outer opacity continues to increase for a bit.
- outerOpacityAnim = new RenderNodeAnimator(
- mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
- outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint,
+ RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
+ outerOpacityAnim.setDuration(inflectionDuration);
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
// Chain the outer opacity exit animation.
- final int outerDuration = opacityDuration - outerInflection;
+ final int outerDuration = opacityDuration - inflectionDuration;
if (outerDuration > 0) {
final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
outerFadeOutAnim.setDuration(outerDuration);
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
- outerFadeOutAnim.setStartDelay(outerInflection);
+ outerFadeOutAnim.setStartDelay(inflectionDuration);
outerFadeOutAnim.setStartValue(inflectionOpacity);
outerFadeOutAnim.addListener(mAnimationListener);
@@ -320,7 +327,7 @@
*/
public void jump() {
endSoftwareAnimations();
- endHardwareAnimations();
+ cancelHardwareAnimations(true);
}
private void endSoftwareAnimations() {
@@ -330,23 +337,6 @@
}
}
- private void endHardwareAnimations() {
- final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
- final int N = runningAnimations.size();
- for (int i = 0; i < N; i++) {
- runningAnimations.get(i).end();
- }
- runningAnimations.clear();
-
- // Abort any pending animations. Since we always have a completion
- // listener on a pending animation, we also need to remove ourselves.
- if (!mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- }
-
- mHardwareAnimating = false;
- }
-
private Paint getTempPaint() {
if (mTempPaint == null) {
mTempPaint = new Paint();
@@ -354,18 +344,18 @@
return mTempPaint;
}
- private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+ private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
final ObjectAnimator outerOpacityAnim;
- if (outerInflection > 0) {
+ if (inflectionDuration > 0) {
// Outer opacity continues to increase for a bit.
outerOpacityAnim = ObjectAnimator.ofFloat(this,
"outerOpacity", inflectionOpacity / 255.0f);
outerOpacityAnim.setAutoCancel(true);
- outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim.setDuration(inflectionDuration);
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
// Chain the outer opacity exit animation.
- final int outerDuration = opacityDuration - outerInflection;
+ final int outerDuration = opacityDuration - inflectionDuration;
if (outerDuration > 0) {
outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -446,14 +436,4 @@
mHardwareAnimating = false;
}
};
-
- /**
- * Interpolator with a smooth log deceleration
- */
- private static final class LogInterpolator implements TimeInterpolator {
- @Override
- public float getInterpolation(float input) {
- return 1 - (float) Math.pow(400, -input * 1.4);
- }
- }
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index fa762b7..b05fb61 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -242,7 +242,7 @@
@Override
protected boolean onStateChange(int[] stateSet) {
- super.onStateChange(stateSet);
+ final boolean changed = super.onStateChange(stateSet);
boolean enabled = false;
boolean pressed = false;
@@ -263,19 +263,7 @@
setRippleActive(enabled && pressed);
setBackgroundActive(focused || (enabled && pressed));
- // Update the paint color. Only applicable when animated in software.
- if (mRipplePaint != null && mState.mColor != null) {
- final ColorStateList stateList = mState.mColor;
- final int newColor = stateList.getColorForState(stateSet, 0);
- final int oldColor = mRipplePaint.getColor();
- if (oldColor != newColor) {
- mRipplePaint.setColor(newColor);
- invalidateSelf();
- return true;
- }
- }
-
- return false;
+ return changed;
}
private void setRippleActive(boolean active) {
@@ -587,6 +575,10 @@
ripples[i].onHotspotBoundsChanged();
}
+ if (mRipple != null) {
+ mRipple.onHotspotBoundsChanged();
+ }
+
if (mBackground != null) {
mBackground.onHotspotBoundsChanged();
}
@@ -617,17 +609,23 @@
final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
final Rect bounds = getDirtyBounds();
+ final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(bounds);
// If we have content, draw it into a layer first.
- final int contentLayer = drawNonMaskContent ?
- drawContentLayer(canvas, bounds, SRC_OVER) : -1;
+ final int contentLayer;
+ if (drawNonMaskContent) {
+ contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
+ } else {
+ contentLayer = -1;
+ }
// Next, try to draw the ripples (into a layer if necessary). If we need
// to mask against the underlying content, set the xfermode to SRC_ATOP.
final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
// If we have a background and a non-opaque mask, draw the masking layer.
- final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
+ final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
if (backgroundLayer >= 0) {
if (drawMask) {
drawMaskingLayer(canvas, bounds, DST_IN);
@@ -644,10 +642,13 @@
canvas.restoreToCount(rippleLayer);
}
- // Composite the layers if needed.
- if (contentLayer >= 0) {
- canvas.restoreToCount(contentLayer);
+ // If we failed to draw anything, at least draw a color so that
+ // invalidation works correctly.
+ if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0) {
+ canvas.drawColor(Color.TRANSPARENT);
}
+
+ canvas.restoreToCount(saveCount);
}
/**
@@ -711,81 +712,29 @@
return restoreToCount;
}
- private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- // Separate the ripple color and alpha channel. The alpha will be
- // applied when we merge the ripples down to the canvas.
- final int rippleARGB;
- if (mState.mColor != null) {
- rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- } else {
- rippleARGB = Color.TRANSPARENT;
+ private int drawBackgroundLayer(
+ Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
+ int saveCount = -1;
+
+ if (mBackground != null && mBackground.shouldDraw()) {
+ // TODO: We can avoid saveLayer here if we push the xfermode into
+ // the background's render thread animator at exit() time.
+ if (drawMask || mode != SRC_OVER) {
+ saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
+ bounds.bottom, getMaskingPaint(mode));
+ }
+
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+ canvas.translate(x, y);
+ mBackground.draw(canvas, getRipplePaint());
+ canvas.translate(-x, -y);
}
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
- }
-
- final int rippleAlpha = Color.alpha(rippleARGB);
- final Paint ripplePaint = mRipplePaint;
- ripplePaint.setColor(rippleARGB);
- ripplePaint.setAlpha(0xFF);
-
- boolean drewRipples = false;
- int restoreToCount = -1;
- int restoreTranslate = -1;
-
- // Draw background.
- final RippleBackground background = mBackground;
- if (background != null) {
- // If we're masking the ripple layer, make sure we have a layer
- // first. This will merge SRC_OVER (directly) onto the canvas.
- final Paint maskingPaint = getMaskingPaint(mode);
- maskingPaint.setAlpha(rippleAlpha);
- restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, maskingPaint);
-
- restoreTranslate = canvas.save();
- // Translate the canvas to the current hotspot bounds.
- canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
-
- drewRipples = background.draw(canvas, ripplePaint);
- }
-
- // Always restore the translation.
- if (restoreTranslate >= 0) {
- canvas.restoreToCount(restoreTranslate);
- }
-
- // If we created a layer with no content, merge it immediately.
- if (restoreToCount >= 0 && !drewRipples) {
- canvas.restoreToCount(restoreToCount);
- restoreToCount = -1;
- }
-
- return restoreToCount;
+ return saveCount;
}
private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- // Separate the ripple color and alpha channel. The alpha will be
- // applied when we merge the ripples down to the canvas.
- final int rippleARGB;
- if (mState.mColor != null) {
- rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- } else {
- rippleARGB = Color.TRANSPARENT;
- }
-
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
- }
-
- final int rippleAlpha = Color.alpha(rippleARGB);
- final Paint ripplePaint = mRipplePaint;
- ripplePaint.setColor(rippleARGB);
- ripplePaint.setAlpha(0xFF);
-
boolean drewRipples = false;
int restoreToCount = -1;
int restoreTranslate = -1;
@@ -807,16 +756,21 @@
// first. This will merge SRC_OVER (directly) onto the canvas.
if (restoreToCount < 0) {
final Paint maskingPaint = getMaskingPaint(mode);
- maskingPaint.setAlpha(rippleAlpha);
+ final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+ final int alpha = Color.alpha(color);
+ maskingPaint.setAlpha(alpha / 2);
+
+ // TODO: We can avoid saveLayer here if we're only drawing one
+ // ripple and we don't have content or a translucent mask.
restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
bounds.right, bounds.bottom, maskingPaint);
- restoreTranslate = canvas.save();
// Translate the canvas to the current hotspot bounds.
+ restoreTranslate = canvas.save();
canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
}
- drewRipples |= ripple.draw(canvas, ripplePaint);
+ drewRipples |= ripple.draw(canvas, getRipplePaint());
}
// Always restore the translation.
@@ -845,6 +799,14 @@
return restoreToCount;
}
+ private Paint getRipplePaint() {
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ }
+ return mRipplePaint;
+ }
+
private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
if (mMaskingPaint == null) {
mMaskingPaint = new Paint();
diff --git a/libs/hwui/AnimationContext.cpp b/libs/hwui/AnimationContext.cpp
index 716dcf5..a20bdae 100644
--- a/libs/hwui/AnimationContext.cpp
+++ b/libs/hwui/AnimationContext.cpp
@@ -17,7 +17,6 @@
#include "Animator.h"
#include "RenderNode.h"
-#include "TreeInfo.h"
#include "renderthread/TimeLord.h"
namespace android {
@@ -34,7 +33,7 @@
}
void AnimationContext::destroy() {
- startFrame();
+ startFrame(TreeInfo::MODE_RT_ONLY);
while (mCurrentFrameAnimations.mNextHandle) {
AnimationHandle* current = mCurrentFrameAnimations.mNextHandle;
AnimatorManager& animators = current->mRenderNode->animators();
@@ -55,7 +54,7 @@
handle->insertAfter(&mNextFrameAnimations);
}
-void AnimationContext::startFrame() {
+void AnimationContext::startFrame(TreeInfo::TraversalMode mode) {
LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle,
"Missed running animations last frame!");
AnimationHandle* head = mNextFrameAnimations.mNextHandle;
diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h
index 9b3d5f4..909ed36 100644
--- a/libs/hwui/AnimationContext.h
+++ b/libs/hwui/AnimationContext.h
@@ -20,6 +20,7 @@
#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
+#include "TreeInfo.h"
#include "renderthread/TimeLord.h"
#include "utils/Macros.h"
@@ -30,7 +31,6 @@
class AnimationListener;
class BaseRenderNodeAnimator;
class RenderNode;
-class TreeInfo;
/*
* AnimationHandle is several classes merged into one.
@@ -90,7 +90,7 @@
// Marks the start of a frame, which will update the frame time and move all
// next frame animations into the current frame
- ANDROID_API virtual void startFrame();
+ ANDROID_API virtual void startFrame(TreeInfo::TraversalMode mode);
// Runs any animations still left in mCurrentFrameAnimations that were not run
// as part of the standard RenderNode:prepareTree pass.
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 1ab6235..35a4a09 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -53,6 +53,10 @@
mListener = listener;
}
AnimationListener* listener() { return mListener.get(); }
+ ANDROID_API void setAllowRunningAsync(bool mayRunAsync) {
+ mMayRunAsync = mayRunAsync;
+ }
+ bool mayRunAsync() { return mMayRunAsync; }
ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); }
ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
@@ -101,6 +105,7 @@
nsecs_t mStartTime;
nsecs_t mDuration;
nsecs_t mStartDelay;
+ bool mMayRunAsync;
sp<AnimationListener> mListener;
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index e06d800..c28fb88 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -90,6 +90,9 @@
if (animator->isRunning()) {
mInfo.out.hasAnimations = true;
}
+ if (CC_UNLIKELY(!animator->mayRunAsync())) {
+ mInfo.out.requiresUiRedraw = true;
+ }
}
return remove;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 0c8d07f..46eeb6a 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -569,6 +569,10 @@
return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty());
}
+ bool hasShadow() const {
+ return getZ() >= 0.0f && getOutline().getPath() != NULL;
+ }
+
private:
// Rendering properties
struct PrimitiveFields {
diff --git a/libs/hwui/RenderState.cpp b/libs/hwui/RenderState.cpp
index a7c5e85..ec8307f 100644
--- a/libs/hwui/RenderState.cpp
+++ b/libs/hwui/RenderState.cpp
@@ -38,6 +38,7 @@
}
void RenderState::onGLContextDestroyed() {
+ AutoMutex _lock(mLayerLock);
if (CC_UNLIKELY(!mActiveLayers.empty())) {
mCaches->dumpMemoryUsage();
for (std::set<renderthread::CanvasContext*>::iterator cit = mRegisteredContexts.begin();
@@ -51,6 +52,13 @@
}
context->mRootRenderNode->debugDumpLayers(" ");
}
+
+ for (std::set<const Layer*>::iterator lit = mActiveLayers.begin();
+ lit != mActiveLayers.end(); lit++) {
+ const Layer* layer = *(lit);
+ ALOGD("Layer %p, fbo %d, buildlayered %d",
+ layer, layer->getFbo(), layer->wasBuildLayered);
+ }
LOG_ALWAYS_FATAL("layers have survived gl context destruction");
}
}
diff --git a/libs/hwui/RenderState.h b/libs/hwui/RenderState.h
index c7ab197..74bafca 100644
--- a/libs/hwui/RenderState.h
+++ b/libs/hwui/RenderState.h
@@ -19,6 +19,7 @@
#include <set>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
+#include <utils/Mutex.h>
#include <private/hwui/DrawGlInfo.h>
@@ -52,9 +53,11 @@
void debugOverdraw(bool enable, bool clear);
void registerLayer(const Layer* layer) {
+ AutoMutex _lock(mLayerLock);
mActiveLayers.insert(layer);
}
void unregisterLayer(const Layer* layer) {
+ AutoMutex _lock(mLayerLock);
mActiveLayers.erase(layer);
}
@@ -83,6 +86,7 @@
GLsizei mViewportWidth;
GLsizei mViewportHeight;
GLuint mFramebuffer;
+ Mutex mLayerLock;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 4129a89..5e6796c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -150,7 +150,7 @@
if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) {
info.canvasContext = this;
}
- mAnimationContext->startFrame();
+ mAnimationContext->startFrame(info.mode);
mRootRenderNode->prepareTree(info);
mAnimationContext->runRemainingAnimations(info);
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 308f783..9b644f4 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -530,7 +530,6 @@
}
}
- /** @hide */
public static final Parcelable.Creator<AudioAttributes> CREATOR
= new Parcelable.Creator<AudioAttributes>() {
/**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9ee7027..e3b8985 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -243,7 +243,7 @@
* or unplugged.
* An integer value of 1 indicates a plugged-in state, 0 is unplugged.
*/
- public static final String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.audio_plug_state";
+ public static final String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.AUDIO_PLUG_STATE";
/**
* Extra used in {@link #ACTION_HDMI_AUDIO_PLUG} to define the maximum number of channels
@@ -251,7 +251,7 @@
* The corresponding integer value is only available when the device is plugged in (as expressed
* by {@link #EXTRA_AUDIO_PLUG_STATE}).
*/
- public static final String EXTRA_MAX_CHANNEL_COUNT = "android.media.extra.max_channel_count";
+ public static final String EXTRA_MAX_CHANNEL_COUNT = "android.media.extra.MAX_CHANNEL_COUNT";
/**
* Extra used in {@link #ACTION_HDMI_AUDIO_PLUG} to define the audio encodings supported by
@@ -261,7 +261,7 @@
* {@link AudioFormat} (for instance see {@link AudioFormat#ENCODING_PCM_16BIT}). Use
* {@link android.content.Intent#getIntArrayExtra(String)} to retrieve the encoding values.
*/
- public static final String EXTRA_ENCODINGS = "android.media.extra.encodings";
+ public static final String EXTRA_ENCODINGS = "android.media.extra.ENCODINGS";
/**
* Broadcast Action: An analog audio speaker/headset plugged in or unplugged.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 3e8ee93..675916b 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1622,8 +1622,7 @@
* @throws IllegalStateException if in the Uninitialized state.
*/
public MediaCodecInfo getCodecInfo() {
- return MediaCodecList.getCodecInfoAt(
- MediaCodecList.findCodecByName(getName()));
+ return MediaCodecList.getInfoFor(getName());
}
private native final ByteBuffer[] getBuffers(boolean input);
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 323a3e3..01f8193 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -321,6 +321,9 @@
// check feature support
for (Feature feat: getValidFeatures()) {
Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+ if (yesNo == null) {
+ continue;
+ }
if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
(yesNo == 0 && isFeatureRequired(feat.mName))) {
return false;
@@ -414,7 +417,7 @@
* profile} and {@code level}. If the type, or profile-level combination
* is not understood by the framework, it returns null.
*/
- public static CodecCapabilities CreateFromProfileLevel(
+ public static CodecCapabilities createFromProfileLevel(
String mime, int profile, int level) {
CodecProfileLevel pl = new CodecProfileLevel();
pl.profile = profile;
@@ -470,13 +473,12 @@
Integer yesNo = (Integer)map.get(key);
if (yesNo == null) {
continue;
- } else if (yesNo > 0) {
- mFlagsRequired |= feat.mValue;
- mDefaultFormat.setInteger(key, 1);
- } else {
- mFlagsSupported |= feat.mValue;
- mDefaultFormat.setInteger(key, 1);
}
+ if (yesNo > 0) {
+ mFlagsRequired |= feat.mValue;
+ }
+ mFlagsSupported |= feat.mValue;
+ mDefaultFormat.setInteger(key, 1);
// TODO restrict features by mFlagsVerified once all codecs reliably verify them
}
}
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index 5084c5c..85e9b16 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -116,6 +116,11 @@
/* package private */ static native final int findCodecByName(String codec);
+ /** @hide */
+ public static MediaCodecInfo getInfoFor(String codec) {
+ return sAllCodecInfos[findCodecByName(codec)];
+ }
+
private static native final void native_init();
/**
diff --git a/packages/PrintSpooler/res/layout/preview_page_loading.xml b/packages/PrintSpooler/res/layout/preview_page_loading.xml
new file mode 100644
index 0000000..1af3a17
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/preview_page_loading.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="36dip"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_grayedout_printer"
+ android:contentDescription="@null"
+ android:scaleType="centerInside"
+ android:adjustViewBounds="true">
+ </ImageView>
+
+</FrameLayout>
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index 6cf9754b5..84fc050 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -16,7 +16,7 @@
<resources>
- <integer name="preview_page_per_row_count">2</integer>
+ <integer name="preview_page_per_row_count">4</integer>
<integer name="print_option_column_count">3</integer>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 5b7fda3..27e1d51 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -76,7 +76,6 @@
<!-- Title for the print dialog announced to the user for accessibility. Not shown in the UI. [CHAR LIMIT=none] -->
<string name="print_dialog">Print dialog</string>
-
<!-- Template for the message that shows the current page out of the total number of pages -->
<string name="current_page_template"><xliff:g id="current_page">%1$d</xliff:g>
/<xliff:g id="page_count">%2$d</xliff:g></string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 09e8b39..1e7a011 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -317,6 +317,11 @@
return mState == STATE_FAILED;
}
+ public boolean hasLaidOutPages() {
+ return mDocumentInfo.info != null
+ && mDocumentInfo.info.getPageCount() > 0;
+ }
+
public void clearUpdateError() {
if (!hasUpdateError()) {
throw new IllegalStateException("No update error to clear");
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index d949673..ce0b9b6 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -17,6 +17,9 @@
package com.android.printspooler.ui;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes.MediaSize;
@@ -26,12 +29,12 @@
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.View.MeasureSpec;
import android.widget.TextView;
import com.android.printspooler.R;
import com.android.printspooler.model.PageContentRepository;
@@ -48,7 +51,7 @@
* This class represents the adapter for the pages in the print preview list.
*/
public final class PageAdapter extends Adapter implements
- PageContentRepository.OnMalformedPdfFileListener{
+ PageContentRepository.OnMalformedPdfFileListener {
private static final String LOG_TAG = "PageAdapter";
private static final int MAX_PREVIEW_PAGES_BATCH = 50;
@@ -86,6 +89,8 @@
// Pages the user selected in the UI.
private PageRange[] mSelectedPages;
+ private BitmapDrawable mEmptyState;
+
private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
private int mSelectedPageCount;
@@ -257,7 +262,7 @@
}
if (updatePreviewAreaAndPageSize) {
- updatePreviewAreaAndPageSize();
+ updatePreviewAreaPageSizeAndEmptyState();
}
if (documentChanged) {
@@ -318,12 +323,12 @@
}
// OK, there are bugs in recycler view which tries to bind views
- // without recycling them which would give us a chane to clean up.
+ // without recycling them which would give us a chance to clean up.
PageContentProvider boundProvider = mPageContentRepository
- .peekPageContentProvider(pageIndexInFile);
+ .peekPageContentProvider(pageIndexInFile);
if (boundProvider != null) {
PageContentView owner = (PageContentView) boundProvider.getOwner();
- owner.init(null, mMediaSize, mMinMargins);
+ owner.init(null, mEmptyState, mMediaSize, mMinMargins);
mPageContentRepository.releasePageContentProvider(boundProvider);
}
@@ -333,7 +338,7 @@
} else {
onSelectedPageNotInFile(pageInDocument);
}
- content.init(provider, mMediaSize, mMinMargins);
+ content.init(provider, mEmptyState, mMediaSize, mMinMargins);
View pageSelector = page.findViewById(R.id.page_selector);
pageSelector.setTag(myHolder);
@@ -384,7 +389,7 @@
mSelectedPages = selectedPages;
mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
mSelectedPages, mDocumentPageCount);
- updatePreviewAreaAndPageSize();
+ updatePreviewAreaPageSizeAndEmptyState();
notifyDataSetChanged();
}
return mSelectedPages;
@@ -392,12 +397,12 @@
public void onPreviewAreaSizeChanged() {
if (mMediaSize != null) {
- updatePreviewAreaAndPageSize();
+ updatePreviewAreaPageSizeAndEmptyState();
notifyDataSetChanged();
}
}
- private void updatePreviewAreaAndPageSize() {
+ private void updatePreviewAreaPageSizeAndEmptyState() {
final int availableWidth = mPreviewArea.getWidth();
final int availableHeight = mPreviewArea.getHeight();
@@ -419,7 +424,7 @@
final int pageContentDesiredHeight = (int) (((float) pageContentDesiredWidth
/ pageAspectRatio) + 0.5f);
- // If the page does not fit entirely in a vertial direction,
+ // If the page does not fit entirely in a vertical direction,
// we shirk it but not less than the minimal page width.
final int pageContentMinHeight = (int) (mPreviewPageMinWidth / pageAspectRatio + 0.5f);
final int pageContentMaxHeight = Math.max(pageContentMinHeight,
@@ -448,6 +453,23 @@
mPreviewArea.setPadding(horizontalPadding, verticalPadding,
horizontalPadding, verticalPadding);
+
+ // Now update the empty state drawable, as it depends on the page
+ // size and is reused for all views for better performance.
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ View content = inflater.inflate(R.layout.preview_page_loading, null, false);
+ content.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY));
+ content.layout(0, 0, content.getMeasuredWidth(), content.getMeasuredHeight());
+
+ Bitmap bitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ content.draw(canvas);
+
+ // Do not recycle the old bitmap if such as it may be set as an empty
+ // state to any of the page views. Just let the GC take care of it.
+ mEmptyState = new BitmapDrawable(mContext.getResources(), bitmap);
}
private PageRange[] computeSelectedPages() {
@@ -718,7 +740,7 @@
private void recyclePageView(PageContentView page, int pageIndexInAdapter) {
PageContentProvider provider = page.getPageContentProvider();
if (provider != null) {
- page.init(null, null, null);
+ page.init(null, null, null, null);
mPageContentRepository.releasePageContentProvider(provider);
mBoundPagesInAdapter.remove(pageIndexInAdapter);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 022e0d0..535081f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -114,14 +114,15 @@
private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
- private static final int STATE_CONFIGURING = 0;
- private static final int STATE_PRINT_CONFIRMED = 1;
- private static final int STATE_PRINT_CANCELED = 2;
- private static final int STATE_UPDATE_FAILED = 3;
- private static final int STATE_CREATE_FILE_FAILED = 4;
- private static final int STATE_PRINTER_UNAVAILABLE = 5;
- private static final int STATE_UPDATE_SLOW = 6;
- private static final int STATE_PRINT_COMPLETED = 7;
+ private static final int STATE_INITIALIZING = 0;
+ private static final int STATE_CONFIGURING = 1;
+ private static final int STATE_PRINT_CONFIRMED = 2;
+ private static final int STATE_PRINT_CANCELED = 3;
+ private static final int STATE_UPDATE_FAILED = 4;
+ private static final int STATE_CREATE_FILE_FAILED = 5;
+ private static final int STATE_PRINTER_UNAVAILABLE = 6;
+ private static final int STATE_UPDATE_SLOW = 7;
+ private static final int STATE_PRINT_COMPLETED = 8;
private static final int UI_STATE_PREVIEW = 0;
private static final int UI_STATE_ERROR = 1;
@@ -196,7 +197,7 @@
private int mCurrentPageCount;
- private int mState;
+ private int mState = STATE_INITIALIZING;
private int mUiState = UI_STATE_PREVIEW;
@@ -290,10 +291,17 @@
mPrintedDocument.start();
ensurePreviewUiShown();
+
+ setState(STATE_CONFIGURING);
}
@Override
public void onPause() {
+ if (mState == STATE_INITIALIZING) {
+ super.onPause();
+ return;
+ }
+
if (isFinishing()) {
PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
@@ -341,9 +349,13 @@
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mState == STATE_INITIALIZING) {
+ return super.onKeyUp(keyCode, event);
+ }
+
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.isTracking() && !event.isCanceled()) {
- if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened()
+ if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
&& !hasErrors()) {
mPrintPreviewController.closeOptions();
} else {
@@ -900,7 +912,7 @@
final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
pages, preview);
- if (willUpdate) {
+ if (willUpdate && !mPrintedDocument.hasLaidOutPages()) {
// When the update is done we update the print preview.
mProgressMessageController.post();
return true;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index 2b5b41b..a25e05e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -195,6 +195,7 @@
if (mPageAdapter.isOpened()) {
mPageAdapter.close(null);
}
+ mRecyclerView.setAdapter(null);
mPageAdapter.destroy();
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
index 23a01bd..e2ae758 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
@@ -17,17 +17,15 @@
package com.android.printspooler.widget;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.View;
import com.android.printspooler.model.PageContentRepository;
-import com.android.printspooler.model.PageContentRepository.PageContentProvider;
import com.android.printspooler.model.PageContentRepository.RenderSpec;
+import com.android.printspooler.model.PageContentRepository.PageContentProvider;
/**
* This class represents a page in the print preview list. The width of the page
@@ -37,35 +35,20 @@
*/
public class PageContentView extends View
implements PageContentRepository.OnPageContentAvailableCallback {
-
- private final ColorDrawable mEmptyState;
-
private PageContentProvider mProvider;
private MediaSize mMediaSize;
private Margins mMinMargins;
+ private Drawable mEmptyState;
+
private boolean mContentRequested;
private boolean mNeedsLayout;
public PageContentView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.textColorPrimary,
- typedValue, true);
-
- mEmptyState = new ColorDrawable(typedValue.data);
-
- setBackground(mEmptyState);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- requestPageContentIfNeeded();
}
@Override
@@ -85,15 +68,19 @@
return mProvider;
}
- public void init(PageContentProvider provider, MediaSize mediaSize, Margins minMargins) {
+ public void init(PageContentProvider provider, Drawable emptyState,
+ MediaSize mediaSize, Margins minMargins) {
final boolean providerChanged = (mProvider == null)
? provider != null : !mProvider.equals(provider);
+ final boolean loadingDrawableChanged = (mEmptyState == null)
+ ? mEmptyState != null : !mEmptyState.equals(emptyState);
final boolean mediaSizeChanged = (mMediaSize == null)
? mediaSize != null : !mMediaSize.equals(mediaSize);
final boolean marginsChanged = (mMinMargins == null)
? minMargins != null : !mMinMargins.equals(minMargins);
- if (!providerChanged && !mediaSizeChanged && !marginsChanged) {
+ if (!providerChanged && !mediaSizeChanged
+ && !marginsChanged && !loadingDrawableChanged) {
return;
}
@@ -101,6 +88,7 @@
mMediaSize = mediaSize;
mMinMargins = minMargins;
+ mEmptyState = emptyState;
mContentRequested = false;
mNeedsLayout = mNeedsLayout || mediaSizeChanged || marginsChanged;
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/services/core/java/com/android/server/NativeDaemonEvent.java
index 2095152..59d50bd 100644
--- a/services/core/java/com/android/server/NativeDaemonEvent.java
+++ b/services/core/java/com/android/server/NativeDaemonEvent.java
@@ -201,20 +201,16 @@
}
while (current < length) {
// find the end of the word
- if (quoted) {
- wordEnd = current;
- while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) {
- if (rawEvent.charAt(wordEnd - 1) != '\\') {
- break;
- } else {
- wordEnd++; // skip this escaped quote and keep looking
- }
+ char terminator = quoted ? '\"' : ' ';
+ wordEnd = current;
+ while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
+ if (rawEvent.charAt(wordEnd) == '\\') {
+ // skip the escaped char
+ ++wordEnd;
}
- } else {
- wordEnd = rawEvent.indexOf(' ', current);
+ ++wordEnd;
}
- // if we didn't find the end-o-word token, take the rest of the string
- if (wordEnd == -1) wordEnd = length;
+ if (wordEnd > length) wordEnd = length;
String word = rawEvent.substring(current, wordEnd);
current += word.length();
if (!quoted) {
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index cf7e65c..cb1748d 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -397,8 +397,7 @@
break;
case NsdManager.NATIVE_DAEMON_EVENT:
NativeEvent event = (NativeEvent) msg.obj;
- if (!handleNativeEvent(event.code, event.raw,
- NativeDaemonEvent.unescapeArgs(event.raw))) {
+ if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
result = NOT_HANDLED;
}
break;
@@ -474,8 +473,14 @@
case NativeResponseCode.SERVICE_RESOLVED:
/* NNN resolveId fullName hostName port txtlen txtdata */
if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
- int index = cooked[2].indexOf(".");
- if (index == -1) {
+ int index = 0;
+ while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
+ if (cooked[2].charAt(index) == '\\') {
+ ++index;
+ }
+ ++index;
+ }
+ if (index >= cooked[2].length()) {
Slog.e(TAG, "Invalid service found " + raw);
break;
}
@@ -483,6 +488,8 @@
String rest = cooked[2].substring(index);
String type = rest.replace(".local.", "");
+ name = unescape(name);
+
clientInfo.mResolvedService.setServiceName(name);
clientInfo.mResolvedService.setServiceType(type);
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
@@ -541,6 +548,30 @@
}
}
+ private String unescape(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ if (c == '\\') {
+ if (++i >= s.length()) {
+ Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = s.charAt(i);
+ if (c != '.' && c != '\\') {
+ if (i + 2 >= s.length()) {
+ Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
+ i += 2;
+ }
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
private NativeDaemonConnector mNativeConnector;
private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
@@ -625,10 +656,12 @@
private class NativeEvent {
final int code;
final String raw;
+ final String[] cooked;
- NativeEvent(int code, String raw) {
+ NativeEvent(int code, String raw, String[] cooked) {
this.code = code;
this.raw = raw;
+ this.cooked = cooked;
}
}
@@ -644,7 +677,7 @@
public boolean onEvent(int code, String raw, String[] cooked) {
// TODO: NDC translates a message to a callback, we could enhance NDC to
// directly interact with a state machine through messages
- NativeEvent event = new NativeEvent(code, raw);
+ NativeEvent event = new NativeEvent(code, raw, cooked);
mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
return true;
}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 2ef9828..0cb8eb8 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -453,11 +453,10 @@
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
if (action.equals(ALARM_WAKEUP)) {
- if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
startNavigating(false);
} else if (action.equals(ALARM_TIMEOUT)) {
- if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
hibernate();
} else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
checkSmsSuplInit(intent);
@@ -691,7 +690,6 @@
intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter = new IntentFilter();
intentFilter.addAction(SIM_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 9a00923..89ea905 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -897,10 +897,9 @@
private static class Callbacks extends Handler {
private static final int MSG_SESSION_CREATED = 1;
private static final int MSG_SESSION_BADGING_CHANGED = 2;
- private static final int MSG_SESSION_OPENED = 3;
+ private static final int MSG_SESSION_ACTIVE_CHANGED = 3;
private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
- private static final int MSG_SESSION_CLOSED = 5;
- private static final int MSG_SESSION_FINISHED = 6;
+ private static final int MSG_SESSION_FINISHED = 5;
private final RemoteCallbackList<IPackageInstallerCallback>
mCallbacks = new RemoteCallbackList<>();
@@ -945,15 +944,12 @@
case MSG_SESSION_BADGING_CHANGED:
callback.onSessionBadgingChanged(sessionId);
break;
- case MSG_SESSION_OPENED:
- callback.onSessionOpened(sessionId);
+ case MSG_SESSION_ACTIVE_CHANGED:
+ callback.onSessionActiveChanged(sessionId, (boolean) msg.obj);
break;
case MSG_SESSION_PROGRESS_CHANGED:
callback.onSessionProgressChanged(sessionId, (float) msg.obj);
break;
- case MSG_SESSION_CLOSED:
- callback.onSessionClosed(sessionId);
- break;
case MSG_SESSION_FINISHED:
callback.onSessionFinished(sessionId, (boolean) msg.obj);
break;
@@ -968,18 +964,14 @@
obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget();
}
- private void notifySessionOpened(int sessionId, int userId) {
- obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget();
+ private void notifySessionActiveChanged(int sessionId, int userId, boolean active) {
+ obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget();
}
private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
}
- private void notifySessionClosed(int sessionId, int userId) {
- obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget();
- }
-
public void notifySessionFinished(int sessionId, int userId, boolean success) {
obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
}
@@ -1022,18 +1014,14 @@
writeSessionsAsync();
}
- public void onSessionOpened(PackageInstallerSession session) {
- mCallbacks.notifySessionOpened(session.sessionId, session.userId);
+ public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
+ mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active);
}
public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
}
- public void onSessionClosed(PackageInstallerSession session) {
- mCallbacks.notifySessionClosed(session.sessionId, session.userId);
- }
-
public void onSessionFinished(PackageInstallerSession session, boolean success) {
mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
synchronized (mSessions) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 85ff54e..f8273c0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -227,7 +227,7 @@
mResolvedBaseFile.getAbsolutePath() : null;
info.progress = mProgress;
info.sealed = mSealed;
- info.open = mOpenCount.get() > 0;
+ info.active = mOpenCount.get() > 0;
info.mode = params.mode;
info.sizeBytes = params.sizeBytes;
@@ -833,14 +833,14 @@
public void open() {
if (mOpenCount.getAndIncrement() == 0) {
- mCallback.onSessionOpened(this);
+ mCallback.onSessionActiveChanged(this, true);
}
}
@Override
public void close() {
if (mOpenCount.decrementAndGet() == 0) {
- mCallback.onSessionClosed(this);
+ mCallback.onSessionActiveChanged(this, false);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d2a627e..7b4270b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3783,21 +3783,25 @@
} else {
pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
}
- if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
- if (numMatch == permissions.length) {
- pi.requestedPermissions = permissions;
- } else {
- pi.requestedPermissions = new String[numMatch];
- numMatch = 0;
- for (int i=0; i<permissions.length; i++) {
- if (tmp[i]) {
- pi.requestedPermissions[numMatch] = permissions[i];
- numMatch++;
+ // The above might return null in cases of uninstalled apps or install-state
+ // skew across users/profiles.
+ if (pi != null) {
+ if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
+ if (numMatch == permissions.length) {
+ pi.requestedPermissions = permissions;
+ } else {
+ pi.requestedPermissions = new String[numMatch];
+ numMatch = 0;
+ for (int i=0; i<permissions.length; i++) {
+ if (tmp[i]) {
+ pi.requestedPermissions[numMatch] = permissions[i];
+ numMatch++;
+ }
}
}
}
+ list.add(pi);
}
- list.add(pi);
}
@Override
@@ -10260,7 +10264,7 @@
|| !bp.packageSetting.keySetData.isUsingUpgradeKeySets()
|| ((PackageSetting) bp.packageSetting).sharedUser != null) {
sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
- pkg.mSignatures) != PackageManager.SIGNATURE_MATCH;
+ pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
} else {
sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 6c80a65..5f639ab 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -23,13 +23,11 @@
import android.util.ArrayMap;
import android.util.ArraySet;
-import java.util.ArrayList;
-
class IntervalStats {
public long beginTime;
public long endTime;
public long lastTimeSaved;
- public final ArrayMap<String, UsageStats> stats = new ArrayMap<>();
+ public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
public Configuration activeConfiguration;
public TimeSparseArray<UsageEvents.Event> events;
@@ -44,13 +42,13 @@
* Gets the UsageStats object for the given package, or creates one and adds it internally.
*/
UsageStats getOrCreateUsageStats(String packageName) {
- UsageStats usageStats = stats.get(packageName);
+ UsageStats usageStats = packageStats.get(packageName);
if (usageStats == null) {
usageStats = new UsageStats();
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
- stats.put(usageStats.mPackageName, usageStats);
+ packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
}
diff --git a/services/usage/java/com/android/server/usage/UnixCalendar.java b/services/usage/java/com/android/server/usage/UnixCalendar.java
new file mode 100644
index 0000000..ce06a91
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UnixCalendar.java
@@ -0,0 +1,99 @@
+/**
+ * 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.server.usage;
+
+import android.app.usage.UsageStatsManager;
+
+/**
+ * A handy calendar object that knows nothing of Locale's or TimeZones. This simplifies
+ * interval book-keeping. It is *NOT* meant to be used as a user-facing calendar, as it has
+ * no concept of Locale or TimeZone.
+ */
+public class UnixCalendar {
+ private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000;
+ private static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
+ private static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
+ private static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
+ private long mTime;
+
+ public UnixCalendar(long time) {
+ mTime = time;
+ }
+
+ public void truncateToDay() {
+ mTime -= mTime % DAY_IN_MILLIS;
+ }
+
+ public void truncateToWeek() {
+ mTime -= mTime % WEEK_IN_MILLIS;
+ }
+
+ public void truncateToMonth() {
+ mTime -= mTime % MONTH_IN_MILLIS;
+ }
+
+ public void truncateToYear() {
+ mTime -= mTime % YEAR_IN_MILLIS;
+ }
+
+ public void addDays(int val) {
+ mTime += val * DAY_IN_MILLIS;
+ }
+
+ public void addWeeks(int val) {
+ mTime += val * WEEK_IN_MILLIS;
+ }
+
+ public void addMonths(int val) {
+ mTime += val * MONTH_IN_MILLIS;
+ }
+
+ public void addYears(int val) {
+ mTime += val * YEAR_IN_MILLIS;
+ }
+
+ public void setTimeInMillis(long time) {
+ mTime = time;
+ }
+
+ public long getTimeInMillis() {
+ return mTime;
+ }
+
+ public static void truncateTo(UnixCalendar calendar, int intervalType) {
+ switch (intervalType) {
+ case UsageStatsManager.INTERVAL_YEARLY:
+ calendar.truncateToYear();
+ break;
+
+ case UsageStatsManager.INTERVAL_MONTHLY:
+ calendar.truncateToMonth();
+ break;
+
+ case UsageStatsManager.INTERVAL_WEEKLY:
+ calendar.truncateToWeek();
+ break;
+
+ case UsageStatsManager.INTERVAL_DAILY:
+ calendar.truncateToDay();
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Can't truncate date to interval " +
+ intervalType);
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 37340a4..62a7ec0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -21,24 +21,30 @@
import android.util.AtomicFile;
import android.util.Slog;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.List;
/**
* Provides an interface to query for UsageStat data from an XML database.
*/
class UsageStatsDatabase {
+ private static final int CURRENT_VERSION = 2;
+
private static final String TAG = "UsageStatsDatabase";
private static final boolean DEBUG = UsageStatsService.DEBUG;
private final Object mLock = new Object();
private final File[] mIntervalDirs;
private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
- private final Calendar mCal;
+ private final UnixCalendar mCal;
+ private final File mVersionFile;
public UsageStatsDatabase(File dir) {
mIntervalDirs = new File[] {
@@ -47,8 +53,9 @@
new File(dir, "monthly"),
new File(dir, "yearly"),
};
+ mVersionFile = new File(dir, "version");
mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
- mCal = Calendar.getInstance();
+ mCal = new UnixCalendar(0);
}
/**
@@ -64,6 +71,8 @@
}
}
+ checkVersionLocked();
+
final FilenameFilter backupFileFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@@ -81,7 +90,45 @@
}
for (File f : files) {
- mSortedStatFiles[i].put(Long.parseLong(f.getName()), new AtomicFile(f));
+ final AtomicFile af = new AtomicFile(f);
+ mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkVersionLocked() {
+ int version;
+ try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) {
+ version = Integer.parseInt(reader.readLine());
+ } catch (NumberFormatException | IOException e) {
+ version = 0;
+ }
+
+ if (version != CURRENT_VERSION) {
+ Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION);
+ doUpgradeLocked(version);
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
+ writer.write(Integer.toString(CURRENT_VERSION));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write new version");
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void doUpgradeLocked(int thisVersion) {
+ if (thisVersion < 2) {
+ // Delete all files if we are version 0. This is a pre-release version,
+ // so this is fine.
+ Slog.i(TAG, "Deleting all usage stats files");
+ for (int i = 0; i < mIntervalDirs.length; i++) {
+ File[] files = mIntervalDirs[i].listFiles();
+ if (files != null) {
+ for (File f : files) {
+ f.delete();
}
}
}
@@ -161,25 +208,48 @@
throw new IllegalArgumentException("Bad interval type " + intervalType);
}
- if (endTime < beginTime) {
+ final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType];
+
+ if (endTime <= beginTime) {
+ if (DEBUG) {
+ Slog.d(TAG, "endTime(" + endTime + ") <= beginTime(" + beginTime + ")");
+ }
return null;
}
- final int startIndex = mSortedStatFiles[intervalType].closestIndexOnOrBefore(beginTime);
+ int startIndex = intervalStats.closestIndexOnOrBefore(beginTime);
if (startIndex < 0) {
+ // All the stats available have timestamps after beginTime, which means they all
+ // match.
+ startIndex = 0;
+ }
+
+ int endIndex = intervalStats.closestIndexOnOrBefore(endTime);
+ if (endIndex < 0) {
+ // All the stats start after this range ends, so nothing matches.
+ if (DEBUG) {
+ Slog.d(TAG, "No results for this range. All stats start after.");
+ }
return null;
}
- int endIndex = mSortedStatFiles[intervalType].closestIndexOnOrAfter(endTime);
- if (endIndex < 0) {
- endIndex = mSortedStatFiles[intervalType].size() - 1;
+ if (intervalStats.keyAt(endIndex) == endTime) {
+ // The endTime is exclusive, so if we matched exactly take the one before.
+ endIndex--;
+ if (endIndex < 0) {
+ // All the stats start after this range ends, so nothing matches.
+ if (DEBUG) {
+ Slog.d(TAG, "No results for this range. All stats start after.");
+ }
+ return null;
+ }
}
try {
IntervalStats stats = new IntervalStats();
ArrayList<T> results = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
- final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i);
+ final AtomicFile f = intervalStats.valueAt(i);
if (DEBUG) {
Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
@@ -230,22 +300,22 @@
synchronized (mLock) {
long timeNow = System.currentTimeMillis();
mCal.setTimeInMillis(timeNow);
- mCal.add(Calendar.YEAR, -3);
+ mCal.addYears(-3);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY],
mCal.getTimeInMillis());
mCal.setTimeInMillis(timeNow);
- mCal.add(Calendar.MONTH, -6);
+ mCal.addMonths(-6);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY],
mCal.getTimeInMillis());
mCal.setTimeInMillis(timeNow);
- mCal.add(Calendar.WEEK_OF_YEAR, -4);
+ mCal.addWeeks(-4);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY],
mCal.getTimeInMillis());
mCal.setTimeInMillis(timeNow);
- mCal.add(Calendar.DAY_OF_YEAR, -7);
+ mCal.addDays(-7);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
mCal.getTimeInMillis());
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e77bf86..2dcdcc4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -38,6 +38,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -62,9 +63,7 @@
static final boolean DEBUG = false;
private static final long TEN_SECONDS = 10 * 1000;
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
- private static final long TWO_MINUTES = 2 * 60 * 1000;
private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
- private static final long END_TIME_DELAY = DEBUG ? 0 : TWO_MINUTES;
// Handler message types.
static final int MSG_REPORT_EVENT = 0;
@@ -78,6 +77,8 @@
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
private File mUsageStatsDir;
+ long mRealTimeSnapshot;
+ long mSystemTimeSnapshot;
public UsageStatsService(Context context) {
super(context);
@@ -104,6 +105,9 @@
cleanUpRemovedUsersLocked();
}
+ mRealTimeSnapshot = SystemClock.elapsedRealtime();
+ mSystemTimeSnapshot = System.currentTimeMillis();
+
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
@@ -178,7 +182,7 @@
}
/**
- * Called by the Bunder stub
+ * Called by the Binder stub
*/
void shutdown() {
synchronized (mLock) {
@@ -382,6 +386,14 @@
*/
private class LocalService extends UsageStatsManagerInternal {
+ /**
+ * The system may have its time change, so at least make sure the events
+ * are monotonic in order.
+ */
+ private long computeMonotonicSystemTime(long realTime) {
+ return (realTime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+ }
+
@Override
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
@@ -392,7 +404,7 @@
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
- event.mTimeStamp = System.currentTimeMillis();
+ event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -406,7 +418,7 @@
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
- event.mTimeStamp = System.currentTimeMillis();
+ event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsUtils.java b/services/usage/java/com/android/server/usage/UsageStatsUtils.java
deleted file mode 100644
index dd5f3b9..0000000
--- a/services/usage/java/com/android/server/usage/UsageStatsUtils.java
+++ /dev/null
@@ -1,63 +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.server.usage;
-
-import android.app.usage.UsageStatsManager;
-
-import java.util.Calendar;
-
-/**
- * A collection of utility methods used by the UsageStatsService and accompanying classes.
- */
-final class UsageStatsUtils {
- private UsageStatsUtils() {}
-
- /**
- * Truncates the date to the given UsageStats bucket. For example, if the bucket is
- * {@link UsageStatsManager#INTERVAL_YEARLY}, the date is truncated to the 1st day of the year,
- * with the time set to 00:00:00.
- *
- * @param bucket The UsageStats bucket to truncate to.
- * @param cal The date to truncate.
- */
- public static void truncateDateTo(int bucket, Calendar cal) {
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
-
- switch (bucket) {
- case UsageStatsManager.INTERVAL_YEARLY:
- cal.set(Calendar.DAY_OF_YEAR, 0);
- break;
-
- case UsageStatsManager.INTERVAL_MONTHLY:
- cal.set(Calendar.DAY_OF_MONTH, 0);
- break;
-
- case UsageStatsManager.INTERVAL_WEEKLY:
- cal.set(Calendar.DAY_OF_WEEK, 0);
- break;
-
- case UsageStatsManager.INTERVAL_DAILY:
- break;
-
- default:
- throw new UnsupportedOperationException("Can't truncate date to bucket " + bucket);
- }
- }
-}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
index 48881d0..9ce6d63 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXml.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -37,10 +37,15 @@
private static final String USAGESTATS_TAG = "usagestats";
private static final String VERSION_ATTR = "version";
+ public static long parseBeginTime(AtomicFile file) {
+ return Long.parseLong(file.getBaseFile().getName());
+ }
+
public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
try {
FileInputStream in = file.openRead();
try {
+ statsOut.beginTime = parseBeginTime(file);
read(in, statsOut);
statsOut.lastTimeSaved = file.getLastModifiedTime();
} finally {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 6529950..ef95a7b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -15,7 +15,6 @@
*/
package com.android.server.usage;
-import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -26,44 +25,52 @@
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
-import android.content.ComponentName;
import android.content.res.Configuration;
import java.io.IOException;
import java.net.ProtocolException;
-import java.util.Locale;
/**
* UsageStats reader/writer for version 1 of the XML format.
*/
final class UsageStatsXmlV1 {
+ private static final String PACKAGES_TAG = "packages";
private static final String PACKAGE_TAG = "package";
- private static final String CONFIGURATION_TAG = "config";
- private static final String EVENT_LOG_TAG = "event-log";
- private static final String BEGIN_TIME_ATTR = "beginTime";
- private static final String END_TIME_ATTR = "endTime";
- private static final String NAME_ATTR = "name";
+ private static final String CONFIGURATIONS_TAG = "configurations";
+ private static final String CONFIG_TAG = "config";
+
+ private static final String EVENT_LOG_TAG = "event-log";
+ private static final String EVENT_TAG = "event";
+
+ // Attributes
private static final String PACKAGE_ATTR = "package";
private static final String CLASS_ATTR = "class";
- private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive";
- private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+ private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
private static final String COUNT_ATTR = "count";
private static final String ACTIVE_ATTR = "active";
private static final String LAST_EVENT_ATTR = "lastEvent";
private static final String TYPE_ATTR = "type";
+
+ // Time attributes stored as an offset of the beginTime.
+ private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+ private static final String END_TIME_ATTR = "endTime";
private static final String TIME_ATTR = "time";
private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- final String name = parser.getAttributeValue(null, NAME_ATTR);
- if (name == null) {
- throw new ProtocolException("no " + NAME_ATTR + " attribute present");
+ final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
+ if (pkg == null) {
+ throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
}
- UsageStats stats = statsOut.getOrCreateUsageStats(name);
+ final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);
+
+ // Apply the offset to the beginTime to find the absolute time.
+ stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
+ parser, LAST_TIME_ACTIVE_ATTR);
+
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
- stats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
}
@@ -72,8 +79,12 @@
final Configuration config = new Configuration();
Configuration.readXmlAttrs(parser, config);
- ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
- configStats.mLastTimeActive = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
+ final ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
+
+ // Apply the offset to the beginTime to find the absolute time.
+ configStats.mLastTimeActive = statsOut.beginTime + XmlUtils.readLongAttribute(
+ parser, LAST_TIME_ACTIVE_ATTR);
+
configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR);
if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) {
@@ -83,30 +94,19 @@
private static void loadEvent(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
- String className;
+ final String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
if (packageName == null) {
- // Try getting the component name if it exists.
- final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR);
- if (componentName == null) {
- throw new ProtocolException("no " + NAME_ATTR + " or " + PACKAGE_ATTR +
- " attribute present");
- }
- ComponentName component = ComponentName.unflattenFromString(componentName);
- if (component == null) {
- throw new ProtocolException("ComponentName " + componentName + " is invalid");
- }
-
- packageName = component.getPackageName();
- className = component.getClassName();
- } else {
- className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
+ throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
}
- UsageEvents.Event event = statsOut.buildEvent(packageName, className);
- event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
- event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR);
+ final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
+ final UsageEvents.Event event = statsOut.buildEvent(packageName, className);
+
+ // Apply the offset to the beginTime to find the absolute time of this event.
+ event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
+
+ event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
event.mConfiguration = new Configuration();
Configuration.readXmlAttrs(parser, event.mConfiguration);
@@ -118,48 +118,60 @@
statsOut.events.put(event.mTimeStamp, event);
}
- private static void writeUsageStats(XmlSerializer xml, final UsageStats stats)
- throws IOException {
+ private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
+ final UsageStats usageStats) throws IOException {
xml.startTag(null, PACKAGE_TAG);
- XmlUtils.writeStringAttribute(xml, NAME_ATTR, stats.mPackageName);
- XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeInForeground);
- XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeUsed);
- XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, stats.mLastEvent);
+
+ // Write the time offset.
+ XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+ usageStats.mLastTimeUsed - stats.beginTime);
+
+ XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
+ XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
+ XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
+
xml.endTag(null, PACKAGE_TAG);
}
- private static void writeConfigStats(XmlSerializer xml, final ConfigurationStats stats,
- boolean isActive) throws IOException {
- xml.startTag(null, CONFIGURATION_TAG);
- XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeActive);
- XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeActive);
- XmlUtils.writeIntAttribute(xml, COUNT_ATTR, stats.mActivationCount);
+ private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats,
+ final ConfigurationStats configStats, boolean isActive) throws IOException {
+ xml.startTag(null, CONFIG_TAG);
+
+ // Write the time offset.
+ XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+ configStats.mLastTimeActive - stats.beginTime);
+
+ XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive);
+ XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount);
if (isActive) {
XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
}
// Now write the attributes representing the configuration object.
- Configuration.writeXmlAttrs(xml, stats.mConfiguration);
+ Configuration.writeXmlAttrs(xml, configStats.mConfiguration);
- xml.endTag(null, CONFIGURATION_TAG);
+ xml.endTag(null, CONFIG_TAG);
}
- private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event)
- throws IOException {
- xml.startTag(null, EVENT_LOG_TAG);
+ private static void writeEvent(XmlSerializer xml, final IntervalStats stats,
+ final UsageEvents.Event event) throws IOException {
+ xml.startTag(null, EVENT_TAG);
+
+ // Store the time offset.
+ XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime);
+
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
if (event.mClass != null) {
XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
}
XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
- XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp);
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
&& event.mConfiguration != null) {
Configuration.writeXmlAttrs(xml, event.mConfiguration);
}
- xml.endTag(null, EVENT_LOG_TAG);
+ xml.endTag(null, EVENT_TAG);
}
/**
@@ -171,7 +183,7 @@
*/
public static void read(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- statsOut.stats.clear();
+ statsOut.packageStats.clear();
statsOut.configurations.clear();
statsOut.activeConfiguration = null;
@@ -179,7 +191,6 @@
statsOut.events.clear();
}
- statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
int eventCode;
@@ -196,11 +207,11 @@
loadUsageStats(parser, statsOut);
break;
- case CONFIGURATION_TAG:
+ case CONFIG_TAG:
loadConfigStats(parser, statsOut);
break;
- case EVENT_LOG_TAG:
+ case EVENT_TAG:
loadEvent(parser, statsOut);
break;
}
@@ -208,32 +219,38 @@
}
/**
- * Writes the stats object to an XML file. The {@link FastXmlSerializer}
+ * Writes the stats object to an XML file. The {@link XmlSerializer}
* has already written the <code><usagestats></code> tag, but attributes may still
* be added.
*
- * @param serializer The serializer to which to write the stats data.
+ * @param xml The serializer to which to write the packageStats data.
* @param stats The stats object to write to the XML file.
*/
- public static void write(FastXmlSerializer serializer, IntervalStats stats) throws IOException {
- serializer.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.beginTime));
- serializer.attribute(null, END_TIME_ATTR, Long.toString(stats.endTime));
+ public static void write(XmlSerializer xml, IntervalStats stats) throws IOException {
+ XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime);
- final int statsCount = stats.stats.size();
+ xml.startTag(null, PACKAGES_TAG);
+ final int statsCount = stats.packageStats.size();
for (int i = 0; i < statsCount; i++) {
- writeUsageStats(serializer, stats.stats.valueAt(i));
+ writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
}
+ xml.endTag(null, PACKAGES_TAG);
+
+ xml.startTag(null, CONFIGURATIONS_TAG);
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
- writeConfigStats(serializer, stats.configurations.valueAt(i), active);
+ writeConfigStats(xml, stats, stats.configurations.valueAt(i), active);
}
+ xml.endTag(null, CONFIGURATIONS_TAG);
+ xml.startTag(null, EVENT_LOG_TAG);
final int eventCount = stats.events != null ? stats.events.size() : 0;
for (int i = 0; i < eventCount; i++) {
- writeEvent(serializer, stats.events.valueAt(i));
+ writeEvent(xml, stats, stats.events.valueAt(i));
}
+ xml.endTag(null, EVENT_LOG_TAG);
}
private UsageStatsXmlV1() {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 7142a99..2769666 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -32,7 +32,6 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
/**
@@ -47,7 +46,7 @@
private final UsageStatsDatabase mDatabase;
private final IntervalStats[] mCurrentStats;
private boolean mStatsChanged = false;
- private final Calendar mDailyExpiryDate;
+ private final UnixCalendar mDailyExpiryDate;
private final StatsUpdatedListener mListener;
private final String mLogPrefix;
@@ -56,7 +55,7 @@
}
UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
- mDailyExpiryDate = Calendar.getInstance();
+ mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
mListener = listener;
@@ -66,6 +65,7 @@
void init() {
mDatabase.init();
+ final long timeNow = System.currentTimeMillis();
int nullCount = 0;
for (int i = 0; i < mCurrentStats.length; i++) {
mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
@@ -73,6 +73,11 @@
// Find out how many intervals we don't have data for.
// Ideally it should be all or none.
nullCount++;
+ } else if (mCurrentStats[i].beginTime > timeNow) {
+ Slog.e(TAG, mLogPrefix + "Interval " + i + " has stat in the future " +
+ mCurrentStats[i].beginTime);
+ mCurrentStats[i] = null;
+ nullCount++;
}
}
@@ -94,17 +99,18 @@
// that is reported.
mDailyExpiryDate.setTimeInMillis(
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
- mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
- UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
- Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
- + sDateFormat.format(mDailyExpiryDate.getTime()));
+ mDailyExpiryDate.addDays(1);
+ mDailyExpiryDate.truncateToDay();
+ Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
+ sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
+ "(" + mDailyExpiryDate.getTimeInMillis() + ")");
}
// Now close off any events that were open at the time this was saved.
for (IntervalStats stat : mCurrentStats) {
- final int pkgCount = stat.stats.size();
+ final int pkgCount = stat.packageStats.size();
for (int i = 0; i < pkgCount; i++) {
- UsageStats pkgStats = stat.stats.valueAt(i);
+ UsageStats pkgStats = stat.packageStats.valueAt(i);
if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
@@ -162,13 +168,13 @@
public void combine(IntervalStats stats, boolean mutable,
List<UsageStats> accResult) {
if (!mutable) {
- accResult.addAll(stats.stats.values());
+ accResult.addAll(stats.packageStats.values());
return;
}
- final int statCount = stats.stats.size();
+ final int statCount = stats.packageStats.size();
for (int i = 0; i < statCount; i++) {
- accResult.add(new UsageStats(stats.stats.valueAt(i)));
+ accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
}
}
};
@@ -195,49 +201,68 @@
* and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
* provided to select the stats to use from the IntervalStats object.
*/
- private <T> List<T> queryStats(int bucketType, long beginTime, long endTime,
+ private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
StatCombiner<T> combiner) {
- if (bucketType == UsageStatsManager.INTERVAL_BEST) {
- bucketType = mDatabase.findBestFitBucket(beginTime, endTime);
+ if (intervalType == UsageStatsManager.INTERVAL_BEST) {
+ intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
+ if (intervalType < 0) {
+ // Nothing saved to disk yet, so every stat is just as equal (no rollover has
+ // occurred.
+ intervalType = UsageStatsManager.INTERVAL_DAILY;
+ }
}
- if (bucketType < 0 || bucketType >= mCurrentStats.length) {
+ if (intervalType < 0 || intervalType >= mCurrentStats.length) {
if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "Bad bucketType used " + bucketType);
+ Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
}
return null;
}
- if (beginTime >= mCurrentStats[bucketType].endTime) {
- if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
- + mCurrentStats[bucketType].endTime);
- }
- // Nothing newer available.
- return null;
-
- } else if (beginTime >= mCurrentStats[bucketType].beginTime) {
- if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType);
- }
- // Fast path for retrieving in-memory state.
- ArrayList<T> results = new ArrayList<>();
- combiner.combine(mCurrentStats[bucketType], true, results);
- return results;
- }
-
- // Flush any changes that were made to disk before we do a disk query.
- // If we're not grabbing the ongoing stats, no need to persist.
- persistActiveStats();
+ final IntervalStats currentStats = mCurrentStats[intervalType];
if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= "
+ Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
+ beginTime + " AND endTime < " + endTime);
}
- final List<T> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime, combiner);
+ if (beginTime >= currentStats.endTime) {
+ if (DEBUG) {
+ Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
+ + currentStats.endTime);
+ }
+ // Nothing newer available.
+ return null;
+ }
+
+ // Truncate the endTime to just before the in-memory stats. Then, we'll append the
+ // in-memory stats to the results (if necessary) so as to avoid writing to disk too
+ // often.
+ final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
+
+ // Get the stats from disk.
+ List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
+ truncatedEndTime, combiner);
if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "Results: " + (results == null ? 0 : results.size()));
+ Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
+ Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
+ " endTime=" + currentStats.endTime);
+ }
+
+ // Now check if the in-memory stats match the range and add them if they do.
+ if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
+ if (DEBUG) {
+ Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
+ }
+
+ if (results == null) {
+ results = new ArrayList<>();
+ }
+ combiner.combine(currentStats, true, results);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
}
return results;
}
@@ -250,44 +275,45 @@
return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
}
- UsageEvents queryEvents(long beginTime, long endTime) {
- if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) {
- if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) {
- return null;
- }
+ UsageEvents queryEvents(final long beginTime, final long endTime) {
+ final ArraySet<String> names = new ArraySet<>();
+ List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
+ beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
+ @Override
+ public void combine(IntervalStats stats, boolean mutable,
+ List<UsageEvents.Event> accumulatedResult) {
+ if (stats.events == null) {
+ return;
+ }
- TimeSparseArray<UsageEvents.Event> events =
- mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events;
- if (events == null) {
- return null;
- }
+ final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
+ if (startIndex < 0) {
+ return;
+ }
- final int startIndex = events.closestIndexOnOrAfter(beginTime);
- if (startIndex < 0) {
- return null;
- }
+ final int size = stats.events.size();
+ for (int i = startIndex; i < size; i++) {
+ if (stats.events.keyAt(i) >= endTime) {
+ return;
+ }
- ArraySet<String> names = new ArraySet<>();
- ArrayList<UsageEvents.Event> results = new ArrayList<>();
- final int size = events.size();
- for (int i = startIndex; i < size; i++) {
- if (events.keyAt(i) >= endTime) {
- break;
- }
- final UsageEvents.Event event = events.valueAt(i);
- names.add(event.mPackage);
- if (event.mClass != null) {
- names.add(event.mClass);
- }
- results.add(event);
- }
- String[] table = names.toArray(new String[names.size()]);
- Arrays.sort(table);
- return new UsageEvents(results, table);
+ final UsageEvents.Event event = stats.events.valueAt(i);
+ names.add(event.mPackage);
+ if (event.mClass != null) {
+ names.add(event.mClass);
+ }
+ accumulatedResult.add(event);
+ }
+ }
+ });
+
+ if (results == null || results.isEmpty()) {
+ return null;
}
- // TODO(adamlesinski): Query the previous days.
- return null;
+ String[] table = names.toArray(new String[names.size()]);
+ Arrays.sort(table);
+ return new UsageEvents(results, table);
}
void persistActiveStats() {
@@ -314,9 +340,9 @@
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
ArraySet<String> continuePreviousDay = new ArraySet<>();
for (IntervalStats stat : mCurrentStats) {
- final int pkgCount = stat.stats.size();
+ final int pkgCount = stat.packageStats.size();
for (int i = 0; i < pkgCount; i++) {
- UsageStats pkgStats = stat.stats.valueAt(i);
+ UsageStats pkgStats = stat.packageStats.valueAt(i);
if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
continuePreviousDay.add(pkgStats.mPackageName);
@@ -360,54 +386,53 @@
private void loadActiveStats() {
final long timeNow = System.currentTimeMillis();
- Calendar tempCal = mDailyExpiryDate;
- for (int bucketType = 0; bucketType < mCurrentStats.length; bucketType++) {
+ final UnixCalendar tempCal = mDailyExpiryDate;
+ for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
tempCal.setTimeInMillis(timeNow);
- UsageStatsUtils.truncateDateTo(bucketType, tempCal);
+ UnixCalendar.truncateTo(tempCal, intervalType);
- if (mCurrentStats[bucketType] != null &&
- mCurrentStats[bucketType].beginTime == tempCal.getTimeInMillis()) {
+ if (mCurrentStats[intervalType] != null &&
+ mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
// These are the same, no need to load them (in memory stats are always newer
// than persisted stats).
continue;
}
- final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(bucketType);
- if (lastBeginTime >= tempCal.getTimeInMillis()) {
+ final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
+ if (lastBeginTime > timeNow) {
+ Slog.e(TAG, mLogPrefix + "Latest usage stats for interval " +
+ intervalType + " begins in the future");
+ mCurrentStats[intervalType] = null;
+ } else if (lastBeginTime >= tempCal.getTimeInMillis()) {
if (DEBUG) {
- Slog.d(TAG, mLogPrefix + "Loading existing stats (" + lastBeginTime +
- ") for bucket " + bucketType);
+ Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
+ sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
+ ") for interval " + intervalType);
}
- mCurrentStats[bucketType] = mDatabase.getLatestUsageStats(bucketType);
- if (DEBUG) {
- if (mCurrentStats[bucketType] != null) {
- Slog.d(TAG, mLogPrefix + "Found " +
- (mCurrentStats[bucketType].events == null ?
- 0 : mCurrentStats[bucketType].events.size()) +
- " events");
- }
- }
+ mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
} else {
- mCurrentStats[bucketType] = null;
+ mCurrentStats[intervalType] = null;
}
- if (mCurrentStats[bucketType] == null) {
+ if (mCurrentStats[intervalType] == null) {
if (DEBUG) {
- Slog.d(TAG, "Creating new stats (" + tempCal.getTimeInMillis() +
- ") for bucket " + bucketType);
+ Slog.d(TAG, "Creating new stats @ " +
+ sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
+ tempCal.getTimeInMillis() + ") for interval " + intervalType);
}
- mCurrentStats[bucketType] = new IntervalStats();
- mCurrentStats[bucketType].beginTime = tempCal.getTimeInMillis();
- mCurrentStats[bucketType].endTime = timeNow;
+ mCurrentStats[intervalType] = new IntervalStats();
+ mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
+ mCurrentStats[intervalType].endTime = timeNow;
}
}
mStatsChanged = false;
mDailyExpiryDate.setTimeInMillis(timeNow);
- mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
- UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
- Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
- + sDateFormat.format(mDailyExpiryDate.getTime()));
+ mDailyExpiryDate.addDays(1);
+ mDailyExpiryDate.truncateToDay();
+ Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
+ sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
+ tempCal.getTimeInMillis() + ")");
}
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
index 5bf0855..c3aa2a0 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -517,6 +517,20 @@
}
/**
+ * Merges the calls within this conference. See {@link PhoneCapabilities#MERGE_CONFERENCE}.
+ */
+ public void mergeConference() {
+ mInCallAdapter.mergeConference(mTelecommCallId);
+ }
+
+ /**
+ * Swaps the calls within this conference. See {@link PhoneCapabilities#SWAP_CONFERENCE}.
+ */
+ public void swapConference() {
+ mInCallAdapter.swapConference(mTelecommCallId);
+ }
+
+ /**
* Obtains the parent of this {@code Call} in a conference, if any.
*
* @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
diff --git a/telecomm/java/android/telecomm/Conference.java b/telecomm/java/android/telecomm/Conference.java
index 879ff66..c838b48 100644
--- a/telecomm/java/android/telecomm/Conference.java
+++ b/telecomm/java/android/telecomm/Conference.java
@@ -93,6 +93,18 @@
public void onUnhold() {}
/**
+ * Invoked when the child calls should be merged. Only invoked if the conference contains the
+ * capability {@link PhoneCapabilities#MERGE_CONFERENCE}.
+ */
+ public void onMerge() {}
+
+ /**
+ * Invoked when the child calls should be swapped. Only invoked if the conference contains the
+ * capability {@link PhoneCapabilities#SWAP_CONFERENCE}.
+ */
+ public void onSwap() {}
+
+ /**
* Sets state to be on hold.
*/
public final void setOnHold() {
@@ -141,7 +153,7 @@
* @param connection The connection to add.
* @return True if the connection was successfully added.
*/
- public boolean addConnection(Connection connection) {
+ public final boolean addConnection(Connection connection) {
if (connection != null && !mChildConnections.contains(connection)) {
if (connection.setConference(this)) {
mChildConnections.add(connection);
@@ -160,7 +172,7 @@
* @param connection The connection to remove.
* @return True if the connection was successfully removed.
*/
- public void removeConnection(Connection connection) {
+ public final void removeConnection(Connection connection) {
Log.d(this, "removing %s from %s", connection, mChildConnections);
if (connection != null && mChildConnections.remove(connection)) {
connection.resetConference();
@@ -173,7 +185,7 @@
/**
* Tears down the conference object and any of it's current connections.
*/
- public void destroy() {
+ public final void destroy() {
Log.d(this, "destroying conference : %s", this);
// Tear down the children.
for (Connection connection : mChildConnections) {
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 4d6e267..6df117e 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -1068,10 +1068,10 @@
if (mState != state) {
Log.d(this, "setState: %s", stateToString(state));
mState = state;
+ onSetState(state);
for (Listener l : mListeners) {
l.onStateChanged(this, state);
}
- onSetState(state);
}
}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 2dc6910..833aa26 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -72,6 +72,8 @@
private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 15;
private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
private static final int MSG_ANSWER_VIDEO = 17;
+ private static final int MSG_MERGE_CONFERENCE = 18;
+ private static final int MSG_SWAP_CONFERENCE = 19;
private static Connection sNullConnection;
@@ -182,6 +184,16 @@
}
@Override
+ public void mergeConference(String callId) {
+ mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
+ }
+
+ @Override
+ public void swapConference(String callId) {
+ mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
@@ -298,6 +310,12 @@
case MSG_SPLIT_FROM_CONFERENCE:
splitFromConference((String) msg.obj);
break;
+ case MSG_MERGE_CONFERENCE:
+ mergeConference((String) msg.obj);
+ break;
+ case MSG_SWAP_CONFERENCE:
+ swapConference((String) msg.obj);
+ break;
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -639,6 +657,22 @@
}
}
+ private void mergeConference(String callId) {
+ Log.d(this, "mergeConference(%s)", callId);
+ Conference conference = findConferenceForAction(callId, "mergeConference");
+ if (conference != null) {
+ conference.onMerge();
+ }
+ }
+
+ private void swapConference(String callId) {
+ Log.d(this, "swapConference(%s)", callId);
+ Conference conference = findConferenceForAction(callId, "swapConference");
+ if (conference != null) {
+ conference.onSwap();
+ }
+ }
+
private void onPostDialContinue(String callId, boolean proceed) {
Log.d(this, "onPostDialContinue(%s)", callId);
findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index 80f7b57..96ea5a6 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -241,6 +241,26 @@
}
/**
+ * Instructs Telecomm to merge child calls of the specified conference call.
+ */
+ public void mergeConference(String callId) {
+ try {
+ mAdapter.mergeConference(callId);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Instructs Telecomm to swap the child calls of the specified conference call.
+ */
+ public void swapConference(String callId) {
+ try {
+ mAdapter.swapConference(callId);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
* Instructs Telecomm to turn the proximity sensor on.
*/
public void turnProximitySensorOn() {
diff --git a/telecomm/java/android/telecomm/PhoneCapabilities.java b/telecomm/java/android/telecomm/PhoneCapabilities.java
index 3d76608..7a338b4 100644
--- a/telecomm/java/android/telecomm/PhoneCapabilities.java
+++ b/telecomm/java/android/telecomm/PhoneCapabilities.java
@@ -27,11 +27,17 @@
/** Call supports the hold feature. */
public static final int SUPPORT_HOLD = 0x00000002;
- /** Call can currently be merged. */
- public static final int MERGE_CALLS = 0x00000004;
+ /**
+ * Calls within a conference can be merged. Some connection services create a conference call
+ * only after two calls have been merged. However, a conference call can also be added the
+ * moment there are more than one call. CDMA calls are implemented in this way because the call
+ * actions are more limited when more than one call exists. This flag allows merge to be exposed
+ * as a capability on the conference call instead of individual calls.
+ */
+ public static final int MERGE_CONFERENCE = 0x00000004;
- /** Call can currently be swapped with another call. */
- public static final int SWAP_CALLS = 0x00000008;
+ /** Calls withing a conference can be swapped between foreground and background. */
+ public static final int SWAP_CONFERENCE = 0x00000008;
/** Call currently supports adding another call to this one. */
public static final int ADD_CALL = 0x00000010;
@@ -42,8 +48,11 @@
/** Call can be muted. */
public static final int MUTE = 0x00000040;
- /** Call supports generic conference mode. */
- public static final int GENERIC_CONFERENCE = 0x00000080;
+ /**
+ * Call supports conference call management. This capability only applies to conference calls
+ * which can have other calls as children.
+ */
+ public static final int MANAGE_CONFERENCE = 0x00000080;
/**
* Local device supports video telephony.
@@ -69,8 +78,8 @@
*/
public static final int VoWIFI = 0x00000800;
- public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
- | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
+ public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CONFERENCE | SWAP_CONFERENCE | ADD_CALL
+ | RESPOND_VIA_TEXT | MUTE | MANAGE_CONFERENCE;
public static String toString(int capabilities) {
StringBuilder builder = new StringBuilder();
@@ -81,11 +90,11 @@
if ((capabilities & SUPPORT_HOLD) != 0) {
builder.append(" SUPPORT_HOLD");
}
- if ((capabilities & MERGE_CALLS) != 0) {
- builder.append(" MERGE_CALLS");
+ if ((capabilities & MERGE_CONFERENCE) != 0) {
+ builder.append(" MERGE_CONFERENCE");
}
- if ((capabilities & SWAP_CALLS) != 0) {
- builder.append(" SWAP_CALLS");
+ if ((capabilities & SWAP_CONFERENCE) != 0) {
+ builder.append(" SWAP_CONFERENCE");
}
if ((capabilities & ADD_CALL) != 0) {
builder.append(" ADD_CALL");
@@ -96,8 +105,8 @@
if ((capabilities & MUTE) != 0) {
builder.append(" MUTE");
}
- if ((capabilities & GENERIC_CONFERENCE) != 0) {
- builder.append(" GENERIC_CONFERENCE");
+ if ((capabilities & MANAGE_CONFERENCE) != 0) {
+ builder.append(" MANAGE_CONFERENCE");
}
if ((capabilities & SUPPORTS_VT_LOCAL) != 0) {
builder.append(" SUPPORTS_VT_LOCAL");
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index 32b877d..a673733 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -65,6 +65,10 @@
void splitFromConference(String callId);
+ void mergeConference(String conferenceCallId);
+
+ void swapConference(String conferenceCallId);
+
void onPostDialContinue(String callId, boolean proceed);
void onPhoneAccountClicked(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 2ce5c6b..626bb91 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -54,6 +54,10 @@
void splitFromConference(String callId);
+ void mergeConference(String callId);
+
+ void swapConference(String callId);
+
void turnOnProximitySensor();
void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 8af5e98..1d9e6a9 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -38,8 +38,8 @@
import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING;
import java.util.Locale;
import java.util.regex.Matcher;
@@ -2215,81 +2215,79 @@
cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
String retStr = dialStr;
+ boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP);
+
// Checks if the plus sign character is in the passed-in dial string
if (dialStr != null &&
dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
- // Format the string based on the rules for the country the number is from,
- // and the current country the phone is camped on.
- if ((currFormat == defaultFormat) && (currFormat == FORMAT_NANP)) {
- // Handle case where default and current telephone numbering plans are NANP.
- String postDialStr = null;
- String tempDialStr = dialStr;
- // Sets the retStr to null since the conversion will be performed below.
- retStr = null;
- if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
- // This routine is to process the plus sign in the dial string by loop through
- // the network portion, post dial portion 1, post dial portion 2... etc. if
- // applied
- do {
- String networkDialStr;
+ // Handle case where default and current telephone numbering plans are NANP.
+ String postDialStr = null;
+ String tempDialStr = dialStr;
+
+ // Sets the retStr to null since the conversion will be performed below.
+ retStr = null;
+ if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
+ // This routine is to process the plus sign in the dial string by loop through
+ // the network portion, post dial portion 1, post dial portion 2... etc. if
+ // applied
+ do {
+ String networkDialStr;
+ // Format the string based on the rules for the country the number is from,
+ // and the current country the phone is camped
+ if (useNanp) {
networkDialStr = extractNetworkPortion(tempDialStr);
- // Handles the conversion within NANP
- networkDialStr = processPlusCodeWithinNanp(networkDialStr);
+ } else {
+ networkDialStr = extractNetworkPortionAlt(tempDialStr);
- // Concatenates the string that is converted from network portion
- if (!TextUtils.isEmpty(networkDialStr)) {
- if (retStr == null) {
- retStr = networkDialStr;
- } else {
- retStr = retStr.concat(networkDialStr);
- }
+ }
+
+ networkDialStr = processPlusCode(networkDialStr, useNanp);
+
+ // Concatenates the string that is converted from network portion
+ if (!TextUtils.isEmpty(networkDialStr)) {
+ if (retStr == null) {
+ retStr = networkDialStr;
} else {
- // This should never happen since we checked the if dialStr is null
- // and if it contains the plus sign in the beginning of this function.
- // The plus sign is part of the network portion.
- Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
- return dialStr;
+ retStr = retStr.concat(networkDialStr);
}
- postDialStr = extractPostDialPortion(tempDialStr);
- if (!TextUtils.isEmpty(postDialStr)) {
- int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
+ } else {
+ // This should never happen since we checked the if dialStr is null
+ // and if it contains the plus sign in the beginning of this function.
+ // The plus sign is part of the network portion.
+ Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
+ return dialStr;
+ }
+ postDialStr = extractPostDialPortion(tempDialStr);
+ if (!TextUtils.isEmpty(postDialStr)) {
+ int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
- // dialableIndex should always be greater than 0
- if (dialableIndex >= 1) {
- retStr = appendPwCharBackToOrigDialStr(dialableIndex,
- retStr,postDialStr);
- // Skips the P/W character, extracts the dialable portion
- tempDialStr = postDialStr.substring(dialableIndex);
- } else {
- // Non-dialable character such as P/W should not be at the end of
- // the dial string after P/W processing in CdmaConnection.java
- // Set the postDialStr to "" to break out of the loop
- if (dialableIndex < 0) {
- postDialStr = "";
- }
- Rlog.e("wrong postDialStr=", postDialStr);
+ // dialableIndex should always be greater than 0
+ if (dialableIndex >= 1) {
+ retStr = appendPwCharBackToOrigDialStr(dialableIndex,
+ retStr,postDialStr);
+ // Skips the P/W character, extracts the dialable portion
+ tempDialStr = postDialStr.substring(dialableIndex);
+ } else {
+ // Non-dialable character such as P/W should not be at the end of
+ // the dial string after P/W processing in CdmaConnection.java
+ // Set the postDialStr to "" to break out of the loop
+ if (dialableIndex < 0) {
+ postDialStr = "";
}
+ Rlog.e("wrong postDialStr=", postDialStr);
}
- if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
- } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
- } else {
- // TODO: Support NANP international conversion and other telephone numbering plans.
- // Currently the phone is never used in non-NANP system, so return the original
- // dial string.
- Rlog.e("checkAndProcessPlusCode:non-NANP not supported", dialStr);
- }
+ }
+ if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
+ } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
}
return retStr;
- }
+ }
- // This function gets the default international dialing prefix
- private static String getDefaultIdp( ) {
- String ps = null;
- SystemProperties.get(PROPERTY_IDP_STRING, ps);
- if (TextUtils.isEmpty(ps)) {
- ps = NANP_IDP_STRING;
- }
+ private static String getCurrentIdp(boolean useNanp) {
+ // in case, there is no IDD is found, we shouldn't convert it.
+ String ps = SystemProperties.get(
+ PROPERTY_OPERATOR_IDP_STRING, useNanp ? NANP_IDP_STRING : PLUS_SIGN_STRING);
return ps;
}
@@ -2399,31 +2397,32 @@
}
/**
- * This function handles the plus code conversion within NANP CDMA network
+ * This function handles the plus code conversion
* If the number format is
* 1)+1NANP,remove +,
* 2)other than +1NANP, any + numbers,replace + with the current IDP
*/
- private static String processPlusCodeWithinNanp(String networkDialStr) {
+ private static String processPlusCode(String networkDialStr, boolean useNanp) {
String retStr = networkDialStr;
- if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr);
+ if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr
+ + "for NANP = " + useNanp);
// If there is a plus sign at the beginning of the dial string,
// Convert the plus sign to the default IDP since it's an international number
if (networkDialStr != null &&
networkDialStr.charAt(0) == PLUS_SIGN_CHAR &&
networkDialStr.length() > 1) {
String newStr = networkDialStr.substring(1);
- if (isOneNanp(newStr)) {
+ // TODO: for nonNanp, should the '+' be removed if following number is country code
+ if (useNanp && isOneNanp(newStr)) {
// Remove the leading plus sign
retStr = newStr;
- } else {
- String idpStr = getDefaultIdp();
- // Replaces the plus sign with the default IDP
- retStr = networkDialStr.replaceFirst("[+]", idpStr);
+ } else {
+ // Replaces the plus sign with the default IDP
+ retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp));
}
}
- if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr);
+ if (DBG) log("processPlusCode, retStr=" + retStr);
return retStr;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cdee3de..53a7a73 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2511,9 +2511,9 @@
* @param content String containing SAT/USAT response in hexadecimal
* format starting with command tag. See TS 102 223 for
* details.
- * @return The APDU response from the ICC card, with the last 4 bytes
- * being the status word. If the command fails, returns an empty
- * string.
+ * @return The APDU response from the ICC card in hexadecimal format
+ * with the last 4 bytes being the status word. If the command fails,
+ * returns an empty string.
*/
public String sendEnvelopeWithStatus(String content) {
try {
@@ -2903,27 +2903,6 @@
}
/**
- * Get the calculated preferred network type.
- * Used for debugging incorrect network type.
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- *
- * @return the preferred network type, defined in RILConstants.java or -1 if
- * none available.
- */
- public int getCalculatedPreferredNetworkType() {
- try {
- return getITelephony().getCalculatedPreferredNetworkType();
- } catch (RemoteException ex) {
- Rlog.e(TAG, "getCalculatedPreferredNetworkType RemoteException", ex);
- } catch (NullPointerException ex) {
- Rlog.e(TAG, "getCalculatedPreferredNetworkType NPE", ex);
- }
- return -1;
- }
-
- /**
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
* <p>
@@ -2932,6 +2911,7 @@
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
*
* @return the preferred network type, defined in RILConstants.java.
+ * @hide
*/
public int getPreferredNetworkType() {
try {
@@ -2954,6 +2934,7 @@
*
* @param networkType the preferred network type, defined in RILConstants.java.
* @return true on success; false on any failure.
+ * @hide
*/
public boolean setPreferredNetworkType(int networkType) {
try {
@@ -2967,25 +2948,17 @@
}
/**
- * Set the CDMA subscription source.
- * Used for device supporting both NV and RUIM for CDMA.
+ * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ *
* <p>
* Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
*
- * @param subscriptionType the subscription type, 0 for RUIM, 1 for NV.
* @return true on success; false on any failure.
*/
- public boolean setCdmaSubscription(int subscriptionType) {
- try {
- return getITelephony().setCdmaSubscription(subscriptionType);
- } catch (RemoteException ex) {
- Rlog.e(TAG, "setCdmaSubscription RemoteException", ex);
- } catch (NullPointerException ex) {
- Rlog.e(TAG, "setCdmaSubscription NPE", ex);
- }
- return false;
+ public boolean setGlobalPreferredNetworkType() {
+ return setPreferredNetworkType(RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
}
/**
@@ -3023,9 +2996,9 @@
}
/**
- * Override the branding for the input ICCID.
+ * Override the branding for the current ICCID.
*
- * Once set, whenever the ICCID is inserted into the device, the service
+ * Once set, whenever the SIM is present in the device, the service
* provider name (SPN) and the operator name will both be replaced by the
* brand value input. To unset the value, the same function should be
* called with a null brand value.
@@ -3034,14 +3007,12 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* or has to be carrier app - see #hasCarrierPrivileges.
*
- * @param iccId The ICCID of that the branding applies to.
* @param brand The brand name to display/set.
* @return true if the operation was executed correctly.
*/
- public boolean setOperatorBrandOverride(String iccId, String brand) {
- // TODO: Validate ICCID format.
+ public boolean setOperatorBrandOverride(String brand) {
try {
- return getITelephony().setOperatorBrandOverride(iccId, brand);
+ return getITelephony().setOperatorBrandOverride(brand);
} catch (RemoteException ex) {
Rlog.e(TAG, "setOperatorBrandOverride RemoteException", ex);
} catch (NullPointerException ex) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5c3dcdb..79c72b88 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -643,15 +643,6 @@
boolean setPreferredNetworkType(int networkType);
/**
- * Set the CDMA subscription source.
- * Used for device supporting both NV and RUIM for CDMA.
- *
- * @param subscriptionType the subscription type, 0 for RUIM, 1 for NV.
- * @return true on success; false on any failure.
- */
- boolean setCdmaSubscription(int subscriptionType);
-
- /**
* User enable/disable Mobile Data.
*
* @param enable true to turn on, else false
@@ -766,9 +757,9 @@
String getLine1AlphaTagForDisplay(long subId);
/**
- * Override the operator branding for the input ICCID.
+ * Override the operator branding for the current ICCID.
*
- * Once set, whenever the ICCID is inserted into the device, the service
+ * Once set, whenever the SIM is present in the device, the service
* provider name (SPN) and the operator name will both be replaced by the
* brand value input. To unset the value, the same function should be
* called with a null brand value.
@@ -777,11 +768,10 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* or has to be carrier app - see #hasCarrierPrivileges.
*
- * @param iccid The ICCID of that the branding applies to.
* @param brand The brand name to display/set.
* @return true if the operation was executed correctly.
*/
- boolean setOperatorBrandOverride(String iccId, String brand);
+ boolean setOperatorBrandOverride(String brand);
/**
* Returns the result and response from RIL for oem request
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index 5ec4247..34992b8 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -123,8 +123,8 @@
/** Indicate the timer value for exiting emergency callback mode */
static final String PROPERTY_ECM_EXIT_TIMER = "ro.cdma.ecmexittimer";
- /** The international dialing prefix conversion string */
- static final String PROPERTY_IDP_STRING = "ro.cdma.idpstring";
+ /** the international dialing prefix of current operator network */
+ static final String PROPERTY_OPERATOR_IDP_STRING = "telephony.operator.idpstring";
/**
* Defines the schema for the carrier specified OTASP number
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java
index 3360e30..256a1d4 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java
@@ -17,6 +17,7 @@
package com.android.test.hwui;
import android.animation.Animator;
+import android.animation.AnimatorSet;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
@@ -34,6 +35,7 @@
private static final int DURATION = 800;
private boolean mShouldBlock;
+ private int mIteration = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -57,10 +59,17 @@
Animator animator = ViewAnimationUtils.createCircularReveal(view,
view.getWidth() / 2, view.getHeight() / 2,
0, Math.max(view.getWidth(), view.getHeight()));
- animator.setDuration(DURATION);
- animator.setAllowRunningAsynchronously(true);
- animator.start();
+ if (mIteration < 2) {
+ animator.setDuration(DURATION);
+ animator.start();
+ } else {
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animator);
+ set.setDuration(DURATION);
+ set.start();
+ }
+ mIteration = (mIteration + 1) % 4;
mShouldBlock = !mShouldBlock;
if (mShouldBlock) {
view.post(sBlockThread);