Merge "Change Animators to reset values when restarted if their target changes" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index cbb4e7b..6da3365 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7191,6 +7191,7 @@
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String STORAGE_SERVICE = "storage";
+ field public static final java.lang.String TELECOMM_SERVICE = "telecomm";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
@@ -15619,7 +15620,7 @@
method public final int getMaxVolume();
method public final int getVolumeControl();
method public final void notifyVolumeChanged();
- method public void onAdjustVolumeBy(int);
+ method public void onAdjustVolume(int);
method public abstract int onGetCurrentVolume();
method public void onSetVolumeTo(int);
field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
@@ -16221,12 +16222,12 @@
package android.media.session {
public final class MediaController {
+ ctor public MediaController(android.media.session.MediaSession.Token);
method public void addCallback(android.media.session.MediaController.Callback);
method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
- method public void adjustVolumeBy(int, int);
+ method public void adjustVolume(int, int);
method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate();
method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
- method public static android.media.session.MediaController fromToken(android.media.session.MediaSession.Token);
method public android.media.MediaMetadata getMetadata();
method public android.media.session.PlaybackState getPlaybackState();
method public int getRatingType();
@@ -16266,6 +16267,7 @@
}
public final class MediaSession {
+ ctor public MediaSession(android.content.Context, java.lang.String);
method public void addCallback(android.media.session.MediaSession.Callback);
method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
@@ -16317,7 +16319,6 @@
public final class MediaSessionManager {
method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
- method public android.media.session.MediaSession createSession(java.lang.String);
method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
}
@@ -25492,6 +25493,8 @@
public static final class Telephony.Mms.Intents {
field public static final java.lang.String CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
field public static final java.lang.String DELETED_CONTENTS = "deleted_contents";
+ field public static final java.lang.String MMS_DOWNLOAD_ACTION = "android.provider.Telephony.MMS_DOWNLOAD";
+ field public static final java.lang.String MMS_SEND_ACTION = "android.provider.Telephony.MMS_SEND";
}
public static final class Telephony.Mms.Outbox implements android.provider.Telephony.BaseMmsColumns {
@@ -25595,8 +25598,10 @@
field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED";
field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER";
field public static final java.lang.String SMS_EMERGENCY_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+ field public static final java.lang.String SMS_FILTER_ACTION = "android.provider.Telephony.SMS_FILTER";
field public static final java.lang.String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
field public static final java.lang.String SMS_REJECTED_ACTION = "android.provider.Telephony.SMS_REJECTED";
+ field public static final java.lang.String SMS_SEND_ACTION = "android.provider.Telephony.SMS_SEND";
field public static final java.lang.String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
field public static final java.lang.String WAP_PUSH_DELIVER_ACTION = "android.provider.Telephony.WAP_PUSH_DELIVER";
field public static final java.lang.String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED";
@@ -28421,6 +28426,14 @@
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
}
+ public class TelecommManager {
+ method public void clearAccounts(java.lang.String);
+ method public java.util.List<android.telecomm.PhoneAccount> getEnabledPhoneAccounts();
+ method public android.telecomm.PhoneAccountMetadata getPhoneAccountMetadata(android.telecomm.PhoneAccount);
+ method public void registerPhoneAccount(android.telecomm.PhoneAccount, android.telecomm.PhoneAccountMetadata);
+ method public void unregisterPhoneAccount(android.telecomm.PhoneAccount);
+ }
+
public class VideoCallProfile implements android.os.Parcelable {
ctor public VideoCallProfile(int, int);
method public int describeContents();
@@ -28782,9 +28795,13 @@
public final class SmsManager {
method public java.util.ArrayList<java.lang.String> divideMessage(java.lang.String);
method public static android.telephony.SmsManager getDefault();
+ method public void injectSmsPdu(byte[], java.lang.String, android.app.PendingIntent);
method public void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+ method public void updateMmsDownloadStatus(int, byte[]);
+ method public void updateMmsSendStatus(int, boolean);
+ method public void updateSmsSendStatus(int, boolean);
field public static final int RESULT_ERROR_GENERIC_FAILURE = 1; // 0x1
field public static final int RESULT_ERROR_NO_SERVICE = 4; // 0x4
field public static final int RESULT_ERROR_NULL_PDU = 3; // 0x3
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 800b925..c771f65 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -114,7 +114,7 @@
List<IBinder> sessions = mSessionService
.getSessions(null, ActivityManager.getCurrentUser());
for (IBinder session : sessions) {
- MediaController controller = MediaController.fromBinder(ISessionController.Stub
+ MediaController controller = new MediaController(ISessionController.Stub
.asInterface(session));
if (controller != null && controller.getSessionInfo().getId().equals(id)) {
ControllerMonitor monitor = new ControllerMonitor(controller);
@@ -248,7 +248,7 @@
List<IBinder> sessions = mSessionService
.getSessions(null, ActivityManager.getCurrentUser());
for (IBinder session : sessions) {
- MediaController controller = MediaController.fromBinder(ISessionController.Stub
+ MediaController controller = new MediaController(ISessionController.Stub
.asInterface(session));
if (controller != null) {
MediaSessionInfo info = controller.getSessionInfo();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index b7f1ff9..d6c17ae 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -998,8 +998,8 @@
final InstallSessionParams params = new InstallSessionParams();
params.installFlags = PackageManager.INSTALL_ALL_USERS;
- params.mode = InstallSessionParams.MODE_FULL_INSTALL;
- params.progressMax = -1;
+ params.setModeFullInstall();
+ params.setProgressMax(-1);
String opt;
while ((opt = nextOption()) != null) {
@@ -1021,10 +1021,11 @@
} else if (opt.equals("-d")) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else if (opt.equals("-p")) {
- params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
+ params.setModeInheritExisting();
} else if (opt.equals("-S")) {
- params.deltaSize = Long.parseLong(nextOptionData());
- params.progressMax = (int) params.deltaSize;
+ final long deltaSize = Long.parseLong(nextOptionData());
+ params.setDeltaSize(deltaSize);
+ params.setProgressMax((int) params.deltaSize);
} else if (opt.equals("--abi")) {
params.abiOverride = checkAbiArgument(nextOptionData());
} else {
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 129e52c..95f83ac 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -356,6 +356,22 @@
public void setTarget(Object target) {
}
+ // Hide reverse() and canReverse() for now since reverse() only work for simple
+ // cases, like we don't support sequential, neither startDelay.
+ // TODO: make reverse() works for all the Animators.
+ /**
+ * @hide
+ */
+ public boolean canReverse() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public void reverse() {
+ }
+
/**
* <p>An animation listener receives notifications from an animation.
* Notifications indicate animation related events, such as the end or the
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 018a2d6..9156eeb 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -124,7 +124,7 @@
// was set on this AnimatorSet, so it should not be passed down to the children.
private TimeInterpolator mInterpolator = null;
-
+ private boolean mReversible = true;
/**
* Sets up this AnimatorSet to play all of the supplied animations at the same time.
* This is equivalent to calling {@link #play(Animator)} with the first animator in the
@@ -177,6 +177,7 @@
if (items.length == 1) {
play(items[0]);
} else {
+ mReversible = false;
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i+1]);
}
@@ -196,6 +197,7 @@
if (items.size() == 1) {
play(items.get(0));
} else {
+ mReversible = false;
for (int i = 0; i < items.size() - 1; ++i) {
play(items.get(i)).before(items.get(i+1));
}
@@ -407,6 +409,9 @@
*/
@Override
public void setStartDelay(long startDelay) {
+ if (mStartDelay > 0) {
+ mReversible = false;
+ }
mStartDelay = startDelay;
}
@@ -512,7 +517,7 @@
node.animation.setInterpolator(mInterpolator);
}
}
- // First, sort the nodes (if necessary). This will ensure that sortedNodes
+ // First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
@@ -626,6 +631,7 @@
anim.mNodeMap = new HashMap<Animator, Node>();
anim.mNodes = new ArrayList<Node>();
anim.mSortedNodes = new ArrayList<Node>();
+ anim.mReversible = mReversible;
// Walk through the old nodes list, cloning each node and adding it to the new nodemap.
// One problem is that the old node dependencies point to nodes in the old AnimatorSet.
@@ -908,6 +914,35 @@
}
/**
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ if (!mReversible) {
+ return false;
+ }
+ // Loop to make sure all the Nodes can reverse.
+ for (Node node : mNodes) {
+ if (!node.animation.canReverse() || node.animation.getStartDelay() > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reverse() {
+ if (canReverse()) {
+ for (Node node : mNodes) {
+ node.animation.reverse();
+ }
+ }
+ }
+
+ /**
* Dependency holds information about the node that some other node is
* dependent upon and the nature of that dependency.
*
@@ -1124,6 +1159,7 @@
* {@link AnimatorSet#play(Animator)} method ends.
*/
public Builder before(Animator anim) {
+ mReversible = false;
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
@@ -1144,6 +1180,7 @@
* {@link AnimatorSet#play(Animator)} method to play.
*/
public Builder after(Animator anim) {
+ mReversible = false;
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 5338dd0..e3380a9 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1038,6 +1038,7 @@
* play backwards. This behavior is only set for the current animation; future playing
* of the animation will use the default behavior of playing forward.
*/
+ @Override
public void reverse() {
mPlayingBackwards = !mPlayingBackwards;
if (mPlayingState == RUNNING) {
@@ -1053,6 +1054,14 @@
}
/**
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ return true;
+ }
+
+ /**
* Called internally to end an animation by removing it from the animations list. Must be
* called on the UI thread.
*/
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 12e18cc..38614a0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -56,6 +56,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.Objects;
final class IntentReceiverLeaked extends AndroidRuntimeException {
public IntentReceiverLeaked(String msg) {
@@ -252,6 +253,22 @@
}
if (mIncludeCode && !mPackageName.equals("android")) {
+ // Avoid the binder call when the package is the current application package.
+ // The activity manager will perform ensure that dexopt is performed before
+ // spinning up the process.
+ if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
+ final String isa = VMRuntime.getRuntime().vmInstructionSet();
+ try {
+ // TODO: We can probably do away with the isa argument since
+ // the AM and PM have enough information to figure this out
+ // themselves. If we do need it, we should match it against the
+ // list of devices ISAs before sending it down to installd.
+ ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa);
+ } catch (RemoteException re) {
+ // Ignored.
+ }
+ }
+
final ArrayList<String> zipPaths = new ArrayList<>();
final ArrayList<String> libPaths = new ArrayList<>();
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index ac74ca1..8a53e08 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -64,6 +64,8 @@
int mInitialMinute;
boolean mIs24HourView;
+ private boolean mIsCanceled;
+
/**
* @param context Parent.
* @param callBack How parent is notified.
@@ -124,10 +126,13 @@
mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
@Override
public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
+ mIsCanceled = isCancel;
if (!isCancel) {
mTimeSetCallback.onTimeSet(view, hourOfDay, minute);
+ TimePickerDialog.this.dismiss();
+ } else {
+ TimePickerDialog.this.cancel();
}
- TimePickerDialog.this.dismiss();
}
});
mTimePicker.setIs24HourView(mIs24HourView);
@@ -150,7 +155,7 @@
}
private void tryNotifyTimeSet() {
- if (mTimeSetCallback != null) {
+ if (mTimeSetCallback != null && !mIsCanceled) {
mTimePicker.clearFocus();
mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
mTimePicker.getCurrentMinute());
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index ba2930b..28108a0 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -331,6 +331,10 @@
* its datastore, if appropriate, and close the socket that had been provided in
* {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
*
+ * <p class="note">If the transport returns TRANSPORT_OK from this method, then the
+ * OS will always provide a matching call to {@link #finishBackup()} even if sending
+ * data via {@link #sendBackupData(int)} failed at some point.
+ *
* @param targetPackage The package whose data is to follow.
* @param socket The socket file descriptor through which the data will be provided.
* If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d068b1f..1569b9f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2539,7 +2539,6 @@
*
* @see #getSystemService
* @see android.telecomm.TelecommManager
- * @hide
*/
public static final String TELECOMM_SERVICE = "telecomm";
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 3a98f5d..eb46cf0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -384,10 +384,16 @@
/**
* Ask the package manager to perform dex-opt (if needed) on the given
- * package, if it already hasn't done mode. Only does this if running
- * in the special development "no pre-dexopt" mode.
+ * package and for the given instruction set if it already hasn't done
+ * so.
+ *
+ * If the supplied instructionSet is null, the package manager will use
+ * the packages default instruction set.
+ *
+ * In most cases, apps are dexopted in advance and this function will
+ * be a no-op.
*/
- boolean performDexOpt(String packageName);
+ boolean performDexOptIfNeeded(String packageName, String instructionSet);
/**
* Update status of external media on the package manager to scan and
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 43c2b15..ab33d75 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4201,7 +4201,7 @@
public int mPreferredOrder = 0;
// For use by package manager to keep track of where it needs to do dexopt.
- public boolean mDexOptNeeded = true;
+ public final ArraySet<String> mDexOptPerformed = new ArraySet<>(4);
// For use by package manager to keep track of when a package was last used.
public long mLastPackageUsageTimeInMills;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 6cda905..3f10b92 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -75,9 +75,7 @@
public static final int DENSITY_400 = 400;
/**
- * Standard quantized DPI for extra-extra-high-density screens. Applications
- * should not generally worry about this density; relying on XHIGH graphics
- * being scaled up to it should be sufficient for almost all cases.
+ * Standard quantized DPI for extra-extra-high-density screens.
*/
public static final int DENSITY_XXHIGH = 480;
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index f2f363a..8c9b819 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -327,6 +327,12 @@
}
/**
+ * Indicates that the content drawn by HardwareDrawCallbacks needs to
+ * be updated, which will be done by the next call to draw()
+ */
+ abstract void invalidateRoot();
+
+ /**
* Draws the specified view.
*
* @param view The view to draw.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index e2ebf6e..b033780 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -96,6 +96,7 @@
private RenderNode mRootNode;
private Choreographer mChoreographer;
private boolean mProfilingEnabled;
+ private boolean mRootNodeNeedsUpdate;
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(
@@ -255,30 +256,41 @@
return changed;
}
- private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
+ private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
-
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
- try {
- canvas.save();
- canvas.translate(mInsetLeft, mInsetTop);
- callbacks.onHardwarePreDraw(canvas);
- canvas.drawRenderNode(view.getDisplayList());
- callbacks.onHardwarePostDraw(canvas);
- canvas.restore();
- } finally {
- mRootNode.end(canvas);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
+ view.getDisplayList();
view.mRecreateDisplayList = false;
}
+ private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ updateViewTreeDisplayList(view);
+
+ if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
+ HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
+ try {
+ canvas.save();
+ canvas.translate(mInsetLeft, mInsetTop);
+ callbacks.onHardwarePreDraw(canvas);
+ canvas.drawRenderNode(view.getDisplayList());
+ callbacks.onHardwarePostDraw(canvas);
+ canvas.restore();
+ mRootNodeNeedsUpdate = false;
+ } finally {
+ mRootNode.end(canvas);
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ @Override
+ void invalidateRoot() {
+ mRootNodeNeedsUpdate = true;
+ }
+
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b554548..341419c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2440,8 +2440,11 @@
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
- mHardwareYOffset = yOffset;
- mHardwareXOffset = xOffset;
+ if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
+ mHardwareYOffset = yOffset;
+ mHardwareXOffset = xOffset;
+ mAttachInfo.mHardwareRenderer.invalidateRoot();
+ }
mResizeAlpha = resizeAlpha;
dirty.setEmpty();
@@ -2827,6 +2830,10 @@
// Set the new focus host and node.
mAccessibilityFocusedHost = view;
mAccessibilityFocusedVirtualView = node;
+
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.invalidateRoot();
+ }
}
@Override
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index aab48b3..988d461 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -138,13 +138,6 @@
}
}
-protected:
- virtual void damageSelf(TreeInfo& info) {
- // Intentionally a no-op. As RootRenderNode gets a new DisplayListData
- // every frame this would result in every draw push being a full inval,
- // which is wrong. Only RootRenderNode has this issue.
- }
-
private:
sp<Looper> mLooper;
std::vector<OnFinishedEvent> mOnFinishedEvents;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 64366e5..1f7acec 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -39,6 +39,7 @@
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <cutils/sched_policy.h>
+#include <private/android_filesystem_config.h>
#include <utils/String8.h>
#include <selinux/android.h>
#include <processgroup/processgroup.h>
@@ -536,8 +537,15 @@
jint debug_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring se_name,
jintArray fdsToClose) {
+ // Grant CAP_WAKE_ALARM to the Bluetooth process.
+ jlong capabilities = 0;
+ if (uid == AID_BLUETOOTH) {
+ capabilities |= (1LL << CAP_WAKE_ALARM);
+ }
+
return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags,
- rlimits, 0, 0, mount_external, se_info, se_name, false, fdsToClose);
+ rlimits, capabilities, capabilities, mount_external, se_info,
+ se_name, false, fdsToClose);
}
static jint com_android_internal_os_Zygote_nativeForkSystemServer(
diff --git a/core/res/res/drawable/btn_check_material_anim.xml b/core/res/res/drawable/btn_check_material_anim.xml
index 73b8a3e..1e05e84 100644
--- a/core/res/res/drawable/btn_check_material_anim.xml
+++ b/core/res/res/drawable/btn_check_material_anim.xml
@@ -30,28 +30,28 @@
<transition android:fromId="@+id/off" android:toId="@+id/on">
<animation-list>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
<bitmap android:src="@drawable/btn_check_to_on_mtrl_008" android:tint="?attr/colorControlActivated" />
@@ -106,28 +106,28 @@
<bitmap android:src="@drawable/btn_check_to_off_mtrl_007" android:tint="?attr/colorControlActivated" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlNormal" />
</item>
<item android:duration="15">
- <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlActivated" />
+ <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlNormal" />
</item>
</animation-list>
</transition>
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 4389e3d..90d8d4c 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -535,6 +535,8 @@
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketInterceptor;
import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
@@ -544,352 +546,378 @@
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
+import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSocketFactory;
+
/**
- * Sample Smack implementation of a client for GCM Cloud Connection Server.
+ * Sample Smack implementation of a client for GCM Cloud Connection Server. This
+ * code can be run as a standalone CCS client.
*
* <p>For illustration purposes only.
*/
public class SmackCcsClient {
- Logger logger = Logger.getLogger("SmackCcsClient");
+ private static final Logger logger = Logger.getLogger("SmackCcsClient");
- public static final String GCM_SERVER = "gcm.googleapis.com";
- public static final int GCM_PORT = 5235;
+ private static final String GCM_SERVER = "gcm.googleapis.com";
+ private static final int GCM_PORT = 5235;
- public static final String GCM_ELEMENT_NAME = "gcm";
- public static final String GCM_NAMESPACE = "google:mobile:data";
+ private static final String GCM_ELEMENT_NAME = "gcm";
+ private static final String GCM_NAMESPACE = "google:mobile:data";
- static Random random = new Random();
- XMPPConnection connection;
- ConnectionConfiguration config;
+ static {
- /**
- * XMPP Packet Extension for GCM Cloud Connection Server.
- */
- class GcmPacketExtension extends DefaultPacketExtension {
- String json;
-
- public GcmPacketExtension(String json) {
- super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
- this.json = json;
+ ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
+ new PacketExtensionProvider() {
+ @Override
+ public PacketExtension parseExtension(XmlPullParser parser) throws
+ Exception {
+ String json = parser.nextText();
+ return new GcmPacketExtension(json);
+ }
+ });
}
- public String getJson() {
- return json;
+ private XMPPConnection connection;
+
+ /**
+ * Indicates whether the connection is in draining state, which means that it
+ * will not accept any new downstream messages.
+ */
+ protected volatile boolean connectionDraining = false;
+
+ /**
+ * Sends a downstream message to GCM.
+ *
+ * @return true if the message has been successfully sent.
+ */
+ public boolean sendDownstreamMessage(String jsonRequest) throws
+ NotConnectedException {
+ if (!connectionDraining) {
+ send(jsonRequest);
+ return true;
+ }
+ logger.info("Dropping downstream message since the connection is draining");
+ return false;
}
- @Override
- public String toXML() {
- return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
- GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
+ /**
+ * Returns a random message id to uniquely identify a message.
+ *
+ * <p>Note: This is generated by a pseudo random number generator for
+ * illustration purpose, and is not guaranteed to be unique.
+ */
+ public String nextMessageId() {
+ return "m-" + UUID.randomUUID().toString();
}
- @SuppressWarnings("unused")
- public Packet toPacket() {
- return new Message() {
- // Must override toXML() because it includes a <body>
+ /**
+ * Sends a packet with contents provided.
+ */
+ protected void send(String jsonRequest) throws NotConnectedException {
+ Packet request = new GcmPacketExtension(jsonRequest).toPacket();
+ connection.sendPacket(request);
+ }
+
+ /**
+ * Handles an upstream data message from a device application.
+ *
+ * <p>This sample echo server sends an echo message back to the device.
+ * Subclasses should override this method to properly process upstream messages.
+ */
+ protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
+ // PackageName of the application that sent this message.
+ String category = (String) jsonObject.get("category");
+ String from = (String) jsonObject.get("from");
+ @SuppressWarnings("unchecked")
+ Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
+ payload.put("ECHO", "Application: " + category);
+
+ // Send an ECHO response back
+ String echo = createJsonMessage(from, nextMessageId(), payload,
+ "echo:CollapseKey", null, false);
+
+ try {
+ sendDownstreamMessage(echo);
+ } catch (NotConnectedException e) {
+ logger.log(Level.WARNING, "Not connected anymore, echo message is
+ not sent", e);
+ }
+ }
+
+ /**
+ * Handles an ACK.
+ *
+ * <p>Logs a {@code INFO} message, but subclasses could override it to
+ * properly handle ACKs.
+ */
+ protected void handleAckReceipt(Map<String, Object> jsonObject) {
+ String messageId = (String) jsonObject.get("message_id");
+ String from = (String) jsonObject.get("from");
+ logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",
+ messageId: " + messageId);
+ }
+
+ /**
+ * Handles a NACK.
+ *
+ * <p>Logs a {@code INFO} message, but subclasses could override it to
+ * properly handle NACKs.
+ */
+ protected void handleNackReceipt(Map<String, Object> jsonObject) {
+ String messageId = (String) jsonObject.get("message_id");
+ String from = (String) jsonObject.get("from");
+ logger.log(Level.INFO, "handleNackReceipt() from: " + from + ",
+ messageId: " + messageId);
+ }
+
+ protected void handleControlMessage(Map<String, Object> jsonObject) {
+ logger.log(Level.INFO, "handleControlMessage(): " + jsonObject);
+ String controlType = (String) jsonObject.get("control_type");
+ if ("CONNECTION_DRAINING".equals(controlType)) {
+ connectionDraining = true;
+ } else {
+ logger.log(Level.INFO, "Unrecognized control type: %s. This could
+ happen if new features are " + "added to the CCS protocol.",
+ controlType);
+ }
+ }
+
+ /**
+ * Creates a JSON encoded GCM message.
+ *
+ * @param to RegistrationId of the target device (Required).
+ * @param messageId Unique messageId for which CCS will send an
+ * "ack/nack" (Required).
+ * @param payload Message content intended for the application. (Optional).
+ * @param collapseKey GCM collapse_key parameter (Optional).
+ * @param timeToLive GCM time_to_live parameter (Optional).
+ * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
+ * @return JSON encoded GCM message.
+ */
+ public static String createJsonMessage(String to, String messageId,
+ Map<String, String> payload, String collapseKey, Long timeToLive,
+ Boolean delayWhileIdle) {
+ Map<String, Object> message = new HashMap<String, Object>();
+ message.put("to", to);
+ if (collapseKey != null) {
+ message.put("collapse_key", collapseKey);
+ }
+ if (timeToLive != null) {
+ message.put("time_to_live", timeToLive);
+ }
+ if (delayWhileIdle != null && delayWhileIdle) {
+ message.put("delay_while_idle", true);
+ }
+ message.put("message_id", messageId);
+ message.put("data", payload);
+ return JSONValue.toJSONString(message);
+ }
+
+ /**
+ * Creates a JSON encoded ACK message for an upstream message received
+ * from an application.
+ *
+ * @param to RegistrationId of the device who sent the upstream message.
+ * @param messageId messageId of the upstream message to be acknowledged to CCS.
+ * @return JSON encoded ack.
+ */
+ protected static String createJsonAck(String to, String messageId) {
+ Map<String, Object> message = new HashMap<String, Object>();
+ message.put("message_type", "ack");
+ message.put("to", to);
+ message.put("message_id", messageId);
+ return JSONValue.toJSONString(message);
+ }
+
+ /**
+ * Connects to GCM Cloud Connection Server using the supplied credentials.
+ *
+ * @param senderId Your GCM project number
+ * @param apiKey API Key of your project
+ */
+ public void connect(long senderId, String apiKey)
+ throws XMPPException, IOException, SmackException {
+ ConnectionConfiguration config =
+ new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
+ config.setSecurityMode(SecurityMode.enabled);
+ config.setReconnectionAllowed(true);
+ config.setRosterLoadedAtLogin(false);
+ config.setSendPresence(false);
+ config.setSocketFactory(SSLSocketFactory.getDefault());
+
+ connection = new XMPPTCPConnection(config);
+ connection.connect();
+
+ connection.addConnectionListener(new LoggingConnectionListener());
+
+ // Handle incoming packets
+ connection.addPacketListener(new PacketListener() {
+
+ @Override
+ public void processPacket(Packet packet) {
+ logger.log(Level.INFO, "Received: " + packet.toXML());
+ Message incomingMessage = (Message) packet;
+ GcmPacketExtension gcmPacket =
+ (GcmPacketExtension) incomingMessage.
+ getExtension(GCM_NAMESPACE);
+ String json = gcmPacket.getJson();
+ try {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> jsonObject =
+ (Map<String, Object>) JSONValue.
+ parseWithException(json);
+
+ // present for "ack"/"nack", null otherwise
+ Object messageType = jsonObject.get("message_type");
+
+ if (messageType == null) {
+ // Normal upstream data message
+ handleUpstreamMessage(jsonObject);
+
+ // Send ACK to CCS
+ String messageId = (String) jsonObject.get("message_id");
+ String from = (String) jsonObject.get("from");
+ String ack = createJsonAck(from, messageId);
+ send(ack);
+ } else if ("ack".equals(messageType.toString())) {
+ // Process Ack
+ handleAckReceipt(jsonObject);
+ } else if ("nack".equals(messageType.toString())) {
+ // Process Nack
+ handleNackReceipt(jsonObject);
+ } else if ("control".equals(messageType.toString())) {
+ // Process control message
+ handleControlMessage(jsonObject);
+ } else {
+ logger.log(Level.WARNING,
+ "Unrecognized message type (%s)",
+ messageType.toString());
+ }
+ } catch (ParseException e) {
+ logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Failed to process packet", e);
+ }
+ }
+ }, new PacketTypeFilter(Message.class));
+
+ // Log all outgoing packets
+ connection.addPacketInterceptor(new PacketInterceptor() {
+ @Override
+ public void interceptPacket(Packet packet) {
+ logger.log(Level.INFO, "Sent: {0}", packet.toXML());
+ }
+ }, new PacketTypeFilter(Message.class));
+
+ connection.login(senderId + "@gcm.googleapis.com", apiKey);
+ }
+
+ public static void main(String[] args) throws Exception {
+ final long senderId = 1234567890L; // your GCM sender id
+ final String password = "Your API key";
+
+ SmackCcsClient ccsClient = new SmackCcsClient();
+
+ ccsClient.connect(senderId, password);
+
+ // Send a sample hello downstream message to a device.
+ String toRegId = "RegistrationIdOfTheTargetDevice";
+ String messageId = ccsClient.nextMessageId();
+ Map<String, String> payload = new HashMap<String, String>();
+ payload.put("Hello", "World");
+ payload.put("CCS", "Dummy Message");
+ payload.put("EmbeddedMessageId", messageId);
+ String collapseKey = "sample";
+ Long timeToLive = 10000L;
+ String message = createJsonMessage(toRegId, messageId, payload,
+ collapseKey, timeToLive, true);
+
+ ccsClient.sendDownstreamMessage(message);
+ }
+
+ /**
+ * XMPP Packet Extension for GCM Cloud Connection Server.
+ */
+ private static final class GcmPacketExtension extends DefaultPacketExtension {
+
+ private final String json;
+
+ public GcmPacketExtension(String json) {
+ super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
+ this.json = json;
+ }
+
+ public String getJson() {
+ return json;
+ }
+
@Override
public String toXML() {
-
- StringBuilder buf = new StringBuilder();
- buf.append("<message");
- if (getXmlns() != null) {
- buf.append(" xmlns=\"").append(getXmlns()).append("\"");
- }
- if (getLanguage() != null) {
- buf.append(" xml:lang=\"").append(getLanguage()).append("\"");
- }
- if (getPacketID() != null) {
- buf.append(" id=\"").append(getPacketID()).append("\"");
- }
- if (getTo() != null) {
- buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
- }
- if (getFrom() != null) {
- buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
- }
- buf.append(">");
- buf.append(GcmPacketExtension.this.toXML());
- buf.append("</message>");
- return buf.toString();
+ return String.format("<%s xmlns=\"%s\">%s</%s>",
+ GCM_ELEMENT_NAME, GCM_NAMESPACE,
+ StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
}
- };
- }
- }
- public SmackCcsClient() {
- // Add GcmPacketExtension
- ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
- GCM_NAMESPACE, new PacketExtensionProvider() {
-
- @Override
- public PacketExtension parseExtension(XmlPullParser parser)
- throws Exception {
- String json = parser.nextText();
- GcmPacketExtension packet = new GcmPacketExtension(json);
- return packet;
- }
- });
- }
-
- /**
- * Returns a random message id to uniquely identify a message.
- *
- * <p>Note:
- * This is generated by a pseudo random number generator for illustration purpose,
- * and is not guaranteed to be unique.
- *
- */
- public String getRandomMessageId() {
- return "m-" + Long.toString(random.nextLong());
- }
-
- /**
- * Sends a downstream GCM message.
- */
- public void send(String jsonRequest) {
- Packet request = new GcmPacketExtension(jsonRequest).toPacket();
- connection.sendPacket(request);
- }
-
- /**
- * Handles an upstream data message from a device application.
- *
- * <p>This sample echo server sends an echo message back to the device.
- * Subclasses should override this method to process an upstream message.
- */
- public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
- String from = jsonObject.get("from").toString();
-
- // PackageName of the application that sent this message.
- String category = jsonObject.get("category").toString();
-
- // Use the packageName as the collapseKey in the echo packet
- String collapseKey = "echo:CollapseKey";
- @SuppressWarnings("unchecked")
- Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
- payload.put("ECHO", "Application: " + category);
-
- // Send an ECHO response back
- String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
- send(echo);
- }
-
- /**
- * Handles an ACK.
- *
- * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to
- * properly handle ACKS.
- */
- public void handleAckReceipt(Map<String, Object> jsonObject) {
- String messageId = jsonObject.get("message_id").toString();
- String from = jsonObject.get("from").toString();
- logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId);
- }
-
- /**
- * Handles a NACK.
- *
- * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to
- * properly handle NACKS.
- */
- public void handleNackReceipt(Map<String, Object> jsonObject) {
- String messageId = jsonObject.get("message_id").toString();
- String from = jsonObject.get("from").toString();
- logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId);
- }
-
- /**
- * Creates a JSON encoded GCM message.
- *
- * @param to RegistrationId of the target device (Required).
- * @param messageId Unique messageId for which CCS will send an "ack/nack" (Required).
- * @param payload Message content intended for the application. (Optional).
- * @param collapseKey GCM collapse_key parameter (Optional).
- * @param timeToLive GCM time_to_live parameter (Optional).
- * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
- * @return JSON encoded GCM message.
- */
- public static String createJsonMessage(String to, String messageId, Map<String, String> payload,
- String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
- Map<String, Object> message = new HashMap<String, Object>();
- message.put("to", to);
- if (collapseKey != null) {
- message.put("collapse_key", collapseKey);
- }
- if (timeToLive != null) {
- message.put("time_to_live", timeToLive);
- }
- if (delayWhileIdle != null && delayWhileIdle) {
- message.put("delay_while_idle", true);
- }
- message.put("message_id", messageId);
- message.put("data", payload);
- return JSONValue.toJSONString(message);
- }
-
- /**
- * Creates a JSON encoded ACK message for an upstream message received from an application.
- *
- * @param to RegistrationId of the device who sent the upstream message.
- * @param messageId messageId of the upstream message to be acknowledged to CCS.
- * @return JSON encoded ack.
- */
- public static String createJsonAck(String to, String messageId) {
- Map<String, Object> message = new HashMap<String, Object>();
- message.put("message_type", "ack");
- message.put("to", to);
- message.put("message_id", messageId);
- return JSONValue.toJSONString(message);
- }
-
- /**
- * Connects to GCM Cloud Connection Server using the supplied credentials.
- *
- * @param username GCM_SENDER_ID@gcm.googleapis.com
- * @param password API Key
- * @throws XMPPException
- */
- public void connect(String username, String password) throws XMPPException {
- config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
- config.setSecurityMode(SecurityMode.enabled);
- config.setReconnectionAllowed(true);
- config.setRosterLoadedAtLogin(false);
- config.setSendPresence(false);
- config.setSocketFactory(SSLSocketFactory.getDefault());
-
- // NOTE: Set to true to launch a window with information about packets sent and received
- config.setDebuggerEnabled(true);
-
- // -Dsmack.debugEnabled=true
- XMPPConnection.DEBUG_ENABLED = true;
-
- connection = new XMPPConnection(config);
- connection.connect();
-
- connection.addConnectionListener(new ConnectionListener() {
-
- @Override
- public void reconnectionSuccessful() {
- logger.info("Reconnecting..");
- }
-
- @Override
- public void reconnectionFailed(Exception e) {
- logger.log(Level.INFO, "Reconnection failed.. ", e);
- }
-
- @Override
- public void reconnectingIn(int seconds) {
- logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
- }
-
- @Override
- public void connectionClosedOnError(Exception e) {
- logger.log(Level.INFO, "Connection closed on error.");
- }
-
- @Override
- public void connectionClosed() {
- logger.info("Connection closed.");
- }
- });
-
- // Handle incoming packets
- connection.addPacketListener(new PacketListener() {
-
- @Override
- public void processPacket(Packet packet) {
- logger.log(Level.INFO, "Received: " + packet.toXML());
- Message incomingMessage = (Message) packet;
- GcmPacketExtension gcmPacket =
- (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
- String json = gcmPacket.getJson();
- try {
- @SuppressWarnings("unchecked")
- Map<String, Object> jsonObject =
- (Map<String, Object>) JSONValue.parseWithException(json);
-
- // present for "ack"/"nack", null otherwise
- Object messageType = jsonObject.get("message_type");
-
- if (messageType == null) {
- // Normal upstream data message
- handleIncomingDataMessage(jsonObject);
-
- // Send ACK to CCS
- String messageId = jsonObject.get("message_id").toString();
- String from = jsonObject.get("from").toString();
- String ack = createJsonAck(from, messageId);
- send(ack);
- } else if ("ack".equals(messageType.toString())) {
- // Process Ack
- handleAckReceipt(jsonObject);
- } else if ("nack".equals(messageType.toString())) {
- // Process Nack
- handleNackReceipt(jsonObject);
- } else {
- logger.log(Level.WARNING, "Unrecognized message type (%s)",
- messageType.toString());
- }
- } catch (ParseException e) {
- logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Couldn't send echo.", e);
+ public Packet toPacket() {
+ Message message = new Message();
+ message.addExtension(this);
+ return message;
}
- }
- }, new PacketTypeFilter(Message.class));
-
-
- // Log all outgoing packets
- connection.addPacketInterceptor(new PacketInterceptor() {
- @Override
- public void interceptPacket(Packet packet) {
- logger.log(Level.INFO, "Sent: {0}", packet.toXML());
- }
- }, new PacketTypeFilter(Message.class));
-
- connection.login(username, password);
- }
-
- public static void main(String [] args) {
- final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com";
- final String password = "API Key";
-
- SmackCcsClient ccsClient = new SmackCcsClient();
-
- try {
- ccsClient.connect(userName, password);
- } catch (XMPPException e) {
- e.printStackTrace();
}
- // Send a sample hello downstream message to a device.
- String toRegId = "RegistrationIdOfTheTargetDevice";
- String messageId = ccsClient.getRandomMessageId();
- Map<String, String> payload = new HashMap<String, String>();
- payload.put("Hello", "World");
- payload.put("CCS", "Dummy Message");
- payload.put("EmbeddedMessageId", messageId);
- String collapseKey = "sample";
- Long timeToLive = 10000L;
- Boolean delayWhileIdle = true;
- ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
- timeToLive, delayWhileIdle));
- }
+ private static final class LoggingConnectionListener
+ implements ConnectionListener {
+
+ @Override
+ public void connected(XMPPConnection xmppConnection) {
+ logger.info("Connected.");
+ }
+
+ @Override
+ public void authenticated(XMPPConnection xmppConnection) {
+ logger.info("Authenticated.");
+ }
+
+ @Override
+ public void reconnectionSuccessful() {
+ logger.info("Reconnecting..");
+ }
+
+ @Override
+ public void reconnectionFailed(Exception e) {
+ logger.log(Level.INFO, "Reconnection failed.. ", e);
+ }
+
+ @Override
+ public void reconnectingIn(int seconds) {
+ logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
+ }
+
+ @Override
+ public void connectionClosedOnError(Exception e) {
+ logger.info("Connection closed on error.");
+ }
+
+ @Override
+ public void connectionClosed() {
+ logger.info("Connection closed.");
+ }
+ }
}</pre>
+
<h3 id="python">Python sample</h3>
<p>Here is an example of a CCS app server written in Python. This sample echo
diff --git a/docs/html/google/play/billing/billing_overview.jd b/docs/html/google/play/billing/billing_overview.jd
index 301d911..12f8c9a 100644
--- a/docs/html/google/play/billing/billing_overview.jd
+++ b/docs/html/google/play/billing/billing_overview.jd
@@ -38,6 +38,16 @@
features that you need to understand in order to add In-app
Billing features into your application.</p>
+<p class="note"><b>Note</b>: Ensure that you comply with applicable laws in the countries where you
+distribute apps. For example, in EU countries, laws based on the
+<a href="http://ec.europa.eu/justice/consumer-marketing/unfair-trade/unfair-practices/">Unfair
+Commercial Practices Directive</a> prohibit direct exhortations to children to buy advertised
+products or to persuade their parents or other adults to buy advertised products for them.
+See the
+<a href="http://ec.europa.eu/consumers/enforcement/docs/common_position_on_online_games_en.pdf">position
+of the EU consumer protection authorities</a> for more information on this and other topics.
+</p>
+
<h2 id="api">In-app Billing API</h2>
<p>Your application accesses the In-app Billing service using an API that is
exposed by the Google Play app that is installed on the device. The Google Play
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index b77a72a..71b15d5 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -349,8 +349,7 @@
<input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
<label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
</p>
-<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return
-onDownloadNdkForRealz(this);"></a></p>
+<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return onDownloadNdkForRealz(this);"></a></p>
</div>
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 485b38a..2cb7b03 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -16,8 +16,6 @@
package android.graphics.drawable;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.annotation.NonNull;
@@ -26,6 +24,7 @@
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.LongSparseLongArray;
import android.util.SparseIntArray;
import android.util.StateSet;
@@ -62,6 +61,8 @@
* @attr ref android.R.styleable#DrawableStates_state_pressed
*/
public class AnimatedStateListDrawable extends StateListDrawable {
+ private static final String LOGTAG = AnimatedStateListDrawable.class.getSimpleName();
+
private static final String ELEMENT_TRANSITION = "transition";
private static final String ELEMENT_ITEM = "item";
@@ -157,48 +158,51 @@
}
private boolean selectTransition(int toIndex) {
- if (toIndex == mTransitionToIndex) {
- // Already animating to that keyframe.
- return true;
- }
-
+ final int fromIndex;
final Transition currentTransition = mTransition;
if (currentTransition != null) {
if (toIndex == mTransitionToIndex) {
+ // Already animating to that keyframe.
return true;
- } else if (toIndex == mTransitionFromIndex) {
+ } else if (toIndex == mTransitionFromIndex && currentTransition.canReverse()) {
// Reverse the current animation.
currentTransition.reverse();
- mTransitionFromIndex = mTransitionToIndex;
- mTransitionToIndex = toIndex;
+ mTransitionToIndex = mTransitionFromIndex;
+ mTransitionFromIndex = toIndex;
return true;
}
+ // Start the next transition from the end of the current one.
+ fromIndex = mTransitionToIndex;
+
// Changing animation, end the current animation.
currentTransition.stop();
- mTransition = null;
+ } else {
+ fromIndex = getCurrentIndex();
}
// Reset state.
+ mTransition = null;
mTransitionFromIndex = -1;
mTransitionToIndex = -1;
final AnimatedStateListState state = mState;
- final int fromIndex = getCurrentIndex();
final int fromId = state.getKeyframeIdAt(fromIndex);
final int toId = state.getKeyframeIdAt(toIndex);
-
if (toId == 0 || fromId == 0) {
// Missing a keyframe ID.
return false;
}
final int transitionIndex = state.indexOfTransition(fromId, toId);
- if (transitionIndex < 0 || !selectDrawable(transitionIndex)) {
+ if (transitionIndex < 0) {
// Couldn't select a transition.
return false;
}
+ // This may fail if we're already on the transition, but that's okay!
+ selectDrawable(transitionIndex);
+
final Transition transition;
final Drawable d = getCurrent();
if (d instanceof AnimationDrawable) {
@@ -302,13 +306,13 @@
@Override
public boolean canReverse() {
- return true;
+ return mAvd.canReverse();
}
@Override
public void start() {
if (mReversed) {
- mAvd.reverse();
+ reverse();
} else {
mAvd.start();
}
@@ -316,7 +320,11 @@
@Override
public void reverse() {
- mAvd.reverse();
+ if (canReverse()) {
+ mAvd.reverse();
+ } else {
+ Log.w(LOGTAG, "Reverse() is called on a drawable can't reverse");
+ }
}
@Override
@@ -493,17 +501,6 @@
return this;
}
- private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator anim) {
- selectDrawable(mTransitionToIndex);
-
- mTransitionToIndex = -1;
- mTransitionFromIndex = -1;
- mTransition = null;
- }
- };
-
static class AnimatedStateListState extends StateListState {
private static final int REVERSE_SHIFT = 32;
private static final int REVERSE_MASK = 0x1;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 071d8ba4..11c2571 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -337,15 +337,33 @@
* Reverses ongoing animations or starts pending animations in reverse.
* <p>
* NOTE: Only works of all animations are ValueAnimators.
+ * @hide
*/
- void reverse() {
+ public void reverse() {
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
- if (animator instanceof ValueAnimator) {
- ((ValueAnimator) animator).reverse();
+ if (animator.canReverse()) {
+ animator.reverse();
+ } else {
+ Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
}
}
}
+
+ /**
+ * @hide
+ */
+ public boolean canReverse() {
+ final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
+ final int size = animators.size();
+ for (int i = 0; i < size; i++) {
+ final Animator animator = animators.get(i);
+ if (!animator.canReverse()) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 54fa143..8cc65b2 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -173,9 +173,6 @@
// UI thread only!
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
-protected:
- virtual void damageSelf(TreeInfo& info);
-
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -250,6 +247,7 @@
void prepareLayer(TreeInfo& info);
void pushLayerUpdate(TreeInfo& info);
void deleteDisplayListData();
+ void damageSelf(TreeInfo& info);
void incParentRefCount() { mParentCount++; }
void decParentRefCount();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 57279b7..a4ac262 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -185,6 +185,11 @@
} else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
dirty.setEmpty();
} else {
+ if (!dirty.intersect(0, 0, width, height)) {
+ ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
+ SK_RECT_ARGS(dirty), width, height);
+ dirty.setEmpty();
+ }
profiler().unionDirty(&dirty);
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 3336694..7d1de24 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -2249,12 +2249,12 @@
}
@Override
- public void onAdjustVolumeBy(final int delta) {
+ public void onAdjustVolume(final int direction) {
sStatic.mHandler.post(new Runnable() {
@Override
public void run() {
if (mVcb != null) {
- mVcb.vcb.onVolumeUpdateRequest(mVcb.route, delta);
+ mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
}
}
});
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index d151e66..9bda1d4 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -32,14 +32,14 @@
/**
* The volume control uses relative adjustment via
- * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
+ * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific
* value should be ignored.
*/
public static final int VOLUME_CONTROL_RELATIVE = 1;
/**
* The volume control uses an absolute value. It may be adjusted using
- * {@link #onAdjustVolumeBy(int)} or set directly using
+ * {@link #onAdjustVolume(int)} or set directly using
* {@link #onSetVolumeTo(int)}.
*/
public static final int VOLUME_CONTROL_ABSOLUTE = 2;
@@ -104,12 +104,13 @@
}
/**
- * Override to handle requests to adjust the volume of the current
- * output.
- *
- * @param delta The amount to change the volume
+ * Override to handle requests to adjust the volume of the current output.
+ * Direction will be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}.
+ *
+ * @param direction The direction to change the volume in.
*/
- public void onAdjustVolumeBy(int delta) {
+ public void onAdjustVolume(int direction) {
}
/**
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index e554e27..39391b6 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -39,6 +39,6 @@
void onRate(in Rating rating);
// These callbacks are for volume handling
- void onAdjustVolumeBy(int delta);
+ void onAdjustVolume(int direction);
void onSetVolumeTo(int value);
}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 6cf5ef2..b555220 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -41,7 +41,7 @@
MediaSessionInfo getSessionInfo();
long getFlags();
ParcelableVolumeInfo getVolumeAttributes();
- void adjustVolumeBy(int delta, int flags);
+ void adjustVolume(int direction, int flags);
void setVolumeTo(int value, int flags);
IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index dce84d4..95c2d61 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -31,7 +31,7 @@
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
- void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
+ void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
void removeSessionsListener(in IActiveSessionsListener listener);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index cc8b31a..7fedd82 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -66,28 +66,27 @@
private final TransportControls mTransportControls;
- private MediaController(ISessionController sessionBinder) {
+ /**
+ * Call for creating a MediaController directly from a binder. Should only
+ * be used by framework code.
+ *
+ * @hide
+ */
+ public MediaController(ISessionController sessionBinder) {
+ if (sessionBinder == null) {
+ throw new IllegalArgumentException("Session token cannot be null");
+ }
mSessionBinder = sessionBinder;
mTransportControls = new TransportControls();
}
/**
- * @hide
- */
- public static MediaController fromBinder(ISessionController sessionBinder) {
- return new MediaController(sessionBinder);
- }
-
- /**
- * Get a new media controller from a session token which may have
- * been obtained from another process. If successful the controller returned
- * will be connected to the session that generated the token.
+ * Create a new MediaController from a session's token.
*
- * @param token The session token to control.
- * @return A controller for the session or null if inaccessible.
+ * @param token The token for the session.
*/
- public static MediaController fromToken(@NonNull MediaSession.Token token) {
- return fromBinder(token.getBinder());
+ public MediaController(@NonNull MediaSession.Token token) {
+ this(token.getBinder());
}
/**
@@ -235,19 +234,21 @@
}
/**
- * Adjust the volume of the stream or output this session is playing on.
- * Negative values will lower the volume. The command will be ignored if it
- * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * Adjust the volume of the stream or output this session is playing on. The
+ * direction must be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+ * The command will be ignored if the session does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
* {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
* {@link AudioManager} may be used to affect the handling.
*
* @see #getVolumeInfo()
- * @param delta The number of steps to adjust the volume by.
+ * @param direction The direction to adjust the volume in.
* @param flags Any flags to pass with the command.
*/
- public void adjustVolumeBy(int delta, int flags) {
+ public void adjustVolume(int direction, int flags) {
try {
- mSessionBinder.adjustVolumeBy(delta, flags);
+ mSessionBinder.adjustVolume(direction, flags);
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 34997bd..4841360 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaMetadata;
@@ -37,10 +38,13 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.telephony.DctConstants.Activity;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +63,9 @@
* create a {@link MediaController} to interact with the session.
* <p>
* To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)}. To receive transport control
- * commands a {@link TransportControlsCallback} must be set with
+ * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called. To receive transport control commands a
+ * {@link TransportControlsCallback} must be set with
* {@link #addTransportControlsCallback}.
* <p>
* When an app is finished performing playback it must call {@link #release()}
@@ -119,18 +124,45 @@
private boolean mActive = false;
/**
+ * Creates a new session. The session will automatically be registered with
+ * the system but will not be published until {@link #setActive(boolean)
+ * setActive(true)} is called. You must call {@link #release()} when
+ * finished with the session.
+ *
+ * @param context The context to use to create the session.
+ * @param tag A short name for debugging purposes.
+ */
+ public MediaSession(@NonNull Context context, @NonNull String tag) {
+ this(context, tag, UserHandle.myUserId());
+ }
+
+ /**
+ * Creates a new session as the specified user. To create a session as a
+ * user other than your own you must hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission.
+ *
+ * @param context The context to use to create the session.
+ * @param tag A short name for debugging purposes.
+ * @param userId The user id to create the session as.
* @hide
*/
- public MediaSession(ISession binder, CallbackStub cbStub) {
- mBinder = binder;
- mCbStub = cbStub;
- ISessionController controllerBinder = null;
- try {
- controllerBinder = mBinder.getController();
- } catch (RemoteException e) {
- throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+ public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null.");
}
- mSessionToken = new Token(controllerBinder);
+ if (TextUtils.isEmpty(tag)) {
+ throw new IllegalArgumentException("tag cannot be null or empty");
+ }
+ mCbStub = new CallbackStub();
+ MediaSessionManager manager = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ try {
+ mBinder = manager.createSession(mCbStub, tag, userId);
+ mSessionToken = new Token(mBinder.getController());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Remote error creating session.", e);
+ }
}
/**
@@ -837,11 +869,11 @@
}
@Override
- public void onAdjustVolumeBy(int delta) {
+ public void onAdjustVolume(int direction) {
MediaSession session = mMediaSession.get();
if (session != null) {
if (session.mVolumeProvider != null) {
- session.mVolumeProvider.onAdjustVolumeBy(delta);
+ session.mVolumeProvider.onAdjustVolume(direction);
}
}
}
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 11f7720..da1a6ed 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -46,6 +47,7 @@
private static final Object sLock = new Object();
private static MediaSessionLegacyHelper sInstance;
+ private Context mContext;
private MediaSessionManager mSessionManager;
private Handler mHandler = new Handler(Looper.getMainLooper());
// The legacy APIs use PendingIntents to register/unregister media button
@@ -54,6 +56,7 @@
= new ArrayMap<PendingIntent, SessionHolder>();
private MediaSessionLegacyHelper(Context context) {
+ mContext = context;
mSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
}
@@ -206,13 +209,13 @@
}
}
- mSessionManager.dispatchAdjustVolumeBy(AudioManager.USE_DEFAULT_STREAM_TYPE,
+ mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
direction, flags);
}
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
- mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
if (DEBUG) {
Log.d(TAG, "dispatched volume adjustment");
}
@@ -225,6 +228,9 @@
return;
}
SessionHolder holder = getHolder(pi, true);
+ if (holder == null) {
+ return;
+ }
if (holder.mRccListener != null) {
if (holder.mRccListener == listener) {
if (DEBUG) {
@@ -270,6 +276,9 @@
return;
}
SessionHolder holder = getHolder(pi, true);
+ if (holder == null) {
+ return;
+ }
if (holder.mMediaButtonListener != null) {
// Already have this listener registered, but update it anyway as
// the extras may have changed.
@@ -316,7 +325,8 @@
private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
SessionHolder holder = mSessions.get(pi);
if (holder == null && createIfMissing) {
- MediaSession session = mSessionManager.createSession(TAG);
+ MediaSession session;
+ session = new MediaSession(mContext, TAG);
session.setActive(true);
holder = new SessionHolder(session, pi);
mSessions.put(pi, holder);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c477406..824b397 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.session.ISessionManager;
import android.os.IBinder;
@@ -64,42 +65,15 @@
}
/**
- * Creates a new session.
+ * Create a new session in the system and get the binder for it.
*
* @param tag A short name for debugging purposes.
- * @return A {@link MediaSession} for the new session.
- */
- public @NonNull MediaSession createSession(@NonNull String tag) {
- return createSessionAsUser(tag, UserHandle.myUserId());
- }
-
- /**
- * Creates a new session as the specified user. To create a session as a
- * user other than your own you must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
- * permission.
- *
- * @param tag A short name for debugging purposes.
- * @param userId The user id to create the session as.
- * @return A {@link MediaSession} for the new session.
+ * @return The binder object from the system
* @hide
*/
- public @NonNull MediaSession createSessionAsUser(@NonNull String tag, int userId) {
- if (TextUtils.isEmpty(tag)) {
- throw new IllegalArgumentException("tag must not be null or empty");
- }
-
- try {
- MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
- MediaSession session = new MediaSession(mService
- .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub);
- cbStub.setMediaSession(session);
-
- return session;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to create session: ", e);
- return null;
- }
+ public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+ @NonNull String tag, int userId) throws RemoteException {
+ return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
}
/**
@@ -142,7 +116,7 @@
List<IBinder> binders = mService.getSessions(notificationListener, userId);
int size = binders.size();
for (int i = 0; i < size; i++) {
- MediaController controller = MediaController.fromBinder(ISessionController.Stub
+ MediaController controller = new MediaController(ISessionController.Stub
.asInterface(binders.get(i)));
controllers.add(controller);
}
@@ -256,17 +230,19 @@
/**
* Dispatch an adjust volume request to the system. It will be sent to the
- * most relevant audio stream or media session.
+ * most relevant audio stream or media session. The direction must be one of
+ * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+ * {@link AudioManager#ADJUST_SAME}.
*
* @param suggestedStream The stream to fall back to if there isn't a
* relevant stream
- * @param delta The amount to adjust the volume by.
+ * @param direction The direction to adjust volume in.
* @param flags Any flags to include with the volume change.
* @hide
*/
- public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+ public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
try {
- mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ mService.dispatchAdjustVolume(suggestedStream, direction, flags);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send adjust volume.", e);
}
@@ -295,7 +271,7 @@
ArrayList<MediaController> controllers = new ArrayList<MediaController>();
int size = tokens.size();
for (int i = 0; i < size; i++) {
- controllers.add(MediaController.fromToken(tokens.get(i)));
+ controllers.add(new MediaController(tokens.get(i)));
}
SessionListener.this.onActiveSessionsChanged(controllers);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index b32d3dd..78a99e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -456,9 +456,11 @@
} else {
// Launch the activity anew with the desired animation
Intent i = new Intent(task.key.baseIntent);
- i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME
- | Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+ | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ if ((i.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == 0) {
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
try {
UserHandle taskUser = new UserHandle(task.userId);
if (launchOpts != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 1bb36dd..8b5ff67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -27,11 +27,12 @@
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings.Global;
+import android.telecomm.TelecommConstants;
+import android.telecomm.TelecommManager;
import android.util.Log;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.TtyIntent;
import com.android.systemui.R;
/**
@@ -89,7 +90,7 @@
else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
updateSimState(intent);
}
- else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
+ else if (action.equals(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED)) {
updateTTY(intent);
}
else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
@@ -110,7 +111,7 @@
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+ filter.addAction(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
@@ -269,7 +270,9 @@
}
private final void updateTTY(Intent intent) {
- final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false);
+ int currentTtyMode = intent.getIntExtra(TelecommConstants.EXTRA_CURRENT_TTY_MODE,
+ TelecommConstants.TTY_MODE_OFF);
+ boolean enabled = currentTtyMode != TelecommConstants.TTY_MODE_OFF;
if (DEBUG) Log.v(TAG, "updateTTY: enabled: " + enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 375f94c..e38c2ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -189,7 +189,7 @@
@Override
public void remoteVolumeChanged(ISessionController binder, int flags)
throws RemoteException {
- MediaController controller = MediaController.fromBinder(binder);
+ MediaController controller = new MediaController(binder);
mPanel.postRemoteVolumeChanged(controller, flags);
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f431fdb..edeb5b4 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1700,7 +1700,7 @@
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
- mMediaController.adjustVolumeBy(direction, AudioManager.FLAG_SHOW_UI);
+ mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI);
@@ -1787,7 +1787,7 @@
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
- mMediaController.adjustVolumeBy(0, AudioManager.FLAG_PLAY_SOUND
+ mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND
| AudioManager.FLAG_VIBRATE);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c3a9dbe..e8e2813 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3535,19 +3535,30 @@
}
} while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
- // Done -- how did it turn out?
- if (result == BackupTransport.TRANSPORT_OK){
- result = transport.finishBackup();
- } else {
- Slog.w(TAG, "Error backing up " + target.packageName);
+ // In all cases we need to give the transport its finish callback
+ int finishResult = transport.finishBackup();
+
+ // If we were otherwise in a good state, now interpret the final
+ // result based on what finishBackup() returned. If we're in a
+ // failure case already, preserve that result and ignore whatever
+ // finishBackup() reported.
+ if (result == BackupTransport.TRANSPORT_OK) {
+ result = finishResult;
}
- } else if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+
+ if (result != BackupTransport.TRANSPORT_OK) {
+ Slog.e(TAG, "Error " + result
+ + " backing up " + target.packageName);
+ }
+ }
+
+ if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
if (DEBUG) {
Slog.i(TAG, "Transport rejected backup of " + target.packageName
+ ", skipping");
}
// do nothing, clean up, and continue looping
- } else {
+ } else if (result != BackupTransport.TRANSPORT_OK) {
if (DEBUG) {
Slog.i(TAG, "Transport failed; aborting backup");
return;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a19eb15..87084d5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1069,27 +1069,31 @@
synchronized (mRecords) {
final int recordCount = mRecords.size();
pw.println("last known state:");
- pw.println(" mCallState=" + mCallState);
- pw.println(" mCallIncomingNumber=" + mCallIncomingNumber);
- pw.println(" mServiceState=" + mServiceState);
- pw.println(" mSignalStrength=" + mSignalStrength);
- pw.println(" mMessageWaiting=" + mMessageWaiting);
- pw.println(" mCallForwarding=" + mCallForwarding);
- pw.println(" mDataActivity=" + mDataActivity);
- pw.println(" mDataConnectionState=" + mDataConnectionState);
- pw.println(" mDataConnectionPossible=" + mDataConnectionPossible);
- pw.println(" mDataConnectionReason=" + mDataConnectionReason);
- pw.println(" mDataConnectionApn=" + mDataConnectionApn);
- pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
- pw.println(" mDataConnectionNetworkCapabilities=" +
- mDataConnectionNetworkCapabilities);
+ for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+ pw.println(" Phone Id=" + i);
+ pw.println(" mCallState=" + mCallState[i]);
+ pw.println(" mCallIncomingNumber=" + mCallIncomingNumber[i]);
+ pw.println(" mServiceState=" + mServiceState[i]);
+ pw.println(" mSignalStrength=" + mSignalStrength[i]);
+ pw.println(" mMessageWaiting=" + mMessageWaiting[i]);
+ pw.println(" mCallForwarding=" + mCallForwarding[i]);
+ pw.println(" mDataActivity=" + mDataActivity[i]);
+ pw.println(" mDataConnectionState=" + mDataConnectionState[i]);
+ pw.println(" mDataConnectionPossible=" + mDataConnectionPossible[i]);
+ pw.println(" mDataConnectionReason=" + mDataConnectionReason[i]);
+ pw.println(" mDataConnectionApn=" + mDataConnectionApn[i]);
+ pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
+ pw.println(" mDataConnectionNetworkCapabilities=" +
+ mDataConnectionNetworkCapabilities[i]);
+ pw.println(" mCellLocation=" + mCellLocation[i]);
+ pw.println(" mCellInfo=" + mCellInfo.get(i));
+ }
pw.println(" mDefaultSubId=" + mDefaultSubId);
- pw.println(" mCellLocation=" + mCellLocation);
- pw.println(" mCellInfo=" + mCellInfo);
pw.println(" mDcRtInfo=" + mDcRtInfo);
pw.println("registrations: count=" + recordCount);
for (Record r : mRecords) {
pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
+ pw.println("is Legacy = " + r.isLegacyApp + " subId = " + r.subId);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 79cb60e..1316da1 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2809,7 +2809,7 @@
void ensurePackageDexOpt(String packageName) {
IPackageManager pm = AppGlobals.getPackageManager();
try {
- if (pm.performDexOpt(packageName)) {
+ if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) {
mDidDexOpt = true;
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 01a21f4..ede3dab 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -203,39 +203,32 @@
}
/**
- * Send a volume adjustment to the session owner.
+ * Send a volume adjustment to the session owner. Direction must be one of
+ * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+ * {@link AudioManager#ADJUST_SAME}.
*
- * @param delta The amount to adjust the volume by.
+ * @param direction The direction to adjust volume in.
*/
- public void adjustVolumeBy(int delta, int flags) {
+ public void adjustVolume(int direction, int flags) {
if (isPlaybackActive(false)) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
+ if (direction > 1) {
+ direction = 1;
+ } else if (direction < -1) {
+ direction = -1;
+ }
if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
- if (delta == 0) {
- mAudioManager.adjustStreamVolume(mAudioStream, delta, flags);
- } else {
- int direction = 0;
- int steps = delta;
- if (delta > 0) {
- direction = 1;
- } else if (delta < 0) {
- direction = -1;
- steps = -delta;
- }
- for (int i = 0; i < steps; i++) {
- mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
- }
- }
+ mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
} else {
if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
// Nothing to do, the volume cannot be changed
return;
}
- mSessionCb.adjustVolumeBy(delta);
+ mSessionCb.adjustVolume(direction);
int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
- mOptimisticVolume = volumeBefore + delta;
+ mOptimisticVolume = volumeBefore + direction;
mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
@@ -752,11 +745,11 @@
}
}
- public void adjustVolumeBy(int delta) {
+ public void adjustVolume(int direction) {
try {
- mCb.onAdjustVolumeBy(delta);
+ mCb.onAdjustVolume(direction);
} catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+ Slog.e(TAG, "Remote failure in adjustVolume.", e);
}
}
@@ -764,7 +757,7 @@
try {
mCb.onSetVolumeTo(value);
} catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+ Slog.e(TAG, "Remote failure in setVolumeTo.", e);
}
}
}
@@ -838,10 +831,10 @@
}
@Override
- public void adjustVolumeBy(int delta, int flags) {
+ public void adjustVolume(int direction, int flags) {
final long token = Binder.clearCallingIdentity();
try {
- MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+ MediaSessionRecord.this.adjustVolume(direction, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 4c475d9..a2dd15e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -681,7 +681,7 @@
}
@Override
- public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
+ public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -690,7 +690,7 @@
synchronized (mLock) {
MediaSessionRecord session = mPriorityStack
.getDefaultVolumeSession(mCurrentUserId);
- dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
+ dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -764,11 +764,11 @@
return resolvedUserId;
}
- private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
+ private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
MediaSessionRecord session) {
if (DEBUG) {
String sessionInfo = session == null ? null : session.getSessionInfo().toString();
- Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+ Log.d(TAG, "Adjusting session " + sessionInfo + " by " + direction + ". flags=" + flags
+ ", suggestedStream=" + suggestedStream);
}
@@ -780,28 +780,13 @@
}
}
try {
- if (delta == 0) {
- mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
- getContext().getOpPackageName());
- } else {
- int direction = 0;
- int steps = delta;
- if (delta > 0) {
- direction = 1;
- } else if (delta < 0) {
- direction = -1;
- steps = -delta;
- }
- for (int i = 0; i < steps; i++) {
- mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
- flags, getContext().getOpPackageName());
- }
- }
+ mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
+ getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
} else {
- session.adjustVolumeBy(delta, flags);
+ session.adjustVolume(direction, flags);
if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
&& mRvc != null) {
try {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 1d53016..355f34f 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -75,7 +75,7 @@
schedule(BackgroundDexOptService.this);
return;
}
- pm.performDexOpt(pkg, false);
+ pm.performDexOpt(pkg, null /* instruction set */, false);
}
// ran to completion, so we abandon our timeslice and do not reschedule
jobFinished(jobParams, false);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 190e87c..db915e2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -76,6 +76,10 @@
@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
+ /** Historical sessions kept around for debugging purposes */
+ @GuardedBy("mSessions")
+ private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
+
private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
private static final FilenameFilter sStageFilter = new FilenameFilter() {
@@ -344,18 +348,29 @@
}
void dump(IndentingPrintWriter pw) {
- pw.println("Active install sessions:");
- pw.increaseIndent();
synchronized (mSessions) {
- final int N = mSessions.size();
+ pw.println("Active install sessions:");
+ pw.increaseIndent();
+ int N = mSessions.size();
for (int i = 0; i < N; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
session.dump(pw);
pw.println();
}
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("Historical install sessions:");
+ pw.increaseIndent();
+ N = mHistoricalSessions.size();
+ for (int i = 0; i < N; i++) {
+ final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
+ session.dump(pw);
+ pw.println();
+ }
+ pw.println();
+ pw.decreaseIndent();
}
- pw.println();
- pw.decreaseIndent();
}
class Callback {
@@ -367,6 +382,7 @@
notifySessionFinished(session.sessionId, success);
synchronized (mSessions) {
mSessions.remove(session.sessionId);
+ mHistoricalSessions.put(session.sessionId, session);
}
writeSessionsAsync();
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 31d9704..0e6a3f0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -65,6 +65,7 @@
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String TAG = "PackageInstaller";
+ private static final boolean LOGD = true;
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -435,35 +436,25 @@
*/
private void spliceExistingFilesIntoStage() throws PackageManagerException {
final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
- final File existingDir = new File(app.getBaseCodePath());
- try {
- linkTreeIgnoringExisting(existingDir, sessionStageDir);
- } catch (ErrnoException e) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Failed to splice into stage");
- }
- }
+ int n = 0;
+ final File[] oldFiles = new File(app.getCodePath()).listFiles();
+ if (!ArrayUtils.isEmpty(oldFiles)) {
+ for (File oldFile : oldFiles) {
+ if (!PackageParser.isApkFile(oldFile)) continue;
- /**
- * Recursively hard link all files from source directory tree to target.
- * When a file already exists in the target tree, it leaves that file
- * intact.
- */
- private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
- final File[] sourceContents = sourceDir.listFiles();
- if (ArrayUtils.isEmpty(sourceContents)) return;
-
- for (File sourceFile : sourceContents) {
- final File targetFile = new File(targetDir, sourceFile.getName());
-
- if (sourceFile.isDirectory()) {
- targetFile.mkdir();
- linkTreeIgnoringExisting(sourceFile, targetFile);
- } else {
- Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+ final File newFile = new File(sessionStageDir, oldFile.getName());
+ try {
+ Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
+ n++;
+ } catch (ErrnoException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to splice into stage", e);
+ }
}
}
+
+ if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6513c88..727cff0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26,7 +26,6 @@
import static android.content.pm.PackageManager.INSTALL_EXTERNAL;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
-import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -4542,24 +4541,29 @@
}
PackageParser.Package p = pkg;
synchronized (mInstallLock) {
- if (p.mDexOptNeeded) {
- performDexOptLI(p, false /* force dex */, false /* defer */,
- true /* include dependencies */);
- }
+ performDexOptLI(p, null /* instruction sets */, false /* force dex */, false /* defer */,
+ true /* include dependencies */);
}
}
}
}
@Override
- public boolean performDexOpt(String packageName) {
- enforceSystemOrRoot("Only the system can request dexopt be performed");
- return performDexOpt(packageName, true);
+ public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
+ return performDexOpt(packageName, instructionSet, true);
}
- public boolean performDexOpt(String packageName, boolean updateUsage) {
+ private static String getPrimaryInstructionSet(ApplicationInfo info) {
+ if (info.primaryCpuAbi == null) {
+ return getPreferredInstructionSet();
+ }
+ return VMRuntime.getInstructionSet(info.primaryCpuAbi);
+ }
+
+ public boolean performDexOpt(String packageName, String instructionSet, boolean updateUsage) {
PackageParser.Package p;
+ final String targetInstructionSet;
synchronized (mPackages) {
p = mPackages.get(packageName);
if (p == null) {
@@ -4569,13 +4573,17 @@
p.mLastPackageUsageTimeInMills = System.currentTimeMillis();
}
mPackageUsage.write(false);
- if (!p.mDexOptNeeded) {
+
+ targetInstructionSet = instructionSet != null ? instructionSet :
+ getPrimaryInstructionSet(p.applicationInfo);
+ if (p.mDexOptPerformed.contains(targetInstructionSet)) {
return false;
}
}
synchronized (mInstallLock) {
- return performDexOptLI(p, false /* force dex */, false /* defer */,
+ final String[] instructionSets = new String[] { targetInstructionSet };
+ return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */,
true /* include dependencies */) == DEX_OPT_PERFORMED;
}
}
@@ -4585,9 +4593,9 @@
synchronized (mPackages) {
for (PackageParser.Package p : mPackages.values()) {
if (DEBUG_DEXOPT) {
- Log.i(TAG, p.packageName + " mDexOptNeeded=" + p.mDexOptNeeded);
+ Log.i(TAG, p.packageName + " mDexOptPerformed=" + p.mDexOptPerformed.toArray());
}
- if (!p.mDexOptNeeded) {
+ if (!p.mDexOptPerformed.isEmpty()) {
continue;
}
if (pkgs == null) {
@@ -4655,6 +4663,10 @@
// 3.) we are skipping an unneeded dexopt
for (String path : paths) {
for (String instructionSet : instructionSets) {
+ if (!forceDex && pkg.mDexOptPerformed.contains(instructionSet)) {
+ continue;
+ }
+
try {
final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
pkg.packageName, instructionSet, defer);
@@ -4669,10 +4681,10 @@
// Don't bother running dexopt again if we failed, it will probably
// just result in an error again. Also, don't bother dexopting for other
// paths & ISAs.
- pkg.mDexOptNeeded = false;
return DEX_OPT_FAILED;
} else {
performedDexOpt = true;
+ pkg.mDexOptPerformed.add(instructionSet);
}
}
@@ -4706,7 +4718,6 @@
// deferred dex-opt. We've either dex-opted one more paths or instruction sets or
// we've skipped all of them because they are up to date. In both cases this
// package doesn't need dexopt any longer.
- pkg.mDexOptNeeded = false;
return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
@@ -4762,8 +4773,8 @@
return allInstructionSets;
}
- private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
- boolean inclDependencies) {
+ private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets,
+ boolean forceDex, boolean defer, boolean inclDependencies) {
HashSet<String> done;
if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
done = new HashSet<String>();
@@ -4771,7 +4782,7 @@
} else {
done = null;
}
- return performDexOptLI(pkg, null /* target instruction sets */, forceDex, defer, done);
+ return performDexOptLI(pkg, instructionSets, forceDex, defer, done);
}
private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
@@ -5569,7 +5580,7 @@
}
if ((scanMode&SCAN_NO_DEX) == 0) {
- if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+ if (performDexOptLI(pkg, null /* instruction sets */, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
removeDataDirsLI(pkg.packageName);
@@ -5648,7 +5659,8 @@
if ((scanMode&SCAN_NO_DEX) == 0) {
for (int i=0; i<clientLibPkgs.size(); i++) {
PackageParser.Package clientPkg = clientLibPkgs.get(i);
- if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+ if (performDexOptLI(clientPkg, null /* instruction sets */,
+ forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
removeDataDirsLI(pkg.packageName);
@@ -6128,7 +6140,8 @@
ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi);
- if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) {
+ if (performDexOptLI(ps.pkg, null /* instruction sets */, forceDexOpt,
+ deferDexOpt, true) == DEX_OPT_FAILED) {
ps.primaryCpuAbiString = null;
ps.pkg.applicationInfo.primaryCpuAbi = null;
return;
@@ -10013,9 +10026,6 @@
}
}
- // Nuke any cached code
- deleteCodeCacheDirsLI(pkgName);
-
boolean sysPkg = (isSystemApp(oldPackage));
if (sysPkg) {
replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
@@ -10053,6 +10063,7 @@
deletedPkg = false;
} else {
// Successfully deleted the old package. Now proceed with re-installation
+ deleteCodeCacheDirsLI(pkgName);
try {
final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
scanMode | SCAN_UPDATE_TIME, System.currentTimeMillis(), user, abiOverride);
@@ -10164,6 +10175,8 @@
}
// Successfully disabled the old package. Now proceed with re-installation
+ deleteCodeCacheDirsLI(packageName);
+
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -10235,7 +10248,7 @@
* remove the target to make sure there isn't a stale
* file from a previous version of the package.
*/
- newPackage.mDexOptNeeded = true;
+ newPackage.mDexOptPerformed.clear();
mInstaller.rmdex(oldCodePath, instructionSet);
mInstaller.rmdex(newPackage.baseCodePath, instructionSet);
}
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index fb820f0..db834a4 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -44,9 +44,9 @@
public void onDestroyed(Connection c) {}
public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
public void onParentConnectionChanged(Connection c, Connection parent) {}
- public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
- public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {}
- public void onSetStatusHints(Connection c, StatusHints statusHints) {}
+ public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {}
+ public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
+ public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
}
public final class State {
@@ -321,7 +321,7 @@
public final void setCallVideoProvider(CallVideoProvider callVideoProvider) {
mCallVideoProvider = callVideoProvider;
for (Listener l : mListeners) {
- l.onSetCallVideoProvider(this, callVideoProvider);
+ l.onCallVideoProviderChanged(this, callVideoProvider);
}
}
@@ -415,7 +415,7 @@
public final void setAudioModeIsVoip(boolean isVoip) {
mAudioModeIsVoip = isVoip;
for (Listener l : mListeners) {
- l.onSetAudioModeIsVoip(this, isVoip);
+ l.onAudioModeIsVoipChanged(this, isVoip);
}
}
@@ -427,7 +427,7 @@
public final void setStatusHints(StatusHints statusHints) {
mStatusHints = statusHints;
for (Listener l : mListeners) {
- l.onSetStatusHints(this, statusHints);
+ l.onStatusHintsChanged(this, statusHints);
}
}
@@ -516,7 +516,6 @@
*/
protected void onPhoneAccountClicked() {}
-
private void addChild(Connection connection) {
Log.d(this, "adding child %s", connection);
mChildConnections.add(connection);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 178cee8..2a6804b 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -364,19 +364,19 @@
}
@Override
- public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {
+ public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {
String id = mIdByConnection.get(c);
mAdapter.setCallVideoProvider(id, callVideoProvider);
}
@Override
- public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {
+ public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
String id = mIdByConnection.get(c);
mAdapter.setAudioModeIsVoip(id, isVoip);
}
@Override
- public void onSetStatusHints(Connection c, StatusHints statusHints) {
+ public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
String id = mIdByConnection.get(c);
mAdapter.setStatusHints(id, statusHints);
}
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index a94841f..b50c1d7 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -121,4 +121,83 @@
* wait for user confirmation before proceeding.
*/
public static final char DTMF_CHARACTER_WAIT = ';';
+
+ /**
+ * TTY (teletypewriter) mode is off.
+ *
+ * @hide
+ */
+ public static final int TTY_MODE_OFF = 0;
+
+ /**
+ * TTY (teletypewriter) mode is on. The speaker is off and the microphone is muted. The user
+ * will communicate with the remote party by sending and receiving text messages.
+ *
+ * @hide
+ */
+ public static final int TTY_MODE_FULL = 1;
+
+ /**
+ * TTY (teletypewriter) mode is in hearing carryover mode (HCO). The microphone is muted but the
+ * speaker is on. The user will communicate with the remote party by sending text messages and
+ * hearing an audible reply.
+ *
+ * @hide
+ */
+ public static final int TTY_MODE_HCO = 2;
+
+ /**
+ * TTY (teletypewriter) mode is in voice carryover mode (VCO). The speaker is off but the
+ * microphone is still on. User will communicate with the remote party by speaking and receiving
+ * text message replies.
+ *
+ * @hide
+ */
+ public static final int TTY_MODE_VCO = 3;
+
+ /**
+ * Broadcast intent action indicating that the current TTY mode has changed. An intent extra
+ * provides this state as an int.
+ * @see #EXTRA_CURRENT_TTY_MODE
+ *
+ * @hide
+ */
+ public static final String ACTION_CURRENT_TTY_MODE_CHANGED =
+ "android.telecomm.intent.action.CURRENT_TTY_MODE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates the current TTY mode.
+ * Valid modes are:
+ * - {@link #TTY_MODE_OFF}
+ * - {@link #TTY_MODE_FULL}
+ * - {@link #TTY_MODE_HCO}
+ * - {@link #TTY_MODE_VCO}
+ *
+ * @hide
+ */
+ public static final String EXTRA_CURRENT_TTY_MODE =
+ "android.telecomm.intent.extra.CURRENT_TTY_MODE";
+
+ /**
+ * Broadcast intent action indicating that the TTY preferred operating mode
+ * has changed. An intent extra provides the new mode as an int.
+ * @see #EXTRA_TTY_PREFERRED_MODE
+ *
+ * @hide
+ */
+ public static final String ACTION_TTY_PREFERRED_MODE_CHANGED =
+ "android.telecomm.intent.action.TTY_PREFERRED_MODE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates preferred TTY mode.
+ * Valid modes are:
+ * - {@link #TTY_MODE_OFF}
+ * - {@link #TTY_MODE_FULL}
+ * - {@link #TTY_MODE_HCO}
+ * - {@link #TTY_MODE_VCO}
+ *
+ * @hide
+ */
+ public static final String EXTRA_TTY_PREFERRED_MODE =
+ "android.telecomm.intent.extra.TTY_PREFERRED";
}
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index fcd2eba..8bf80bb 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -28,21 +28,9 @@
/**
* Provides access to Telecomm-related functionality.
* TODO(santoscordon): Move this all into PhoneManager.
- * @hide
*/
public class TelecommManager {
- /**
- * The extra used with an {@link android.content.Intent#ACTION_CALL} or
- * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a {@link PhoneAccount}
- * to use when making the call.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getParcelableExtra(String)}.
- */
- public static final String EXTRA_PHONE_ACCOUNT = "account";
-
private static final String TAG = "TelecommManager";
private static final String TELECOMM_SERVICE_NAME = "telecomm";
@@ -138,8 +126,6 @@
* Remove all Accounts for a given package from the system.
*
* @param packageName A package name that may have registered Accounts.
- *
- * @hide
*/
@SystemApi
public void clearAccounts(String packageName) {
@@ -254,6 +240,44 @@
}
}
+ /**
+ * Returns whether TTY is supported on this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isTtySupported() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().isTtySupported();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current TTY mode of the device. For TTY to be on the user must enable it in
+ * settings and have a wired headset plugged in. Valid modes are:
+ * - {@link android.telecomm.TelecommConstants#TTY_MODE_OFF}
+ * - {@link android.telecomm.TelecommConstants#TTY_MODE_FULL}
+ * - {@link android.telecomm.TelecommConstants#TTY_MODE_HCO}
+ * - {@link android.telecomm.TelecommConstants#TTY_MODE_VCO}
+ *
+ * @hide
+ */
+ public int getCurrentTtyMode() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getCurrentTtyMode();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
+ }
+ return TelecommConstants.TTY_MODE_OFF;
+ }
+
private ITelecommService getTelecommService() {
return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
}
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 3334385..43caa1e 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -101,4 +101,14 @@
* @see PhoneManager#handlePinMmi
*/
boolean handlePinMmi(String dialString);
+
+ /**
+ * @see TelecomManager#isTtySupported
+ */
+ boolean isTtySupported();
+
+ /**
+ * @see TelecomManager#getCurrentTtyMode
+ */
+ int getCurrentTtyMode();
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 9cbb455..b47cc3c 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -161,7 +161,7 @@
Log.e(TAG, "Error getting session", e);
return;
}
- mController = MediaController.fromToken(token);
+ mController = new MediaController(token);
mContext.setMediaController(mController);
mController.addCallback(mControllerCb, mHandler);
mTransportControls = mController.getTransportControls();
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 78353b2..feecfde 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.support.media.protocols.MediaPlayerProtocol;
import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
@@ -84,7 +85,7 @@
.build());
mRouter.setRoutingCallback(new RoutingCallback(), null);
- mSession = man.createSession("OneMedia");
+ mSession = new MediaSession(mContext, "OneMedia");
mSession.addCallback(mCallback);
mSession.addTransportControlsCallback(new TransportCallback());
mSession.setPlaybackState(mPlaybackState);
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
similarity index 98%
rename from tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
rename to tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
index 30fb1b8..18d7755 100644
--- a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
@@ -19,7 +19,7 @@
android:drawable="@drawable/vector_drawable12" />
<item android:id="@+id/off"
android:drawable="@drawable/vector_drawable12" />
- <transition android:fromId="@+id/off" android:toId="@+id/on">
+ <transition android:fromId="@+id/off" android:toId="@+id/on" android:reversible="true">
<animated-vector android:drawable="@drawable/vector_drawable12">
<target
android:name="pie1"
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml
new file mode 100644
index 0000000..6a67b02
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/on" android:state_checked="true"
+ android:drawable="@drawable/vector_drawable_grouping_1" />
+ <item android:id="@+id/off"
+ android:drawable="@drawable/vector_drawable_grouping_1" />
+ <transition android:fromId="@+id/off" android:toId="@+id/on"
+ android:drawable="@drawable/animation_vector_drawable_grouping_1"
+ android:reversible="true">
+ </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml
new file mode 100644
index 0000000..65cf25b
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/on" android:state_checked="true"
+ android:drawable="@drawable/vector_drawable_favorite" />
+ <item android:id="@+id/off"
+ android:drawable="@drawable/vector_drawable_favorite" />
+ <transition android:fromId="@+id/off" android:toId="@+id/on"
+ android:drawable="@drawable/animation_vector_drawable_favorite"
+ android:reversible="true">
+ </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
index 0ae0136..566cc4b 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
@@ -27,7 +27,9 @@
private static final String LOGCAT = "AnimatedStateVectorDrawableTest";
protected int[] icon = {
- R.drawable.state_animation_vector_drawable
+ R.drawable.state_animation_vector_drawable01,
+ R.drawable.state_animation_vector_drawable02,
+ R.drawable.state_animation_vector_drawable03,
};
@Override
@@ -37,7 +39,7 @@
ScrollView scrollView = new ScrollView(this);
GridLayout container = new GridLayout(this);
scrollView.addView(container);
- container.setColumnCount(1);
+ container.setColumnCount(5);
for (int i = 0; i < icon.length; i++) {
CheckBox button = new CheckBox(this);
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
new file mode 100644
index 0000000..a3ec2cc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
@@ -0,0 +1,761 @@
+/*
+ * 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 android.graphics;
+
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/*
+ * (non-Javadoc)
+ * The class is adapted from a demo tool for Blending Modes written by
+ * Romain Guy (romainguy@android.com). The tool is available at
+ * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
+ */
+public final class BlendComposite implements Composite {
+ public enum BlendingMode {
+ NORMAL,
+ AVERAGE,
+ MULTIPLY,
+ SCREEN,
+ DARKEN,
+ LIGHTEN,
+ OVERLAY,
+ HARD_LIGHT,
+ SOFT_LIGHT,
+ DIFFERENCE,
+ NEGATION,
+ EXCLUSION,
+ COLOR_DODGE,
+ INVERSE_COLOR_DODGE,
+ SOFT_DODGE,
+ COLOR_BURN,
+ INVERSE_COLOR_BURN,
+ SOFT_BURN,
+ REFLECT,
+ GLOW,
+ FREEZE,
+ HEAT,
+ ADD,
+ SUBTRACT,
+ STAMP,
+ RED,
+ GREEN,
+ BLUE,
+ HUE,
+ SATURATION,
+ COLOR,
+ LUMINOSITY
+ }
+
+ public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL);
+ public static final BlendComposite Average = new BlendComposite(BlendingMode.AVERAGE);
+ public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY);
+ public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN);
+ public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN);
+ public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN);
+ public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY);
+ public static final BlendComposite HardLight = new BlendComposite(BlendingMode.HARD_LIGHT);
+ public static final BlendComposite SoftLight = new BlendComposite(BlendingMode.SOFT_LIGHT);
+ public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE);
+ public static final BlendComposite Negation = new BlendComposite(BlendingMode.NEGATION);
+ public static final BlendComposite Exclusion = new BlendComposite(BlendingMode.EXCLUSION);
+ public static final BlendComposite ColorDodge = new BlendComposite(BlendingMode.COLOR_DODGE);
+ public static final BlendComposite InverseColorDodge = new BlendComposite(BlendingMode.INVERSE_COLOR_DODGE);
+ public static final BlendComposite SoftDodge = new BlendComposite(BlendingMode.SOFT_DODGE);
+ public static final BlendComposite ColorBurn = new BlendComposite(BlendingMode.COLOR_BURN);
+ public static final BlendComposite InverseColorBurn = new BlendComposite(BlendingMode.INVERSE_COLOR_BURN);
+ public static final BlendComposite SoftBurn = new BlendComposite(BlendingMode.SOFT_BURN);
+ public static final BlendComposite Reflect = new BlendComposite(BlendingMode.REFLECT);
+ public static final BlendComposite Glow = new BlendComposite(BlendingMode.GLOW);
+ public static final BlendComposite Freeze = new BlendComposite(BlendingMode.FREEZE);
+ public static final BlendComposite Heat = new BlendComposite(BlendingMode.HEAT);
+ public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD);
+ public static final BlendComposite Subtract = new BlendComposite(BlendingMode.SUBTRACT);
+ public static final BlendComposite Stamp = new BlendComposite(BlendingMode.STAMP);
+ public static final BlendComposite Red = new BlendComposite(BlendingMode.RED);
+ public static final BlendComposite Green = new BlendComposite(BlendingMode.GREEN);
+ public static final BlendComposite Blue = new BlendComposite(BlendingMode.BLUE);
+ public static final BlendComposite Hue = new BlendComposite(BlendingMode.HUE);
+ public static final BlendComposite Saturation = new BlendComposite(BlendingMode.SATURATION);
+ public static final BlendComposite Color = new BlendComposite(BlendingMode.COLOR);
+ public static final BlendComposite Luminosity = new BlendComposite(BlendingMode.LUMINOSITY);
+
+ private float alpha;
+ private BlendingMode mode;
+
+ private BlendComposite(BlendingMode mode) {
+ this(mode, 1.0f);
+ }
+
+ private BlendComposite(BlendingMode mode, float alpha) {
+ this.mode = mode;
+ setAlpha(alpha);
+ }
+
+ public static BlendComposite getInstance(BlendingMode mode) {
+ return new BlendComposite(mode);
+ }
+
+ public static BlendComposite getInstance(BlendingMode mode, float alpha) {
+ return new BlendComposite(mode, alpha);
+ }
+
+ public BlendComposite derive(BlendingMode mode) {
+ return this.mode == mode ? this : new BlendComposite(mode, getAlpha());
+ }
+
+ public BlendComposite derive(float alpha) {
+ return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha);
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ public BlendingMode getMode() {
+ return mode;
+ }
+
+ private void setAlpha(float alpha) {
+ if (alpha < 0.0f || alpha > 1.0f) {
+ throw new IllegalArgumentException(
+ "alpha must be comprised between 0.0f and 1.0f");
+ }
+
+ this.alpha = alpha;
+ }
+
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(alpha) * 31 + mode.ordinal();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof BlendComposite)) {
+ return false;
+ }
+
+ BlendComposite bc = (BlendComposite) obj;
+
+ if (mode != bc.mode) {
+ return false;
+ }
+
+ return alpha == bc.alpha;
+ }
+
+ public CompositeContext createContext(ColorModel srcColorModel,
+ ColorModel dstColorModel,
+ RenderingHints hints) {
+ return new BlendingContext(this);
+ }
+
+ private static final class BlendingContext implements CompositeContext {
+ private final Blender blender;
+ private final BlendComposite composite;
+
+ private BlendingContext(BlendComposite composite) {
+ this.composite = composite;
+ this.blender = Blender.getBlenderFor(composite);
+ }
+
+ public void dispose() {
+ }
+
+ public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+ if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+ dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+ dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
+ throw new IllegalStateException(
+ "Source and destination must store pixels as INT.");
+ }
+
+ int width = Math.min(src.getWidth(), dstIn.getWidth());
+ int height = Math.min(src.getHeight(), dstIn.getHeight());
+
+ float alpha = composite.getAlpha();
+
+ int[] srcPixel = new int[4];
+ int[] dstPixel = new int[4];
+ int[] result = new int[4];
+ int[] srcPixels = new int[width];
+ int[] dstPixels = new int[width];
+
+ for (int y = 0; y < height; y++) {
+ dstIn.getDataElements(0, y, width, 1, dstPixels);
+ if (alpha != 0) {
+ src.getDataElements(0, y, width, 1, srcPixels);
+ for (int x = 0; x < width; x++) {
+ // pixels are stored as INT_ARGB
+ // our arrays are [R, G, B, A]
+ int pixel = srcPixels[x];
+ srcPixel[0] = (pixel >> 16) & 0xFF;
+ srcPixel[1] = (pixel >> 8) & 0xFF;
+ srcPixel[2] = (pixel ) & 0xFF;
+ srcPixel[3] = (pixel >> 24) & 0xFF;
+
+ pixel = dstPixels[x];
+ dstPixel[0] = (pixel >> 16) & 0xFF;
+ dstPixel[1] = (pixel >> 8) & 0xFF;
+ dstPixel[2] = (pixel ) & 0xFF;
+ dstPixel[3] = (pixel >> 24) & 0xFF;
+
+ result = blender.blend(srcPixel, dstPixel, result);
+
+ // mixes the result with the opacity
+ if (alpha == 1) {
+ dstPixels[x] = (result[3] & 0xFF) << 24 |
+ (result[0] & 0xFF) << 16 |
+ (result[1] & 0xFF) << 8 |
+ result[2] & 0xFF;
+ } else {
+ dstPixels[x] =
+ ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 |
+ ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 |
+ ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 |
+ (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
+ }
+
+ }
+ }
+ dstOut.setDataElements(0, y, width, 1, dstPixels);
+ }
+ }
+ }
+
+ private static abstract class Blender {
+ public abstract int[] blend(int[] src, int[] dst, int[] result);
+
+ private static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+ float var_R = (r / 255f);
+ float var_G = (g / 255f);
+ float var_B = (b / 255f);
+
+ float var_Min;
+ float var_Max;
+ float del_Max;
+
+ if (var_R > var_G) {
+ var_Min = var_G;
+ var_Max = var_R;
+ } else {
+ var_Min = var_R;
+ var_Max = var_G;
+ }
+ if (var_B > var_Max) {
+ var_Max = var_B;
+ }
+ if (var_B < var_Min) {
+ var_Min = var_B;
+ }
+
+ del_Max = var_Max - var_Min;
+
+ float H, S, L;
+ L = (var_Max + var_Min) / 2f;
+
+ if (del_Max - 0.01f <= 0.0f) {
+ H = 0;
+ S = 0;
+ } else {
+ if (L < 0.5f) {
+ S = del_Max / (var_Max + var_Min);
+ } else {
+ S = del_Max / (2 - var_Max - var_Min);
+ }
+
+ float del_R = (((var_Max - var_R) / 6f) + (del_Max / 2f)) / del_Max;
+ float del_G = (((var_Max - var_G) / 6f) + (del_Max / 2f)) / del_Max;
+ float del_B = (((var_Max - var_B) / 6f) + (del_Max / 2f)) / del_Max;
+
+ if (var_R == var_Max) {
+ H = del_B - del_G;
+ } else if (var_G == var_Max) {
+ H = (1 / 3f) + del_R - del_B;
+ } else {
+ H = (2 / 3f) + del_G - del_R;
+ }
+ if (H < 0) {
+ H += 1;
+ }
+ if (H > 1) {
+ H -= 1;
+ }
+ }
+
+ hsl[0] = H;
+ hsl[1] = S;
+ hsl[2] = L;
+ }
+
+ private static void HSLtoRGB(float h, float s, float l, int[] rgb) {
+ int R, G, B;
+
+ if (s - 0.01f <= 0.0f) {
+ R = (int) (l * 255.0f);
+ G = (int) (l * 255.0f);
+ B = (int) (l * 255.0f);
+ } else {
+ float var_1, var_2;
+ if (l < 0.5f) {
+ var_2 = l * (1 + s);
+ } else {
+ var_2 = (l + s) - (s * l);
+ }
+ var_1 = 2 * l - var_2;
+
+ R = (int) (255.0f * hue2RGB(var_1, var_2, h + (1.0f / 3.0f)));
+ G = (int) (255.0f * hue2RGB(var_1, var_2, h));
+ B = (int) (255.0f * hue2RGB(var_1, var_2, h - (1.0f / 3.0f)));
+ }
+
+ rgb[0] = R;
+ rgb[1] = G;
+ rgb[2] = B;
+ }
+
+ private static float hue2RGB(float v1, float v2, float vH) {
+ if (vH < 0.0f) {
+ vH += 1.0f;
+ }
+ if (vH > 1.0f) {
+ vH -= 1.0f;
+ }
+ if ((6.0f * vH) < 1.0f) {
+ return (v1 + (v2 - v1) * 6.0f * vH);
+ }
+ if ((2.0f * vH) < 1.0f) {
+ return (v2);
+ }
+ if ((3.0f * vH) < 2.0f) {
+ return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f);
+ }
+ return (v1);
+ }
+
+ public static Blender getBlenderFor(BlendComposite composite) {
+ switch (composite.getMode()) {
+ case NORMAL:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ System.arraycopy(src, 0, result, 0, 4);
+ return result;
+ }
+ };
+ case ADD:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 4; i++) {
+ result[i] = Math.min(255, src[i] + dst[i]);
+ }
+ return result;
+ }
+ };
+ case AVERAGE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = (src[i] + dst[i]) >> 1;
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case BLUE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ System.arraycopy(dst, 0, result, 0, 3);
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case COLOR:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ float[] srcHSL = new float[3];
+ RGBtoHSL(src[0], src[1], src[2], srcHSL);
+ float[] dstHSL = new float[3];
+ RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+ HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result);
+ result[3] = Math.min(255, src[3] + dst[3]);
+
+ return result;
+ }
+ };
+ case COLOR_BURN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = src[i] == 0 ? 0 :
+ Math.max(0, 255 - (((255 - dst[i]) << 8) / src[i]));
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case COLOR_DODGE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = src[i] == 255 ? 255 :
+ Math.min((dst[i] << 8) / (255 - src[i]), 255);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case DARKEN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = Math.min(src[i], dst[i]);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case DIFFERENCE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case EXCLUSION:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case FREEZE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = src[i] == 0 ? 0 :
+ Math.max(0, 255 - (255 - dst[i]) * (255 - dst[i]) / src[i]);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case GLOW:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = dst[i] == 255 ? 255 :
+ Math.min(255, src[i] * src[i] / (255 - dst[i]));
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case GREEN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0],
+ dst[1],
+ src[2],
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case HARD_LIGHT:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ src[0] < 128 ? dst[0] * src[0] >> 7 :
+ 255 - ((255 - src[0]) * (255 - dst[0]) >> 7),
+ src[1] < 128 ? dst[1] * src[1] >> 7 :
+ 255 - ((255 - src[1]) * (255 - dst[1]) >> 7),
+ src[2] < 128 ? dst[2] * src[2] >> 7 :
+ 255 - ((255 - src[2]) * (255 - dst[2]) >> 7),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case HEAT:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]),
+ dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]),
+ dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case HUE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ float[] srcHSL = new float[3];
+ RGBtoHSL(src[0], src[1], src[2], srcHSL);
+ float[] dstHSL = new float[3];
+ RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+ HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result);
+ result[3] = Math.min(255, src[3] + dst[3]);
+
+ return result;
+ }
+ };
+ case INVERSE_COLOR_BURN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0] == 0 ? 0 :
+ Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])),
+ dst[1] == 0 ? 0 :
+ Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])),
+ dst[2] == 0 ? 0 :
+ Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case INVERSE_COLOR_DODGE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0] == 255 ? 255 :
+ Math.min((src[0] << 8) / (255 - dst[0]), 255),
+ dst[1] == 255 ? 255 :
+ Math.min((src[1] << 8) / (255 - dst[1]), 255),
+ dst[2] == 255 ? 255 :
+ Math.min((src[2] << 8) / (255 - dst[2]), 255),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case LIGHTEN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = Math.max(src[i], dst[i]);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case LUMINOSITY:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ float[] srcHSL = new float[3];
+ RGBtoHSL(src[0], src[1], src[2], srcHSL);
+ float[] dstHSL = new float[3];
+ RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+ HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result);
+ result[3] = Math.min(255, src[3] + dst[3]);
+
+ return result;
+ }
+ };
+ case MULTIPLY:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = (src[i] * dst[i]) >> 8;
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case NEGATION:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ 255 - Math.abs(255 - dst[0] - src[0]),
+ 255 - Math.abs(255 - dst[1] - src[1]),
+ 255 - Math.abs(255 - dst[2] - src[2]),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case OVERLAY:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ for (int i = 0; i < 3; i++) {
+ result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 :
+ 255 - ((255 - dst[i]) * (255 - src[i]) >> 7);
+ }
+ result[3] = Math.min(255, src[3] + dst[3]);
+ return result;
+ }
+ };
+ case RED:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ src[0],
+ dst[1],
+ dst[2],
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case REFLECT:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])),
+ src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])),
+ src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case SATURATION:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ float[] srcHSL = new float[3];
+ RGBtoHSL(src[0], src[1], src[2], srcHSL);
+ float[] dstHSL = new float[3];
+ RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+ HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result);
+ result[3] = Math.min(255, src[3] + dst[3]);
+
+ return result;
+ }
+ };
+ case SCREEN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ 255 - ((255 - src[0]) * (255 - dst[0]) >> 8),
+ 255 - ((255 - src[1]) * (255 - dst[1]) >> 8),
+ 255 - ((255 - src[2]) * (255 - dst[2]) >> 8),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case SOFT_BURN:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0] + src[0] < 256 ?
+ (dst[0] == 255 ? 255 :
+ Math.min(255, (src[0] << 7) / (255 - dst[0]))) :
+ Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])),
+ dst[1] + src[1] < 256 ?
+ (dst[1] == 255 ? 255 :
+ Math.min(255, (src[1] << 7) / (255 - dst[1]))) :
+ Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])),
+ dst[2] + src[2] < 256 ?
+ (dst[2] == 255 ? 255 :
+ Math.min(255, (src[2] << 7) / (255 - dst[2]))) :
+ Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case SOFT_DODGE:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ dst[0] + src[0] < 256 ?
+ (src[0] == 255 ? 255 :
+ Math.min(255, (dst[0] << 7) / (255 - src[0]))) :
+ Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])),
+ dst[1] + src[1] < 256 ?
+ (src[1] == 255 ? 255 :
+ Math.min(255, (dst[1] << 7) / (255 - src[1]))) :
+ Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])),
+ dst[2] + src[2] < 256 ?
+ (src[2] == 255 ? 255 :
+ Math.min(255, (dst[2] << 7) / (255 - src[2]))) :
+ Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case SOFT_LIGHT:
+ break;
+ case STAMP:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)),
+ Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)),
+ Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ case SUBTRACT:
+ return new Blender() {
+ @Override
+ public int[] blend(int[] src, int[] dst, int[] result) {
+ return new int[] {
+ Math.max(0, src[0] + dst[0] - 256),
+ Math.max(0, src[1] + dst[1] - 256),
+ Math.max(0, src[2] + dst[2] - 256),
+ Math.min(255, src[3] + dst[3])
+ };
+ }
+ };
+ }
+ throw new IllegalArgumentException("Blender not implement for " +
+ composite.getMode().name());
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 7c8ef70..2ff0fc1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -20,6 +20,7 @@
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Bitmap.Config;
@@ -534,7 +535,8 @@
// set the color
graphics.setColor(new Color(color, true /*alpha*/));
- Composite composite = PorterDuffXfermode_Delegate.getComposite(mode, 0xFF);
+ Composite composite = PorterDuffUtility.getComposite(
+ PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
if (composite != null) {
graphics.setComposite(composite);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index ee90595..4ac376c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -21,11 +21,10 @@
import android.graphics.PorterDuff.Mode;
-import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite;
import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
/**
@@ -57,7 +56,7 @@
@Override
public boolean isSupported() {
- return getAlphaCompositeRule(mMode) != -1;
+ return true;
}
@Override
@@ -68,7 +67,7 @@
@Override
public void applyFilter(Graphics2D g, int width, int height) {
BufferedImage image = createFilterImage(width, height);
- g.setComposite(getComposite());
+ g.setComposite(getComposite(mMode, 0xFF));
g.drawImage(image, 0, 0, null);
}
@@ -101,49 +100,36 @@
return image;
}
- private AlphaComposite getComposite() {
- return AlphaComposite.getInstance(getAlphaCompositeRule(mMode));
- }
-
// For filtering the colors, the src image should contain the "color" only for pixel values
// which are not transparent in the target image. But, we are using a simple rectangular image
- // completely filled with color. Hence some AlphaComposite rules do not apply as intended.
- // However, in such cases, they can usually be mapped to some other mode, which produces an
+ // completely filled with color. Hence some Composite rules do not apply as intended. However,
+ // in such cases, they can usually be mapped to some other mode, which produces an
// equivalent result.
private Mode getCompatibleMode(Mode mode) {
Mode m = mode;
+ // Modes that are directly supported:
+ // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN,
+ // ADD, OVERLAY
switch (mode) {
- // Modes that are directly supported.
- case CLEAR:
- case DST:
- case SRC_IN:
- case DST_IN:
- case DST_OUT:
- case SRC_ATOP:
- break;
- // Modes that can be mapped to one of the supported modes.
- case SRC:
- m = Mode.SRC_IN;
- break;
- case SRC_OVER:
- m = Mode.SRC_ATOP;
- break;
- case DST_OVER:
- m = Mode.DST;
- break;
- case SRC_OUT:
- m = Mode.CLEAR;
- break;
- case DST_ATOP:
- m = Mode.DST_IN;
- break;
- case XOR:
- m = Mode.DST_OUT;
- break;
- // This mode is not supported, but used by Action Bar Overflow Popup Menus. We map this
- // to the closest supported mode, to prevent showing excessive warnings to the user.
- case MULTIPLY:
- m = Mode.SRC_IN;
+ // Modes that can be mapped to one of the supported modes.
+ case SRC:
+ m = Mode.SRC_IN;
+ break;
+ case SRC_OVER:
+ m = Mode.SRC_ATOP;
+ break;
+ case DST_OVER:
+ m = Mode.DST;
+ break;
+ case SRC_OUT:
+ m = Mode.CLEAR;
+ break;
+ case DST_ATOP:
+ m = Mode.DST_IN;
+ break;
+ case XOR:
+ m = Mode.DST_OUT;
+ break;
}
return m;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index f6c36b6..8825f84 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -16,17 +16,14 @@
package android.graphics;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.PorterDuff.Mode;
-import java.awt.AlphaComposite;
import java.awt.Composite;
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
/**
@@ -58,7 +55,7 @@
@Override
public Composite getComposite(int alpha) {
- return getComposite(mMode, alpha);
+ return PorterDuffUtility.getComposite(mMode, alpha);
}
@Override
@@ -72,9 +69,6 @@
return null;
}
- public static Composite getComposite(int mode, int alpha) {
- return getComposite(getPorterDuffMode(mode), alpha);
- }
// ---- native methods ----
@@ -90,17 +84,4 @@
mMode = getPorterDuffMode(mode);
}
- private static Composite getComposite(Mode mode, int alpha255) {
- float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
- int rule = getAlphaCompositeRule(mode);
- if (rule >= 0) {
- return AlphaComposite.getInstance(rule, alpha1);
- }
-
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
- null, null /*data*/);
-
- return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
- }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index bc53e93..9588035 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -19,11 +19,14 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
+import android.graphics.BlendComposite;
+import android.graphics.BlendComposite.BlendingMode;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter_Delegate;
import android.graphics.PorterDuffXfermode_Delegate;
import java.awt.AlphaComposite;
+import java.awt.Composite;
/**
* Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
@@ -51,46 +54,54 @@
}
/**
- * A utility method to convert the porterDuffMode to an int to be used as a rule for {@link
- * AlphaComposite}. If {@code AlphaComposite} doesn't support the mode, -1 is returned.
+ * A utility method to get the {@link Composite} that represents the filter for the given
+ * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes.
*/
- public static int getAlphaCompositeRule(Mode porterDuffMode) {
- switch (porterDuffMode) {
+ public static Composite getComposite(Mode mode, int alpha255) {
+ float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
+ switch (mode) {
case CLEAR:
- return AlphaComposite.CLEAR;
- case DARKEN:
- break;
- case DST:
- return AlphaComposite.DST;
- case DST_ATOP:
- return AlphaComposite.DST_ATOP;
- case DST_IN:
- return AlphaComposite.DST_IN;
- case DST_OUT:
- return AlphaComposite.DST_OUT;
- case DST_OVER:
- return AlphaComposite.DST_OVER;
- case LIGHTEN:
- break;
- case MULTIPLY:
- break;
- case SCREEN:
- break;
+ return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1);
case SRC:
- return AlphaComposite.SRC;
- case SRC_ATOP:
- return AlphaComposite.SRC_ATOP;
- case SRC_IN:
- return AlphaComposite.SRC_IN;
- case SRC_OUT:
- return AlphaComposite.SRC_OUT;
+ return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1);
+ case DST:
+ return AlphaComposite.getInstance(AlphaComposite.DST, alpha1);
case SRC_OVER:
- return AlphaComposite.SRC_OVER;
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+ case DST_OVER:
+ return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1);
+ case SRC_IN:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1);
+ case DST_IN:
+ return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1);
+ case SRC_OUT:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1);
+ case DST_OUT:
+ return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1);
+ case SRC_ATOP:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1);
+ case DST_ATOP:
+ return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1);
case XOR:
- return AlphaComposite.XOR;
- }
- // This is an unsupported mode.
- return -1;
+ return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1);
+ case DARKEN:
+ return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1);
+ case LIGHTEN:
+ return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1);
+ case MULTIPLY:
+ return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1);
+ case SCREEN:
+ return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1);
+ case ADD:
+ return BlendComposite.getInstance(BlendingMode.ADD, alpha1);
+ case OVERLAY:
+ return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1);
+ default:
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+ String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
+ null, null /*data*/);
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+ }
}
}