Merge "AVD now support path morphing."
diff --git a/Android.mk b/Android.mk
index 56e9df6..8f7779e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -175,6 +175,7 @@
core/java/android/nfc/INfcAdapterExtras.aidl \
core/java/android/nfc/INfcTag.aidl \
core/java/android/nfc/INfcCardEmulation.aidl \
+ core/java/android/nfc/INfcLockscreenDispatch.aidl \
core/java/android/os/IBatteryPropertiesListener.aidl \
core/java/android/os/IBatteryPropertiesRegistrar.aidl \
core/java/android/os/ICancellationSignal.aidl \
diff --git a/api/current.txt b/api/current.txt
index e85e1d3..a263946 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -957,6 +957,7 @@
field public static final int ratingBarStyleIndicator = 16843280; // 0x1010210
field public static final int ratingBarStyleSmall = 16842877; // 0x101007d
field public static final int readPermission = 16842759; // 0x1010007
+ field public static final int relinquishTaskIdentity = 16843896; // 0x1010478
field public static final int repeatCount = 16843199; // 0x10101bf
field public static final int repeatMode = 16843200; // 0x10101c0
field public static final int reqFiveWayNav = 16843314; // 0x1010232
@@ -2524,7 +2525,14 @@
public static final class R.transition {
ctor public R.transition();
+ field public static final int explode = 17760259; // 0x10f0003
+ field public static final int fade = 17760258; // 0x10f0002
+ field public static final int move = 17760257; // 0x10f0001
field public static final int no_transition = 17760256; // 0x10f0000
+ field public static final int slide_bottom = 17760260; // 0x10f0004
+ field public static final int slide_left = 17760263; // 0x10f0007
+ field public static final int slide_right = 17760262; // 0x10f0006
+ field public static final int slide_top = 17760261; // 0x10f0005
}
public static final class R.xml {
@@ -8085,6 +8093,7 @@
field public static final int FLAG_IMMERSIVE = 2048; // 0x800
field public static final int FLAG_MULTIPROCESS = 1; // 0x1
field public static final int FLAG_NO_HISTORY = 128; // 0x80
+ field public static final int FLAG_RELINQUISH_TASK_IDENTITY = 4096; // 0x1000
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10
field public static final int LAUNCH_MULTIPLE = 0; // 0x0
@@ -12834,6 +12843,7 @@
method public boolean isOutputSupportedFor(int);
method public static boolean isOutputSupportedFor(java.lang.Class<T>);
method public boolean isOutputSupportedFor(android.view.Surface);
+ field public static final long NO_MIN_FRAME_DURATION = 0L; // 0x0L
}
public final class TonemapCurve {
@@ -13795,6 +13805,7 @@
method public deprecated void setWiredHeadsetOn(boolean);
method public deprecated boolean shouldVibrate(int);
method public void startBluetoothSco();
+ method public void startBluetoothScoVirtualCall();
method public void stopBluetoothSco();
method public void unloadSoundEffects();
method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
@@ -14479,6 +14490,11 @@
method public java.lang.String getDefaultUrl();
}
+ public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
+ ctor public MediaDrm.MediaDrmStateException(int, java.lang.String);
+ method public int getErrorCode();
+ }
+
public static abstract interface MediaDrm.OnEventListener {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
@@ -15347,6 +15363,19 @@
ctor public UnsupportedSchemeException(java.lang.String);
}
+ public abstract class VolumeProvider {
+ ctor public VolumeProvider(int, int);
+ method public final int getMaxVolume();
+ method public final int getVolumeControl();
+ method public final void notifyVolumeChanged();
+ method public void onAdjustVolumeBy(int);
+ method public abstract int onGetCurrentVolume();
+ method public void onSetVolumeTo(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
}
package android.media.audiofx {
@@ -15713,6 +15742,7 @@
method public android.media.session.PlaybackState getPlaybackState();
method public int getRatingType();
method public android.media.session.MediaController.TransportControls getTransportControls();
+ method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
method public void removeCallback(android.media.session.MediaController.Callback);
method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
}
@@ -15736,6 +15766,14 @@
method public void stop();
}
+ public static final class MediaController.VolumeInfo {
+ method public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getVolumeControl();
+ method public int getVolumeType();
+ }
+
public final class MediaSession {
method public void addCallback(android.media.session.MediaSession.Callback);
method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
@@ -15753,9 +15791,11 @@
method public void setMetadata(android.media.MediaMetadata);
method public void setPlaybackState(android.media.session.PlaybackState);
method public void setPlaybackToLocal(int);
- method public void setPlaybackToRemote(android.media.session.RemoteVolumeProvider);
+ method public void setPlaybackToRemote(android.media.VolumeProvider);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int VOLUME_TYPE_LOCAL = 1; // 0x1
+ field public static final int VOLUME_TYPE_REMOTE = 2; // 0x2
}
public static abstract class MediaSession.Callback {
@@ -15826,19 +15866,6 @@
field public static final int STATE_STOPPED = 1; // 0x1
}
- public abstract class RemoteVolumeProvider {
- ctor public RemoteVolumeProvider(int, int);
- method public final int getMaxVolume();
- method public final int getVolumeControl();
- method public final void notifyVolumeChanged();
- method public void onAdjustVolumeBy(int);
- method public abstract int onGetCurrentVolume();
- method public void onSetVolumeTo(int);
- field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
- field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
- field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
- }
-
}
package android.media.tv {
@@ -17637,6 +17664,7 @@
method public boolean invokeBeam(android.app.Activity);
method public boolean isEnabled();
method public boolean isNdefPushEnabled();
+ method public boolean registerLockscreenDispatch(android.nfc.NfcAdapter.NfcLockscreenDispatch, int[]);
method public void setBeamPushUris(android.net.Uri[], android.app.Activity);
method public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
@@ -17672,6 +17700,10 @@
method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
}
+ public static abstract interface NfcAdapter.NfcLockscreenDispatch {
+ method public abstract boolean onTagDetected(android.nfc.Tag);
+ }
+
public static abstract interface NfcAdapter.OnNdefPushCompleteCallback {
method public abstract void onNdefPushComplete(android.nfc.NfcEvent);
}
@@ -23460,7 +23492,7 @@
field public static final java.lang.String CONTENT_DIRECTORY = "data";
}
- public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
+ public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.DataUsageStatColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
field public static final java.lang.String CONTENT_DIRECTORY = "entities";
field public static final java.lang.String DATA_ID = "data_id";
field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
@@ -27794,7 +27826,6 @@
field public static final java.lang.String ACTION_CALL_SERVICE;
field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER;
field public static final java.lang.String ACTION_CALL_SERVICE_SELECTOR;
- field public static final java.lang.String ACTION_CHANGE_DEFAULT_PHONE = "android.telecomm.ACTION_CHANGE_DEFAULT_PHONE";
field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
@@ -27802,7 +27833,6 @@
field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE";
field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS";
- field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6296dd1..6b2a0e2 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -52,13 +52,13 @@
);
}
-static SkBitmap::Config flinger2skia(PixelFormat f)
+static SkColorType flinger2skia(PixelFormat f)
{
switch (f) {
case PIXEL_FORMAT_RGB_565:
- return SkBitmap::kRGB_565_Config;
+ return kRGB_565_SkColorType;
default:
- return SkBitmap::kARGB_8888_Config;
+ return kN32_SkColorType;
}
}
@@ -174,9 +174,10 @@
if (base) {
if (png) {
+ const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f),
+ kPremul_SkAlphaType);
SkBitmap b;
- b.setConfig(flinger2skia(f), w, h, s*bytesPerPixel(f));
- b.setPixels((void*)base);
+ b.installPixels(info, const_cast<void*>(base), s*bytesPerPixel(f));
SkDynamicMemoryWStream stream;
SkImageEncoder::EncodeStream(&stream, b,
SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9132883..d0d0289 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -5935,14 +5936,21 @@
}
/**
- * Put this Activity in a mode where the user is locked to the
+ * Request to put this Activity in a mode where the user is locked to the
* current task.
*
* This will prevent the user from launching other apps, going to settings,
* or reaching the home screen.
*
- * Lock task mode will only start if the activity has been whitelisted by the
- * Device Owner through DevicePolicyManager#setLockTaskComponents.
+ * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true
+ * for this component then the app will go directly into Lock Task mode. The user
+ * will not be able to exit this mode until {@link Activity#stopLockTask()} is called.
+ *
+ * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
+ * then the system will prompt the user with a dialog requesting permission to enter
+ * this mode. When entered through this method the user can exit at any time by
+ * swiping down twice from the top of the screen. Calling stopLockTask will also
+ * exit the mode.
*/
public void startLockTask() {
try {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 56462ae..572d389 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2145,6 +2145,13 @@
return true;
}
+ case START_LOCK_TASK_BY_CURRENT: {
+ data.enforceInterface(IActivityManager.descriptor);
+ startLockTaskModeOnCurrent();
+ reply.writeNoException();
+ return true;
+ }
+
case STOP_LOCK_TASK_MODE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
stopLockTaskMode();
@@ -2152,6 +2159,13 @@
return true;
}
+ case STOP_LOCK_TASK_BY_CURRENT: {
+ data.enforceInterface(IActivityManager.descriptor);
+ stopLockTaskModeOnCurrent();
+ reply.writeNoException();
+ return true;
+ }
+
case IS_IN_LOCK_TASK_MODE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final boolean isInLockTaskMode = isInLockTaskMode();
@@ -4947,6 +4961,17 @@
}
@Override
+ public void startLockTaskModeOnCurrent() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(START_LOCK_TASK_BY_CURRENT, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public void stopLockTaskMode() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -4958,6 +4983,17 @@
}
@Override
+ public void stopLockTaskModeOnCurrent() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(STOP_LOCK_TASK_BY_CURRENT, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public boolean isInLockTaskMode() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index c7030b0..fa9b1e3 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -233,6 +233,13 @@
if (getViewsTransition() != null) {
getDecor().captureTransitioningViews(mTransitioningViews);
mTransitioningViews.removeAll(mSharedElements);
+ Rect r = new Rect();
+ for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mTransitioningViews.get(i);
+ if (!view.getGlobalVisibleRect(r)) {
+ mTransitioningViews.remove(i);
+ }
+ }
}
setEpicenter();
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index bf2d7e5..b630278 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -429,6 +429,9 @@
public IBinder getHomeActivityToken() throws RemoteException;
/** @hide */
+ public void startLockTaskModeOnCurrent() throws RemoteException;
+
+ /** @hide */
public void startLockTaskMode(int taskId) throws RemoteException;
/** @hide */
@@ -438,6 +441,9 @@
public void stopLockTaskMode() throws RemoteException;
/** @hide */
+ public void stopLockTaskModeOnCurrent() throws RemoteException;
+
+ /** @hide */
public boolean isInLockTaskMode() throws RemoteException;
/** @hide */
@@ -744,4 +750,6 @@
int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
+ int START_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+221;
+ int STOP_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+222;
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b78b9c9..c583998 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -188,6 +188,9 @@
endPerformanceSnapshot();
}
if (mPerfMetrics != null) {
+ if (results == null) {
+ results = new Bundle();
+ }
results.putAll(mPerfMetrics);
}
if (mUiAutomation != null) {
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 706ef04..4631323 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -31,12 +31,22 @@
* @hide
*/
public class BackupTransport {
+ // Zero return always means things are okay. If returned from
+ // getNextFullRestoreDataChunk(), it means that no data could be delivered at
+ // this time, but the restore is still running and the caller should simply
+ // retry.
public static final int TRANSPORT_OK = 0;
- public static final int TRANSPORT_ERROR = 1;
- public static final int TRANSPORT_NOT_INITIALIZED = 2;
- public static final int TRANSPORT_PACKAGE_REJECTED = 3;
- public static final int AGENT_ERROR = 4;
- public static final int AGENT_UNKNOWN = 5;
+
+ // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
+ // we've delivered the entire data stream for the current restore target.
+ public static final int NO_MORE_DATA = -1;
+
+ // Result codes that indicate real errors are negative and not -1
+ public static final int TRANSPORT_ERROR = -1000;
+ public static final int TRANSPORT_NOT_INITIALIZED = -1001;
+ public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
+ public static final int AGENT_ERROR = -1003;
+ public static final int AGENT_UNKNOWN = -1004;
IBackupTransport mBinderImpl = new TransportImpl();
/** @hide */
@@ -370,11 +380,14 @@
* @param socket The file descriptor that the transport will use for delivering the
* streamed archive. The transport must close this socket in all cases when returning
* from this method.
- * @return 0 when no more data for the current package is available. A positive value
- * indicates the presence of that many bytes to be delivered to the app. Any negative
- * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
- * indicating a fatal error condition that precludes further restore operations
- * on the current dataset.
+ * @return {@link #NO_MORE_DATA} when no more data for the current package is available.
+ * A positive value indicates the presence of that many bytes to be delivered to the app.
+ * A value of zero indicates that no data was deliverable at this time, but the restore
+ * is still running and the caller should retry. {@link #TRANSPORT_PACKAGE_REJECTED}
+ * means that the current package's restore operation should be aborted, but that
+ * the transport itself is still in a good state and so a multiple-package restore
+ * sequence can still be continued. Any other negative return value is treated as a
+ * fatal error condition that aborts all further restore operations on the current dataset.
*/
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return 0;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3dfa78b..e24dc84 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
/**
@@ -6821,72 +6822,12 @@
if (other == null) {
return false;
}
- if (mAction != other.mAction) {
- if (mAction != null) {
- if (!mAction.equals(other.mAction)) {
- return false;
- }
- } else {
- if (!other.mAction.equals(mAction)) {
- return false;
- }
- }
- }
- if (mData != other.mData) {
- if (mData != null) {
- if (!mData.equals(other.mData)) {
- return false;
- }
- } else {
- if (!other.mData.equals(mData)) {
- return false;
- }
- }
- }
- if (mType != other.mType) {
- if (mType != null) {
- if (!mType.equals(other.mType)) {
- return false;
- }
- } else {
- if (!other.mType.equals(mType)) {
- return false;
- }
- }
- }
- if (mPackage != other.mPackage) {
- if (mPackage != null) {
- if (!mPackage.equals(other.mPackage)) {
- return false;
- }
- } else {
- if (!other.mPackage.equals(mPackage)) {
- return false;
- }
- }
- }
- if (mComponent != other.mComponent) {
- if (mComponent != null) {
- if (!mComponent.equals(other.mComponent)) {
- return false;
- }
- } else {
- if (!other.mComponent.equals(mComponent)) {
- return false;
- }
- }
- }
- if (mCategories != other.mCategories) {
- if (mCategories != null) {
- if (!mCategories.equals(other.mCategories)) {
- return false;
- }
- } else {
- if (!other.mCategories.equals(mCategories)) {
- return false;
- }
- }
- }
+ if (!Objects.equals(this.mAction, other.mAction)) return false;
+ if (!Objects.equals(this.mData, other.mData)) return false;
+ if (!Objects.equals(this.mType, other.mType)) return false;
+ if (!Objects.equals(this.mPackage, other.mPackage)) return false;
+ if (!Objects.equals(this.mComponent, other.mComponent)) return false;
+ if (!Objects.equals(this.mCategories, other.mCategories)) return false;
return true;
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index abc8cde..bcf1e87 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -252,6 +252,13 @@
*/
public static final int FLAG_IMMERSIVE = 0x0800;
/**
+ * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+ * baseIntent replaced by the activity immediately above this. Each activity may further
+ * relinquish its identity to the activity above it using this flag. Set from the
+ * android.R.attr#relinquishTaskIdentity attribute.
+ */
+ public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
+ /**
* Bit in {@link #flags} indicating that tasks started with this activity are to be
* removed from the recent list of tasks when the last activity in the task is finished.
* {@link android.R.attr#autoRemoveFromRecents}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b5ceebe..9d871c5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2892,6 +2892,7 @@
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
parser.collectCertificates(pkg, 0);
+ parser.collectManifestDigest(pkg);
}
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 546f3a5..b40a441 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -744,6 +744,8 @@
* {@code AndroidManifest.xml}, {@code true} is returned.
*/
public void collectManifestDigest(Package pkg) throws PackageParserException {
+ pkg.manifestDigest = null;
+
// TODO: extend to gather digest for split APKs
try {
final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
@@ -2627,6 +2629,12 @@
false)) {
a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
}
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+ }
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 20dcf83..d2146ac 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -885,9 +885,9 @@
/**
* Extracts theme attributes from a typed array for later resolution using
- * {@link Theme#resolveAttributes(int[], int[])}. Removes the entries from
- * the typed array so that subsequent calls to typed getters will return the
- * default value without crashing.
+ * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
+ * Removes the entries from the typed array so that subsequent calls to typed
+ * getters will return the default value without crashing.
*
* @return an array of length {@link #getIndexCount()} populated with theme
* attributes, or null if there are no theme attributes in the typed
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0705e0c..cc8503b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -167,11 +167,16 @@
private boolean mOneShot;
private boolean mWithBuffer;
private boolean mFaceDetectionRunning = false;
- private Object mAutoFocusCallbackLock = new Object();
+ private final Object mAutoFocusCallbackLock = new Object();
private static final int NO_ERROR = 0;
private static final int EACCESS = -13;
private static final int ENODEV = -19;
+ private static final int EBUSY = -16;
+ private static final int EINVAL = -22;
+ private static final int ENOSYS = -38;
+ private static final int EUSERS = -87;
+ private static final int EOPNOTSUPP = -95;
/**
* Broadcast Action: A new picture is taken by the camera, and the entry of
@@ -190,6 +195,22 @@
public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
/**
+ * Camera HAL device API version 1.0
+ * @hide
+ */
+ public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+
+ /**
+ * A constant meaning the normal camera connect/open will be used.
+ */
+ private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2;
+
+ /**
+ * Used to indicate HAL version un-specified.
+ */
+ private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+
+ /**
* Hardware face detection. It does not use much CPU.
*/
private static final int CAMERA_FACE_DETECTION_HW = 0;
@@ -331,14 +352,82 @@
return null;
}
- Camera(int cameraId) {
- int err = cameraInit(cameraId);
+ /**
+ * Creates a new Camera object to access a particular hardware camera with
+ * given hal API version. If the same camera is opened by other applications
+ * or the hal API version is not supported by this device, this will throw a
+ * RuntimeException.
+ * <p>
+ * You must call {@link #release()} when you are done using the camera,
+ * otherwise it will remain locked and be unavailable to other applications.
+ * <p>
+ * Your application should only have one Camera object active at a time for
+ * a particular hardware camera.
+ * <p>
+ * Callbacks from other methods are delivered to the event loop of the
+ * thread which called open(). If this thread has no event loop, then
+ * callbacks are delivered to the main application event loop. If there is
+ * no main application event loop, callbacks are not delivered.
+ * <p class="caution">
+ * <b>Caution:</b> On some devices, this method may take a long time to
+ * complete. It is best to call this method from a worker thread (possibly
+ * using {@link android.os.AsyncTask}) to avoid blocking the main
+ * application UI thread.
+ *
+ * @param cameraId The hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1.
+ * @param halVersion The HAL API version this camera device to be opened as.
+ * @return a new Camera object, connected, locked and ready for use.
+ *
+ * @throws IllegalArgumentException if the {@code halVersion} is invalid
+ *
+ * @throws RuntimeException if opening the camera fails (for example, if the
+ * camera is in use by another process or device policy manager has disabled
+ * the camera).
+ *
+ * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
+ * @see #CAMERA_HAL_API_VERSION_1_0
+ *
+ * @hide
+ */
+ public static Camera openLegacy(int cameraId, int halVersion) {
+ if (halVersion < CAMERA_HAL_API_VERSION_1_0) {
+ throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+ }
+
+ return new Camera(cameraId, halVersion);
+ }
+
+ /**
+ * Create a legacy camera object.
+ *
+ * @param cameraId The hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1.
+ * @param halVersion The HAL API version this camera device to be opened as.
+ */
+ private Camera(int cameraId, int halVersion) {
+ int err = cameraInitVersion(cameraId, halVersion);
if (checkInitErrors(err)) {
switch(err) {
case EACCESS:
throw new RuntimeException("Fail to connect to camera service");
case ENODEV:
throw new RuntimeException("Camera initialization failed");
+ case ENOSYS:
+ throw new RuntimeException("Camera initialization failed because some methods"
+ + " are not implemented");
+ case EOPNOTSUPP:
+ throw new RuntimeException("Camera initialization failed because the hal"
+ + " version is not supported by this device");
+ case EINVAL:
+ throw new RuntimeException("Camera initialization failed because the input"
+ + " arugments are invalid");
+ case EBUSY:
+ throw new RuntimeException("Camera initialization failed because the camera"
+ + " device was already opened");
+ case EUSERS:
+ throw new RuntimeException("Camera initialization failed because the max"
+ + " number of camera devices were already opened");
default:
// Should never hit this.
throw new RuntimeException("Unknown camera error");
@@ -346,10 +435,7 @@
}
}
- /**
- * @hide
- */
- public int cameraInit(int cameraId) {
+ private int cameraInitVersion(int cameraId, int halVersion) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -369,9 +455,48 @@
String packageName = ActivityThread.currentPackageName();
- return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+ return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName);
}
+ private int cameraInitNormal(int cameraId) {
+ return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT);
+ }
+
+ /**
+ * Connect to the camera service using #connectLegacy
+ *
+ * <p>
+ * This acts the same as normal except that it will return
+ * the detailed error code if open fails instead of
+ * converting everything into {@code NO_INIT}.</p>
+ *
+ * <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p>
+ *
+ * @return a detailed errno error code, or {@code NO_ERROR} on success
+ *
+ * @hide
+ */
+ public int cameraInitUnspecified(int cameraId) {
+ return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED);
+ }
+
+ /** used by Camera#open, Camera#open(int) */
+ Camera(int cameraId) {
+ int err = cameraInitNormal(cameraId);
+ if (checkInitErrors(err)) {
+ switch(err) {
+ case EACCESS:
+ throw new RuntimeException("Fail to connect to camera service");
+ case ENODEV:
+ throw new RuntimeException("Camera initialization failed");
+ default:
+ // Should never hit this.
+ throw new RuntimeException("Unknown camera error");
+ }
+ }
+ }
+
+
/**
* @hide
*/
@@ -392,11 +517,12 @@
Camera() {
}
+ @Override
protected void finalize() {
release();
}
- private native final int native_setup(Object camera_this, int cameraId,
+ private native final int native_setup(Object camera_this, int cameraId, int halVersion,
String packageName);
private native final void native_release();
@@ -929,7 +1055,7 @@
private class EventHandler extends Handler
{
- private Camera mCamera;
+ private final Camera mCamera;
public EventHandler(Camera c, Looper looper) {
super(looper);
@@ -2210,6 +2336,7 @@
* @hide
* @deprecated
*/
+ @Deprecated
public void dump() {
Log.e(TAG, "dump: size=" + mMap.size());
for (String k : mMap.keySet()) {
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 31896f5..2bc3dd4 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -74,4 +74,11 @@
int getLegacyParameters(int cameraId, out String[] parameters);
// Determines if a particular API version is supported; see ICameraService.h for version defines
int supportsCameraApi(int cameraId, int apiVersion);
+
+ int connectLegacy(ICameraClient client, int cameraId,
+ int halVersion,
+ String clientPackageName,
+ int clientUid,
+ // Container for an ICamera object
+ out BinderHolder device);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 4a87680..e2f88eb 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -579,6 +579,7 @@
* of the lens that can be focused correctly.</p>
* <p>If the lens is fixed-focus, this should be
* 0.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*/
public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE =
new Key<Float>("android.lens.info.minimumFocusDistance", float.class);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 73188ff..9a3d806 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -278,14 +278,19 @@
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
int id = Integer.parseInt(cameraId);
try {
- mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
- USE_CALLING_UID, holder);
- cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
- } catch (CameraRuntimeException e) {
- if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+ if (supportsCamera2Api(cameraId)) {
+ // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
+ mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
+ USE_CALLING_UID, holder);
+ cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+ } else {
// Use legacy camera implementation for HAL1 devices
Log.i(TAG, "Using legacy camera HAL.");
cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+ }
+ } catch (CameraRuntimeException e) {
+ if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+ throw new AssertionError("Should've gone down the shim path");
} else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
e.getReason() == CameraAccessException.CAMERA_DISABLED ||
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 5a02435..dad1854 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -248,7 +248,6 @@
* <li>Manual frame duration control<ul>
* <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
* <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
- * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
* </ul>
* </li>
* <li>Manual exposure control<ul>
@@ -279,6 +278,9 @@
* result.</p>
* <p>A given camera device may also support additional manual sensor controls,
* but this capability only covers the above list of controls.</p>
+ * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
+ * additionally return a min frame duration that is greater than
+ * zero for each supported size-format combination.</p>
*
* @see CaptureRequest#BLACK_LEVEL_LOCK
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 2cfb611..5bc59dc 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -18,6 +18,7 @@
import android.hardware.camera2.CameraCharacteristics.Key;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.TypeReference;
import android.os.Parcel;
import android.os.Parcelable;
@@ -280,7 +281,7 @@
@Override
public int hashCode() {
- return mSettings.hashCode();
+ return HashCodeHelpers.hashCode(mSettings, mSurfaceSet, mUserTag);
}
public static final Parcelable.Creator<CaptureRequest> CREATOR =
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d9f3af4..2e59eee 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -28,6 +28,8 @@
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.CloseableLock;
+import android.hardware.camera2.utils.CloseableLock.ScopedLock;
import android.hardware.camera2.utils.LongParcelable;
import android.os.Handler;
import android.os.IBinder;
@@ -57,13 +59,14 @@
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUser mRemoteDevice;
- private final Object mLock = new Object();
+ private final CloseableLock mCloseLock;
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateListener mDeviceListener;
private volatile StateListener mSessionStateListener;
private final Handler mDeviceHandler;
+ private volatile boolean mClosing = false;
private boolean mInError = false;
private boolean mIdle = true;
@@ -100,7 +103,9 @@
private final Runnable mCallOnOpened = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onOpened(CameraDeviceImpl.this);
@@ -113,7 +118,9 @@
private final Runnable mCallOnUnconfigured = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onUnconfigured(CameraDeviceImpl.this);
@@ -126,7 +133,9 @@
private final Runnable mCallOnActive = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onActive(CameraDeviceImpl.this);
@@ -139,7 +148,9 @@
private final Runnable mCallOnBusy = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onBusy(CameraDeviceImpl.this);
@@ -150,20 +161,29 @@
};
private final Runnable mCallOnClosed = new Runnable() {
+ private boolean mClosedOnce = false;
+
@Override
public void run() {
+ if (mClosedOnce) {
+ throw new AssertionError("Don't post #onClosed more than once");
+ }
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onClosed(CameraDeviceImpl.this);
}
mDeviceListener.onClosed(CameraDeviceImpl.this);
+ mClosedOnce = true;
}
};
private final Runnable mCallOnIdle = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onIdle(CameraDeviceImpl.this);
@@ -176,7 +196,9 @@
private final Runnable mCallOnDisconnected = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onDisconnected(CameraDeviceImpl.this);
@@ -195,6 +217,7 @@
mDeviceListener = listener;
mDeviceHandler = handler;
mCharacteristics = characteristics;
+ mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId);
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -210,8 +233,8 @@
}
public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
// TODO: Move from decorator to direct binder-mediated exceptions
- synchronized(mLock) {
// If setRemoteFailure already called, do nothing
if (mInError) return;
@@ -254,7 +277,9 @@
}
final int code = failureCode;
final boolean isError = failureIsError;
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed, can't go to error state
+
mInError = true;
mDeviceHandler.post(new Runnable() {
@Override
@@ -280,7 +305,7 @@
if (outputs == null) {
outputs = new ArrayList<Surface>();
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
@@ -344,7 +369,7 @@
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateListener listener, Handler handler)
throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
if (DEBUG) {
Log.d(TAG, "createCaptureSession");
}
@@ -389,7 +414,7 @@
@Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -435,7 +460,7 @@
* starting and stopping repeating request and flushing.
*
* <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
- * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
+ * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
* If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
* is added to the list mFrameNumberRequestPairs.</p>
*
@@ -446,7 +471,7 @@
private void checkEarlyTriggerSequenceComplete(
final int requestId, final long lastFrameNumber) {
// lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
- // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
+ // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
final CaptureListenerHolder holder;
int index = mCaptureListenerMap.indexOfKey(requestId);
@@ -463,7 +488,7 @@
if (holder != null) {
if (DEBUG) {
- Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
+ Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
+ " request did not reach HAL");
}
@@ -480,10 +505,9 @@
|| lastFrameNumber > Integer.MAX_VALUE) {
throw new AssertionError(lastFrameNumber + " cannot be cast to int");
}
- holder.getListener().onCaptureSequenceCompleted(
+ holder.getListener().onCaptureSequenceAborted(
CameraDeviceImpl.this,
- requestId,
- lastFrameNumber);
+ requestId);
}
}
};
@@ -509,7 +533,7 @@
handler = checkHandler(handler);
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
int requestId;
@@ -581,7 +605,7 @@
@Override
public void stopRepeating() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
@@ -612,7 +636,7 @@
private void waitUntilIdle() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
@@ -633,7 +657,7 @@
@Override
public void flush() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
mDeviceHandler.post(mCallOnBusy);
@@ -656,8 +680,15 @@
@Override
public void close() {
- synchronized (mLock) {
+ mClosing = true;
+ // Acquire exclusive lock, close, release (idempotent)
+ mCloseLock.close();
+ /*
+ * The rest of this is safe, since no other methods will be able to execute
+ * (they will throw ISE instead; the callbacks will get dropped)
+ */
+ {
try {
if (mRemoteDevice != null) {
mRemoteDevice.disconnect();
@@ -805,7 +836,12 @@
// remove request from mCaptureListenerMap
final int requestId = frameNumberRequestPair.getValue();
final CaptureListenerHolder holder;
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) {
+ Log.w(TAG, "Camera closed while checking sequences");
+ return;
+ }
+
int index = mCaptureListenerMap.indexOfKey(requestId);
holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
: null;
@@ -890,9 +926,12 @@
@Override
public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
Runnable r = null;
- if (isClosed()) return;
- synchronized(mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) {
+ return; // Camera already closed
+ }
+
mInError = true;
switch (errorCode) {
case ERROR_CAMERA_DISCONNECTED:
@@ -914,25 +953,24 @@
break;
}
CameraDeviceImpl.this.mDeviceHandler.post(r);
- }
- // Fire onCaptureSequenceCompleted
- if (DEBUG) {
- Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ // Fire onCaptureSequenceCompleted
+ if (DEBUG) {
+ Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ }
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+ checkAndFireSequenceComplete();
}
- mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
- checkAndFireSequenceComplete();
-
}
@Override
public void onCameraIdle() {
- if (isClosed()) return;
-
if (DEBUG) {
Log.d(TAG, "Camera now idle");
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
if (!CameraDeviceImpl.this.mIdle) {
CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
}
@@ -948,30 +986,33 @@
}
final CaptureListenerHolder holder;
- // Get the listener for this frame ID, if there is one
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
+ // Get the listener for this frame ID, if there is one
holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- }
- if (holder == null) {
- return;
- }
+ if (holder == null) {
+ return;
+ }
- if (isClosed()) return;
+ if (isClosed()) return;
- // Dispatch capture start notice
- holder.getHandler().post(
- new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
- holder.getListener().onCaptureStarted(
- CameraDeviceImpl.this,
- holder.getRequest(resultExtras.getSubsequenceId()),
- timestamp);
+ // Dispatch capture start notice
+ holder.getHandler().post(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()) {
+ holder.getListener().onCaptureStarted(
+ CameraDeviceImpl.this,
+ holder.getRequest(resultExtras.getSubsequenceId()),
+ timestamp);
+ }
}
- }
- });
+ });
+
+ }
}
@Override
@@ -984,88 +1025,91 @@
+ requestId);
}
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
- // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
- result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
- getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+ result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+ getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
- final CaptureListenerHolder holder;
- synchronized (mLock) {
- holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- }
+ final CaptureListenerHolder holder =
+ CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
- boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+ Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+ boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
- // Update tracker (increment counter) when it's not a partial result.
- if (!quirkIsPartialResult) {
- mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
- }
-
- // Check if we have a listener for this
- if (holder == null) {
- if (DEBUG) {
- Log.d(TAG,
- "holder is null, early return at frame "
- + resultExtras.getFrameNumber());
+ // Update tracker (increment counter) when it's not a partial result.
+ if (!quirkIsPartialResult) {
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(),
+ /*error*/false);
}
- return;
- }
- if (isClosed()) {
- if (DEBUG) {
- Log.d(TAG,
- "camera is closed, early return at frame "
- + resultExtras.getFrameNumber());
+ // Check if we have a listener for this
+ if (holder == null) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "holder is null, early return at frame "
+ + resultExtras.getFrameNumber());
+ }
+ return;
}
- return;
- }
- final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
-
-
- Runnable resultDispatch = null;
-
- // Either send a partial result or the final capture completed result
- if (quirkIsPartialResult) {
- final CaptureResult resultAsCapture =
- new CaptureResult(result, request, requestId);
-
- // Partial result
- resultDispatch = new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()){
- holder.getListener().onCapturePartial(
- CameraDeviceImpl.this,
- request,
- resultAsCapture);
- }
+ if (isClosed()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "camera is closed, early return at frame "
+ + resultExtras.getFrameNumber());
}
- };
- } else {
- final TotalCaptureResult resultAsCapture =
- new TotalCaptureResult(result, request, requestId);
+ return;
+ }
- // Final capture result
- resultDispatch = new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()){
- holder.getListener().onCaptureCompleted(
- CameraDeviceImpl.this,
- request,
- resultAsCapture);
+ final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+
+
+ Runnable resultDispatch = null;
+
+ // Either send a partial result or the final capture completed result
+ if (quirkIsPartialResult) {
+ final CaptureResult resultAsCapture =
+ new CaptureResult(result, request, requestId);
+
+ // Partial result
+ resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()){
+ holder.getListener().onCapturePartial(
+ CameraDeviceImpl.this,
+ request,
+ resultAsCapture);
+ }
}
- }
- };
- }
+ };
+ } else {
+ final TotalCaptureResult resultAsCapture =
+ new TotalCaptureResult(result, request, requestId);
- holder.getHandler().post(resultDispatch);
+ // Final capture result
+ resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()){
+ holder.getListener().onCaptureCompleted(
+ CameraDeviceImpl.this,
+ request,
+ resultAsCapture);
+ }
+ }
+ };
+ }
- // Fire onCaptureSequenceCompleted
- if (!quirkIsPartialResult) {
- checkAndFireSequenceComplete();
+ holder.getHandler().post(resultDispatch);
+
+ // Fire onCaptureSequenceCompleted
+ if (!quirkIsPartialResult) {
+ checkAndFireSequenceComplete();
+ }
+
}
}
@@ -1101,10 +1145,9 @@
}
}
+ /** Whether the camera device has started to close (may not yet have finished) */
private boolean isClosed() {
- synchronized(mLock) {
- return (mRemoteDevice == null);
- }
+ return mClosing;
}
private CameraCharacteristics getCharacteristics() {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 54d9c3c..abd69c1 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -76,13 +76,11 @@
}
// TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
Camera legacyCamera = Camera.openUninitialized();
- int initErrors = legacyCamera.cameraInit(cameraId);
+ int initErrors = legacyCamera.cameraInitUnspecified(cameraId);
+
// Check errors old HAL initialization
- if (Camera.checkInitErrors(initErrors)) {
- // TODO: Map over old camera error codes. This likely involves improving the error
- // reporting in the HAL1 connect path.
- throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED);
- }
+ CameraBinderDecorator.throwOnError(initErrors);
+
LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
return new CameraDeviceUserShim(cameraId, device);
}
@@ -169,7 +167,7 @@
}
int numSurfaces = mSurfaces.size();
if (numSurfaces > 0) {
- surfaces = new ArrayList<Surface>();
+ surfaces = new ArrayList<>();
for (int i = 0; i < numSurfaces; ++i) {
surfaces.add(mSurfaces.valueAt(i));
}
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 0382557..5d44fd2 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -100,6 +100,7 @@
break;
case MSG_ALLOW_FRAMES:
mDroppingFrames = false;
+ break;
default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index c34a34d..50515a2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,7 +23,6 @@
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -34,7 +33,8 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.hardware.camera2.utils.CameraBinderDecorator.*;
/**
* This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -50,9 +50,11 @@
public static final String DEBUG_PROP = "HAL1ShimLogging";
private final String TAG;
+ private static final boolean DEBUG = false;
private final int mCameraId;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
+ private List<Surface> mConfiguredSurfaces;
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
@@ -84,6 +86,9 @@
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onError callback.");
+ }
try {
mDeviceCallbacks.onCameraError(errorCode, extras);
} catch (RemoteException e) {
@@ -92,13 +97,14 @@
}
}
});
-
-
}
@Override
public void onConfiguring() {
// Do nothing
+ if (DEBUG) {
+ Log.d(TAG, "doing onConfiguring callback.");
+ }
}
@Override
@@ -108,6 +114,9 @@
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onIdle callback.");
+ }
try {
mDeviceCallbacks.onCameraIdle();
} catch (RemoteException e) {
@@ -126,6 +135,9 @@
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onCaptureStarted callback.");
+ }
try {
// TODO: Don't fake timestamp
mDeviceCallbacks.onCaptureStarted(extras, timestamp);
@@ -135,7 +147,6 @@
}
}
});
-
}
@Override
@@ -145,6 +156,9 @@
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onCaptureResult callback.");
+ }
try {
// TODO: Don't fake metadata
mDeviceCallbacks.onResultReceived(result, extras);
@@ -200,16 +214,35 @@
/**
* Configure the device with a set of output surfaces.
*
+ * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
+ *
+ * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
+ *
* @param outputs a list of surfaces to set.
- * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
+ * @return an error code for this binder operation, or {@link NO_ERROR}
* on success.
*/
public int configureOutputs(List<Surface> outputs) {
+ if (outputs != null) {
+ for (Surface output : outputs) {
+ if (output == null) {
+ Log.e(TAG, "configureOutputs - null outputs are not allowed");
+ return BAD_VALUE;
+ }
+ }
+ }
+
int error = mDeviceState.setConfiguring();
- if (error == CameraBinderDecorator.NO_ERROR) {
+ if (error == NO_ERROR) {
mRequestThreadManager.configure(outputs);
error = mDeviceState.setIdle();
}
+
+ // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
+ if (error == NO_ERROR) {
+ mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
+ }
+
return error;
}
@@ -226,7 +259,35 @@
*/
public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
/*out*/LongParcelable frameNumber) {
- // TODO: validate request here
+ if (requestList == null || requestList.isEmpty()) {
+ Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
+ return BAD_VALUE;
+ }
+
+ // Make sure that there all requests have at least 1 surface; all surfaces are non-null
+ for (CaptureRequest request : requestList) {
+ if (request.getTargets().isEmpty()) {
+ Log.e(TAG, "submitRequestList - "
+ + "Each request must have at least one Surface target");
+ return BAD_VALUE;
+ }
+
+ for (Surface surface : request.getTargets()) {
+ if (surface == null) {
+ Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
+ return BAD_VALUE;
+ } else if (mConfiguredSurfaces == null) {
+ Log.e(TAG, "submitRequestList - must configure " +
+ " device with valid surfaces before submitting requests");
+ return INVALID_OPERATION;
+ } else if (!mConfiguredSurfaces.contains(surface)) {
+ Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
+ return BAD_VALUE;
+ }
+ }
+ }
+
+ // TODO: further validation of request here
mIdle.close();
return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
frameNumber);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 8bb066f..048878c 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -21,12 +21,16 @@
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationDuration;
import android.util.Log;
+import android.util.Range;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
@@ -44,6 +48,14 @@
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+ // for metadata
+ private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
+
+ private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // ms
+ private static final long APPROXIMATE_SENSOR_AREA = (1 << 20); // 8mp
+ private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
+ private static final long NS_PER_MS = 1000000;
+
/**
* Create characteristics for a legacy device by mapping the {@code parameters}
* and {@code info}
@@ -85,15 +97,16 @@
}
private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
+ m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
mapStreamConfigs(m, p);
-
+ mapAeConfig(m, p);
+ mapCapabilities(m, p);
+ mapLens(m, p);
+ mapFlash(m, p);
// TODO: map other fields
}
private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
- // TODO: set non-empty durations
- m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[] {} );
- m.set(SCALER_AVAILABLE_STALL_DURATIONS, new StreamConfigurationDuration[] {} );
ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
/*
@@ -122,10 +135,98 @@
String.format("mapStreamConfigs - Skipping non-public format %x", format));
}
}
+
+ List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
appendStreamConfig(availableStreamConfigs,
HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
availableStreamConfigs.toArray(new StreamConfiguration[0]));
+
+ // No frame durations available
+ m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
+
+ StreamConfigurationDuration[] jpegStalls =
+ new StreamConfigurationDuration[jpegSizes.size()];
+ int i = 0;
+ long longestStallDuration = -1;
+ for (Camera.Size s : jpegSizes) {
+ long stallDuration = calculateJpegStallDuration(s);
+ jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
+ s.height, stallDuration);
+ if (longestStallDuration < stallDuration) {
+ longestStallDuration = stallDuration;
+ }
+ }
+ // Set stall durations for jpeg, other formats use default stall duration
+ m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
+
+ m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {
+
+ List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
+ if (fpsRanges == null) {
+ throw new AssertionError("Supported FPS ranges cannot be null.");
+ }
+ int rangesSize = fpsRanges.size();
+ if (rangesSize <= 0) {
+ throw new AssertionError("At least one FPS range must be supported.");
+ }
+ Range<Integer>[] ranges = new Range[rangesSize];
+ int i = 0;
+ for (int[] r : fpsRanges) {
+ ranges[i++] = Range.create(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+
+ List<String> antiBandingModes = p.getSupportedAntibanding();
+ int antiBandingModesSize = antiBandingModes.size();
+ if (antiBandingModesSize > 0) {
+ int[] modes = new int[antiBandingModesSize];
+ int j = 0;
+ for (String mode : antiBandingModes) {
+ int convertedMode = convertAntiBandingMode(mode);
+ if (convertedMode == -1) {
+ Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
+ " not supported, skipping...");
+ } else {
+ modes[j++] = convertedMode;
+ }
+ }
+ m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
+ }
+ }
+
+ private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
+ int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
+ m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
+ }
+
+ private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
+ /*
+ * We can tell if the lens is fixed focus;
+ * but if it's not, we can't tell the minimum focus distance, so leave it null then.
+ */
+ if (p.getFocusMode() == Camera.Parameters.FOCUS_MODE_FIXED) {
+ m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
+ }
+ }
+
+ private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) {
+ boolean flashAvailable = false;
+ List<String> supportedFlashModes = p.getSupportedFlashModes();
+ if (supportedFlashModes != null) {
+ // If only 'OFF' is available, we don't really have flash support
+ if (!(supportedFlashModes.contains(Camera.Parameters.FLASH_MODE_OFF) &&
+ supportedFlashModes.size() == 1)) {
+ flashAvailable = true;
+ }
+ }
+
+ m.set(FLASH_INFO_AVAILABLE, flashAvailable);
}
private static void appendStreamConfig(
@@ -136,4 +237,115 @@
configs.add(config);
}
}
+
+ /**
+ * Returns -1 if the anti-banding mode string is null, or not supported.
+ */
+ private static int convertAntiBandingMode(final String mode) {
+ if (mode == null) {
+ return -1;
+ }
+ switch(mode) {
+ case Camera.Parameters.ANTIBANDING_OFF: {
+ return CONTROL_AE_ANTIBANDING_MODE_OFF;
+ }
+ case Camera.Parameters.ANTIBANDING_50HZ: {
+ return CONTROL_AE_ANTIBANDING_MODE_50HZ;
+ }
+ case Camera.Parameters.ANTIBANDING_60HZ: {
+ return CONTROL_AE_ANTIBANDING_MODE_60HZ;
+ }
+ case Camera.Parameters.ANTIBANDING_AUTO: {
+ return CONTROL_AE_ANTIBANDING_MODE_AUTO;
+ }
+ default: {
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the anti-banding mode enum is not supported.
+ */
+ private static String convertAntiBandingModeToLegacy(int mode) {
+ switch(mode) {
+ case CONTROL_AE_ANTIBANDING_MODE_OFF: {
+ return Camera.Parameters.ANTIBANDING_OFF;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
+ return Camera.Parameters.ANTIBANDING_50HZ;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
+ return Camera.Parameters.ANTIBANDING_60HZ;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
+ return Camera.Parameters.ANTIBANDING_AUTO;
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+
+
+ private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
+ int[] legacyFps = new int[2];
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
+ return legacyFps;
+ }
+
+ /**
+ * Return the stall duration for a given output jpeg size in nanoseconds.
+ *
+ * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
+ */
+ private static long calculateJpegStallDuration(Camera.Size size) {
+ long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
+ long area = size.width * (long) size.height;
+ long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME * NS_PER_MS /
+ APPROXIMATE_SENSOR_AREA; // 600ms stall for 8mp
+ return baseDuration + area * stallPerArea;
+ }
+
+ /**
+ * Generate capture result metadata from legacy camera parameters.
+ *
+ * @param params a {@link Camera.Parameters} object to generate metadata from.
+ * @param request the {@link CaptureRequest} used for this result.
+ * @param timestamp the timestamp to use for this result in nanoseconds.
+ * @return a {@link CameraMetadataNative} object containing result metadata.
+ */
+ public static CameraMetadataNative convertResultMetadata(Camera.Parameters params,
+ CaptureRequest request,
+ long timestamp) {
+ CameraMetadataNative result = new CameraMetadataNative();
+ result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+ result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
+
+ // TODO: Remaining result metadata tags conversions.
+ return result;
+ }
+
+ /**
+ * Set the legacy parameters using the request metadata.
+ *
+ * @param request a {@link CaptureRequest} object to generate parameters from.
+ * @param params the a {@link Camera.Parameters} to set parameters in.
+ */
+ public static void convertRequestMetadata(CaptureRequest request,
+ /*out*/Camera.Parameters params) {
+ Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
+ if (antiBandingMode != null) {
+ String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
+ if (legacyMode != null) params.setAntibanding(legacyMode);
+ }
+
+ Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+ if (aeFpsRange != null) {
+ int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
+ params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ }
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 7b522ff..e0f3429 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -16,11 +16,9 @@
package android.hardware.camera2.legacy;
-import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.os.ConditionVariable;
@@ -71,21 +69,26 @@
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
+ private volatile long mLastJpegTimestamp;
+ private volatile long mLastPreviewTimestamp;
private volatile RequestHolder mInFlightPreview;
private volatile RequestHolder mInFlightJpeg;
- private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
- private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
+ private final List<Surface> mPreviewOutputs = new ArrayList<Surface>();
+ private final List<Surface> mCallbackOutputs = new ArrayList<Surface>();
private GLThreadManager mGLThreadManager;
private SurfaceTexture mPreviewTexture;
+ private Camera.Parameters mParams;
private Size mIntermediateBufferSize;
private final RequestQueue mRequestQueue = new RequestQueue();
+ private CaptureRequest mLastRequest = null;
private SurfaceTexture mDummyTexture;
private Surface mDummySurface;
private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
+ private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
/**
* Container object for Configure messages.
@@ -209,23 +212,34 @@
}
};
+ private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
+ @Override
+ public void onShutter() {
+ mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+ }
+ };
+
private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ RequestHolder holder = mInFlightPreview;
+ if (holder == null) {
+ mGLThreadManager.queueNewFrame(null);
+ Log.w(TAG, "Dropping preview frame.");
+ return;
+ }
+
if (DEBUG) {
mPrevCounter.countAndLog();
}
- RequestHolder holder = mInFlightPreview;
- if (holder == null) {
- Log.w(TAG, "Dropping preview frame.");
- mInFlightPreview = null;
- return;
- }
+ mInFlightPreview = null;
+
if (holder.hasPreviewTargets()) {
mGLThreadManager.queueNewFrame(holder.getHolderTargets());
}
+ mLastPreviewTimestamp = surfaceTexture.getTimestamp();
mReceivedPreview.open();
}
};
@@ -252,7 +266,7 @@
}
mInFlightJpeg = request;
// TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
- mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
+ mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
mPreviewRunning = false;
}
@@ -300,19 +314,20 @@
mInFlightPreview = null;
mInFlightJpeg = null;
-
- for (Surface s : outputs) {
- int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
- switch (format) {
- case CameraMetadataNative.NATIVE_JPEG_FORMAT:
- mCallbackOutputs.add(s);
- break;
- default:
- mPreviewOutputs.add(s);
- break;
+ if (outputs != null) {
+ for (Surface s : outputs) {
+ int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
+ switch (format) {
+ case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+ mCallbackOutputs.add(s);
+ break;
+ default:
+ mPreviewOutputs.add(s);
+ break;
+ }
}
}
-
+ mParams = mCamera.getParameters();
if (mPreviewOutputs.size() > 0) {
List<Size> outputSizes = new ArrayList<>(outputs.size());
for (Surface s : mPreviewOutputs) {
@@ -323,13 +338,11 @@
Size largestOutput = findLargestByArea(outputSizes);
- Camera.Parameters params = mCamera.getParameters();
-
// Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
- List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+ List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes());
Size largestJpegDimen = findLargestByArea(supportedJpegSizes);
- List<Size> supportedPreviewSizes = convertSizeList(params.getSupportedPreviewSizes());
+ List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes());
// Use smallest preview dimension with same aspect ratio as sensor that is >= than all
// of the configured output dimensions. If none exists, fall back to using the largest
@@ -357,8 +370,6 @@
}
}
-
-
// TODO: Detect and optimize single-output paths here to skip stream teeing.
if (mGLThreadManager == null) {
mGLThreadManager = new GLThreadManager(mCameraId);
@@ -419,7 +430,6 @@
private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
private boolean mCleanup = false;
- private List<RequestHolder> mRepeating = null;
@SuppressWarnings("unchecked")
@Override
@@ -428,10 +438,14 @@
return true;
}
+ if (DEBUG) {
+ Log.d(TAG, "Request thread handling message:" + msg.what);
+ }
switch (msg.what) {
case MSG_CONFIGURE_OUTPUTS:
ConfigureHolder config = (ConfigureHolder) msg.obj;
- Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
+ int sizes = config.surfaces != null ? config.surfaces.size() : 0;
+ Log.i(TAG, "Configure outputs: " + sizes +
" surfaces configured.");
try {
configureOutputs(config.surfaces);
@@ -459,7 +473,15 @@
List<RequestHolder> requests =
nextBurst.first.produceRequestHolders(nextBurst.second);
for (RequestHolder holder : requests) {
+ CaptureRequest request = holder.getRequest();
+ if (mLastRequest == null || mLastRequest != request) {
+ mLastRequest = request;
+ LegacyMetadataMapper.convertRequestMetadata(mLastRequest,
+ /*out*/mParams);
+ mCamera.setParameters(mParams);
+ }
mDeviceState.setCaptureStart(holder);
+ long timestamp = 0;
try {
if (holder.hasPreviewTargets()) {
mReceivedPreview.close();
@@ -468,6 +490,7 @@
// TODO: report error to CameraDevice
Log.e(TAG, "Hit timeout for preview callback!");
}
+ timestamp = mLastPreviewTimestamp;
}
if (holder.hasJpegTargets()) {
mReceivedJpeg.close();
@@ -478,16 +501,19 @@
Log.e(TAG, "Hit timeout for jpeg callback!");
}
mInFlightJpeg = null;
+ timestamp = mLastJpegTimestamp;
}
} catch (IOException e) {
// TODO: err handling
throw new IOError(e);
}
- Camera.Parameters params = mCamera.getParameters();
- CameraMetadataNative result = convertResultMetadata(params,
- holder.getRequest());
+ CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams,
+ request, timestamp);
mDeviceState.setCaptureResult(holder, result);
}
+ if (DEBUG) {
+ mRequestCounter.countAndLog();
+ }
break;
case MSG_CLEANUP:
mCleanup = true;
@@ -506,15 +532,6 @@
}
};
- private CameraMetadataNative convertResultMetadata(Camera.Parameters params,
- CaptureRequest request) {
- CameraMetadataNative result = new CameraMetadataNative();
- result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
-
- // TODO: Remaining result metadata tags conversions.
- return result;
- }
-
/**
* Create a new RequestThreadManager.
*
@@ -597,12 +614,14 @@
/**
- * Configure with the current output Surfaces.
+ * Configure with the current list of output Surfaces.
*
* <p>
* This operation blocks until the configuration is complete.
* </p>
*
+ * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
+ *
* @param outputs a {@link java.util.Collection} of outputs to configure.
*/
public void configure(Collection<Surface> outputs) {
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 49f419f..e9d32f0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -486,6 +486,7 @@
}
checkGlError("before updateTexImage");
mSurfaceTexture.updateTexImage();
+ if (targetSurfaces == null) return;
for (EGLSurfaceHolder holder : mSurfaces) {
if (targetSurfaces.contains(holder.surface)) {
makeCurrent(holder.eglSurface);
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 3036425..fff171b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -63,6 +63,12 @@
public final class StreamConfigurationMap {
private static final String TAG = "StreamConfigurationMap";
+
+ /**
+ * Indicates that a minimum frame duration is not available for a particular configuration.
+ */
+ public static final long NO_MIN_FRAME_DURATION = 0;
+
/**
* Create a new {@link StreamConfigurationMap}.
*
@@ -359,7 +365,9 @@
*
* @param format an image format from {@link ImageFormat} or {@link PixelFormat}
* @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+ * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+ * can only occur on limited mode devices).
*
* @throws IllegalArgumentException if {@code format} or {@code size} was not supported
* @throws NullPointerException if {@code size} was {@code null}
@@ -406,7 +414,9 @@
* a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
* non-empty array returned by {@link #getOutputSizes(Class)}
* @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+ * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+ * can only occur on limited mode devices).
*
* @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
* @throws NullPointerException if {@code size} or {@code klass} was {@code null}
@@ -892,7 +902,7 @@
private long getDurationDefault(int duration) {
switch (duration) {
case DURATION_MIN_FRAME:
- throw new AssertionError("Minimum frame durations are required to be listed");
+ return NO_MIN_FRAME_DURATION;
case DURATION_STALL:
return 0L; // OK. A lack of a stall duration implies a 0 stall duration
default:
diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java
new file mode 100644
index 0000000..af55055
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/CloseableLock.java
@@ -0,0 +1,330 @@
+/*
+ * 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.hardware.camera2.utils;
+
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Implement a shared/exclusive lock that can be closed.
+ *
+ * <p>A shared lock can be acquired if any other shared locks are also acquired. An
+ * exclusive lock acquire will block until all shared locks have been released.</p>
+ *
+ * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
+ * while a lock is already held will immediately succeed.</p>
+ *
+ * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
+ * supported; attempting it will throw an {@link IllegalStateException}.</p>
+ *
+ * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
+ * </p>
+ */
+public class CloseableLock implements AutoCloseable {
+
+ private static final boolean VERBOSE = false;
+
+ private final String TAG = "CloseableLock";
+ private final String mName;
+
+ private volatile boolean mClosed = false;
+
+ /** If an exclusive lock is acquired by some thread. */
+ private boolean mExclusive = false;
+ /**
+ * How many shared locks are acquired by any thread:
+ *
+ * <p>Reentrant locking increments this. If an exclusive lock is held,
+ * this value will stay at 0.</p>
+ */
+ private int mSharedLocks = 0;
+
+ private final ReentrantLock mLock = new ReentrantLock();
+ /** This condition automatically releases mLock when waiting; re-acquiring it after notify */
+ private final Condition mCondition = mLock.newCondition();
+
+ /** How many times the current thread is holding the lock */
+ private final ThreadLocal<Integer> mLockCount =
+ new ThreadLocal<Integer>() {
+ @Override protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ /**
+ * Helper class to release a lock at the end of a try-with-resources statement.
+ */
+ public class ScopedLock implements AutoCloseable {
+ private ScopedLock() {}
+
+ /** Release the lock with {@link CloseableLock#releaseLock}. */
+ @Override
+ public void close() {
+ releaseLock();
+ }
+ }
+
+ /**
+ * Create a new instance; starts out with 0 locks acquired.
+ */
+ public CloseableLock() {
+ mName = "";
+ }
+
+ /**
+ * Create a new instance; starts out with 0 locks acquired.
+ *
+ * @param name set an optional name for logging functionality
+ */
+ public CloseableLock(String name) {
+ mName = name;
+ }
+
+ /**
+ * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock.
+ *
+ * <p>Marking a lock as closed will fail all further acquisition attempts;
+ * it will also immediately unblock all other threads currently trying to acquire a lock.</p>
+ *
+ * <p>This operation is idempotent; calling it more than once has no effect.</p>
+ *
+ * @throws IllegalStateException
+ * if an attempt is made to {@code close} while this thread has a lock acquired
+ */
+ @Override
+ public void close() {
+ if (mClosed) {
+ log("close - already closed; ignoring");
+ return;
+ }
+
+ ScopedLock scoper = acquireExclusiveLock();
+ // Already closed by another thread?
+ if (scoper == null) {
+ return;
+ } else if (mLockCount.get() != 1) {
+ // Future: may want to add a #releaseAndClose to allow this.
+ throw new IllegalStateException(
+ "Cannot close while one or more acquired locks are being held by this " +
+ "thread; release all other locks first");
+ }
+
+ try {
+ mLock.lock();
+
+ mClosed = true;
+ mExclusive = false;
+ mSharedLocks = 0;
+ mLockCount.remove();
+
+ // Notify all threads that are waiting to unblock and return immediately
+ mCondition.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+
+ log("close - completed");
+ }
+
+ /**
+ * Try to acquire the lock non-exclusively, blocking until the operation completes.
+ *
+ * <p>If the lock has already been closed, or being closed before this operation returns,
+ * the call will immediately return {@code false}.</p>
+ *
+ * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed),
+ * this operation will return immediately. If another thread holds an exclusive lock,
+ * this thread will block until the exclusive lock has been released.</p>
+ *
+ * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is
+ * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p>
+ *
+ * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+ * was already closed.
+ *
+ * @throws IllegalStateException if this thread is already holding an exclusive lock
+ */
+ public ScopedLock acquireLock() {
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, all further acquisitions will fail
+ if (mClosed) {
+ log("acquire lock early aborted (already closed)");
+ return null;
+ }
+
+ ownedLocks = mLockCount.get();
+
+ // This thread is already holding an exclusive lock
+ if (mExclusive && ownedLocks > 0) {
+ throw new IllegalStateException(
+ "Cannot acquire shared lock while holding exclusive lock");
+ }
+
+ // Is another thread holding the exclusive lock? Block until we can get in.
+ while (mExclusive) {
+ mCondition.awaitUninterruptibly();
+
+ // Did another thread #close while we were waiting? Unblock immediately.
+ if (mClosed) {
+ log("acquire lock unblocked aborted (already closed)");
+ return null;
+ }
+ }
+
+ mSharedLocks++;
+
+ ownedLocks = mLockCount.get() + 1;
+ mLockCount.set(ownedLocks);
+ } finally {
+ mLock.unlock();
+ }
+
+ log("acquired lock (local own count = " + ownedLocks + "");
+ return new ScopedLock();
+ }
+
+ /**
+ * Try to acquire the lock exclusively, blocking until all other threads release their locks.
+ *
+ * <p>If the lock has already been closed, or being closed before this operation returns,
+ * the call will immediately return {@code false}.</p>
+ *
+ * <p>If any other threads are holding a lock, this thread will block until all
+ * other locks are released.</p>
+ *
+ * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported,
+ * and must be matched by an equal number of {@link #releaseLock} calls.</p>
+ *
+ * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+ * was already closed.
+ *
+ * @throws IllegalStateException
+ * if an attempt is made to acquire an exclusive lock while already holding a lock
+ */
+ public ScopedLock acquireExclusiveLock() {
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, all further acquisitions will fail
+ if (mClosed) {
+ log("acquire exclusive lock early aborted (already closed)");
+ return null;
+ }
+
+ ownedLocks = mLockCount.get();
+
+ // This thread is already holding a shared lock
+ if (!mExclusive && ownedLocks > 0) {
+ throw new IllegalStateException(
+ "Cannot acquire exclusive lock while holding shared lock");
+ }
+
+ /*
+ * Is another thread holding the lock? Block until we can get in.
+ *
+ * If we are already holding the lock, always let it through since
+ * we are just reentering the exclusive lock.
+ */
+ while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) {
+ mCondition.awaitUninterruptibly();
+
+ // Did another thread #close while we were waiting? Unblock immediately.
+ if (mClosed) {
+ log("acquire exclusive lock unblocked aborted (already closed)");
+ return null;
+ }
+ }
+
+ mExclusive = true;
+
+ ownedLocks = mLockCount.get() + 1;
+ mLockCount.set(ownedLocks);
+ } finally {
+ mLock.unlock();
+ }
+
+ log("acquired exclusive lock (local own count = " + ownedLocks + "");
+ return new ScopedLock();
+ }
+
+ /**
+ * Release a single lock that was acquired.
+ *
+ * <p>Any other other that is blocked and trying to acquire a lock will get a chance
+ * to acquire the lock.</p>
+ *
+ * @throws IllegalStateException if no locks were acquired, or if the lock was already closed
+ */
+ public void releaseLock() {
+ if (mLockCount.get() <= 0) {
+ throw new IllegalStateException(
+ "Cannot release lock that was not acquired by this thread");
+ }
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, it couldn't have been acquired in the first place
+ if (mClosed) {
+ throw new IllegalStateException("Do not release after the lock has been closed");
+ }
+
+ if (!mExclusive) {
+ mSharedLocks--;
+ } else {
+ if (mSharedLocks != 0) {
+ throw new AssertionError("Too many shared locks " + mSharedLocks);
+ }
+ }
+
+ ownedLocks = mLockCount.get() - 1;
+ mLockCount.set(ownedLocks);
+
+ if (ownedLocks == 0 && mExclusive) {
+ // Wake up any threads that might be waiting for the exclusive lock to be released
+ mExclusive = false;
+ mCondition.signalAll();
+ } else if (ownedLocks == 0 && mSharedLocks == 0) {
+ // Wake up any threads that might be trying to get the exclusive lock
+ mCondition.signalAll();
+ }
+ } finally {
+ mLock.unlock();
+ }
+
+ log("released lock (local lock count " + ownedLocks + ")");
+ }
+
+ private void log(String what) {
+ if (VERBOSE) {
+ Log.v(TAG + "[" + mName + "]", what);
+ }
+ }
+
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9218c11..53e87a6 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
+import android.nfc.INfcLockscreenDispatch;
import android.os.Bundle;
/**
@@ -52,4 +53,6 @@
void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
void setP2pModes(int initatorModes, int targetModes);
+
+ void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList);
}
diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl
new file mode 100644
index 0000000..27e9a8c
--- /dev/null
+++ b/core/java/android/nfc/INfcLockscreenDispatch.aidl
@@ -0,0 +1,12 @@
+package android.nfc;
+
+import android.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface INfcLockscreenDispatch {
+
+ boolean onTagDetected(in Tag tag);
+
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index dd8e41c..8991066 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -380,6 +380,16 @@
public Uri[] createBeamUris(NfcEvent event);
}
+
+ /**
+ * A callback to be invoked when an application has registered for receiving
+ * tags at the lockscreen.
+ */
+ public interface NfcLockscreenDispatch {
+ public boolean onTagDetected(Tag tag);
+ }
+
+
/**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
@@ -1417,6 +1427,23 @@
}
}
+ public boolean registerLockscreenDispatch(final NfcLockscreenDispatch lockscreenDispatch,
+ int[] techList) {
+ try {
+ sService.registerLockscreenDispatch(new INfcLockscreenDispatch.Stub() {
+ @Override
+ public boolean onTagDetected(Tag tag) throws RemoteException {
+ return lockscreenDispatch.onTagDetected(tag);
+ }
+ }, techList);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+
+ return true;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 1e7d7f1..8038a38 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1694,7 +1694,7 @@
*/
public static final class Entity implements BaseColumns, ContactsColumns,
ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
- StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+ StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
/**
* no public constructor since this is a utility class
*/
@@ -7813,6 +7813,25 @@
"pinned_position_update");
/**
+ * <p>
+ * The method to invoke in order to undemote a formerly demoted contact. The contact id of
+ * the contact must be provided as an argument. If the contact was not previously demoted,
+ * nothing will be done.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * <pre>
+ * final long contactId = 10;
+ * resolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+ * String.valueOf(contactId), null);
+ * </pre>
+ *
+ * </p>
+ */
+ public static final String UNDEMOTE_METHOD = "undemote";
+
+ /**
* Default value for the pinned position of an unpinned contact. Also equal to
* {@link Integer#MAX_VALUE}.
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 06c05ee..9b6f0ea 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2460,6 +2460,18 @@
public static final String POINTER_SPEED = "pointer_speed";
/**
+ * Whether lock-to-app will be triggered by long-press on recents.
+ * @hide
+ */
+ public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+
+ /**
+ * Whether lock-to-app will lock the keyguard when exiting.
+ * @hide
+ */
+ public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked";
+
+ /**
* I am the lolrus.
* <p>
* Nonzero values indicate that the user has a bukkit.
@@ -4537,12 +4549,6 @@
public static final String SMS_DEFAULT_APPLICATION = "sms_default_application";
/**
- * Specifies the package name currently configured to be the primary phone application
- * @hide
- */
- public static final String PHONE_DEFAULT_APPLICATION = "phone_default_application";
-
- /**
* Name of a package that the current user has explicitly allowed to see all of that
* user's notifications.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 89c2f37..d25ef16 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14240,6 +14240,7 @@
* @return True if the view is attached to a window and the window is
* hardware accelerated; false in any other case.
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean isHardwareAccelerated() {
return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index af1de78..3f72b4c 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -52,7 +52,7 @@
* The View whose properties are being animated by this class. This is set at
* construction time.
*/
- private final View mView;
+ final View mView;
/**
* The duration of the underlying Animator object. By default, we don't set the duration
@@ -253,10 +253,9 @@
ViewPropertyAnimator(View view) {
mView = view;
view.ensureTransformationInfo();
- // TODO: Disabled because of b/15287046
- //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
- // mRTBackend = new ViewPropertyAnimatorRT(view);
- //}
+ if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ mRTBackend = new ViewPropertyAnimatorRT(view);
+ }
}
/**
@@ -434,6 +433,9 @@
}
mPendingAnimations.clear();
mView.removeCallbacks(mAnimationStarter);
+ if (mRTBackend != null) {
+ mRTBackend.cancelAll();
+ }
}
/**
diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java
index 709efdb..8b4277a 100644
--- a/core/java/android/view/ViewPropertyAnimatorRT.java
+++ b/core/java/android/view/ViewPropertyAnimatorRT.java
@@ -50,6 +50,15 @@
return true;
}
+ public void cancelAll() {
+ for (int i = 0; i < mAnimators.length; i++) {
+ if (mAnimators[i] != null) {
+ mAnimators[i].cancel();
+ mAnimators[i] = null;
+ }
+ }
+ }
+
private void doStartAnimation(ViewPropertyAnimator parent) {
int size = parent.mPendingAnimations.size();
@@ -63,12 +72,22 @@
NameValuesHolder holder = parent.mPendingAnimations.get(i);
int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
- RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue);
+ final float finalValue = holder.mFromValue + holder.mDeltaValue;
+ RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
animator.setStartDelay(startDelay);
animator.setDuration(duration);
animator.setInterpolator(interpolator);
animator.setTarget(mView);
animator.start();
+
+ // Alpha is a special snowflake that has the canonical value stored
+ // in mTransformationInfo instead of in RenderNode, so we need to update
+ // it with the final value here.
+ if (property == RenderNodeAnimator.ALPHA) {
+ // Don't need null check because ViewPropertyAnimator's
+ // ctor calls ensureTransformationInfo()
+ parent.mView.mTransformationInfo.mAlpha = finalValue;
+ }
}
parent.mPendingAnimations.clear();
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 04b18c1..93810b3 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1326,12 +1326,28 @@
if (sel != null) {
positionSelector(INVALID_POSITION, sel);
mSelectedTop = sel.getTop();
- } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
- View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null) positionSelector(mMotionPosition, child);
} else {
- mSelectedTop = 0;
- mSelectorRect.setEmpty();
+ final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
+ && mTouchMode < TOUCH_MODE_SCROLL;
+ if (inTouchMode) {
+ // If the user's finger is down, select the motion position.
+ final View child = getChildAt(mMotionPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mMotionPosition, child);
+ }
+ } else if (mSelectedPosition != INVALID_POSITION) {
+ // If we had previously positioned the selector somewhere,
+ // put it back there. It might not match up with the data,
+ // but it's transitioning out so it's not a big deal.
+ final View child = getChildAt(mSelectorPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mSelectorPosition, child);
+ }
+ } else {
+ // Otherwise, clear selection.
+ mSelectedTop = 0;
+ mSelectorRect.setEmpty();
+ }
}
// Attempt to restore accessibility focus, if necessary.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index eeb8015..1baeca8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1718,14 +1718,24 @@
}
mSelectedTop = sel.getTop();
} else {
- // If the user's finger is down, select the motion position.
- // Otherwise, clear selection.
- if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
+ final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
+ || mTouchMode == TOUCH_MODE_DONE_WAITING;
+ if (inTouchMode) {
+ // If the user's finger is down, select the motion position.
final View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null) {
+ if (child != null) {
positionSelector(mMotionPosition, child);
}
+ } else if (mSelectorPosition != INVALID_POSITION) {
+ // If we had previously positioned the selector somewhere,
+ // put it back there. It might not match up with the data,
+ // but it's transitioning out so it's not a big deal.
+ final View child = getChildAt(mSelectorPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mSelectorPosition, child);
+ }
} else {
+ // Otherwise, clear selection.
mSelectedTop = 0;
mSelectorRect.setEmpty();
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index cca29cf..9a8380d 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -90,6 +90,7 @@
private boolean mSplitTrack;
private CharSequence mTextOn;
private CharSequence mTextOff;
+ private boolean mShowText;
private int mTouchMode;
private int mTouchSlop;
@@ -188,6 +189,7 @@
mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
+ mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
mThumbTextPadding = a.getDimensionPixelSize(
com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
mSwitchMinWidth = a.getDimensionPixelSize(
@@ -533,20 +535,43 @@
requestLayout();
}
+ /**
+ * Sets whether the on/off text should be displayed.
+ *
+ * @param showText {@code true} to display on/off text
+ * @hide
+ */
+ public void setShowText(boolean showText) {
+ if (mShowText != showText) {
+ mShowText = showText;
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return whether the on/off text should be displayed
+ * @hide
+ */
+ public boolean getShowText() {
+ return mShowText;
+ }
+
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mOnLayout == null) {
- mOnLayout = makeLayout(mTextOn);
- }
+ if (mShowText) {
+ if (mOnLayout == null) {
+ mOnLayout = makeLayout(mTextOn);
+ }
- if (mOffLayout == null) {
- mOffLayout = makeLayout(mTextOff);
+ if (mOffLayout == null) {
+ mOffLayout = makeLayout(mTextOff);
+ }
}
mTrackDrawable.getPadding(mTempRect);
- final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
- + mThumbTextPadding * 2;
+ final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+ + mThumbTextPadding * 2 : 0;
mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
final int switchWidth = Math.max(mSwitchMinWidth,
@@ -568,9 +593,10 @@
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
- Layout layout = isChecked() ? mOnLayout : mOffLayout;
- if (layout != null && !TextUtils.isEmpty(layout.getText())) {
- event.getText().add(layout.getText());
+
+ final CharSequence text = isChecked() ? mTextOn : mTextOff;
+ if (text != null) {
+ event.getText().add(text);
}
}
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 7e11850..4995ea1d1 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -28,7 +28,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
-import android.webkit.WebViewFactory;
import com.android.internal.util.GrowingArrayUtils;
@@ -55,6 +54,11 @@
// that is done.
public static long COMMIT_PERIOD = 3*60*60*1000; // Commit current stats every 3 hours
+ // Minimum uptime period before committing. If the COMMIT_PERIOD has elapsed but
+ // the total uptime has not exceeded this amount, then the commit will be held until
+ // it is reached.
+ public static long COMMIT_UPTIME_PERIOD = 60*60*1000; // Must have at least 1 hour elapsed
+
public static final int STATE_NOTHING = -1;
public static final int STATE_PERSISTENT = 0;
public static final int STATE_TOP = 1;
@@ -81,6 +85,24 @@
public static final int PSS_USS_MAXIMUM = 6;
public static final int PSS_COUNT = PSS_USS_MAXIMUM+1;
+ public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0;
+ public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1;
+ public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2;
+ public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3;
+ public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4;
+ public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5;
+ public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6;
+ public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7;
+ public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8;
+ public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9;
+ public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10;
+ public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11;
+ public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12;
+ public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13;
+ public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14;
+ public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15;
+ public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1;
+
public static final int ADJ_NOTHING = -1;
public static final int ADJ_MEM_FACTOR_NORMAL = 0;
public static final int ADJ_MEM_FACTOR_MODERATE = 1;
@@ -174,7 +196,7 @@
static final String CSV_SEP = "\t";
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 14;
+ private static final int PARCEL_VERSION = 18;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535453;
@@ -200,11 +222,16 @@
public int mMemFactor = STATE_NOTHING;
public long mStartTime;
+ public int[] mSysMemUsageTable = null;
+ public int mSysMemUsageTableSize = 0;
+ public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
+
public long mTimePeriodStartClock;
public long mTimePeriodStartRealtime;
public long mTimePeriodEndRealtime;
+ public long mTimePeriodStartUptime;
+ public long mTimePeriodEndUptime;
String mRuntime;
- String mWebView;
boolean mRunning;
static final int LONGS_SIZE = 4096;
@@ -304,11 +331,77 @@
mMemFactorDurations[i] += other.mMemFactorDurations[i];
}
+ for (int i=0; i<other.mSysMemUsageTableSize; i++) {
+ int ent = other.mSysMemUsageTable[i];
+ int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK));
+ }
+
if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
mTimePeriodStartClock = other.mTimePeriodStartClock;
mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
}
mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
+ mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+ }
+
+ public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
+ long nativeMem) {
+ if (mMemFactor != STATE_NOTHING) {
+ int state = mMemFactor * STATE_COUNT;
+ mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1;
+ for (int i=0; i<3; i++) {
+ mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem;
+ }
+ addSysMemUsage(state, mSysMemUsageArgs, 0);
+ }
+ }
+
+ void addSysMemUsage(int state, long[] data, int dataOff) {
+ int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+ int off;
+ if (idx >= 0) {
+ off = mSysMemUsageTable[idx];
+ } else {
+ mAddLongTable = mSysMemUsageTable;
+ mAddLongTableSize = mSysMemUsageTableSize;
+ off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT);
+ mSysMemUsageTable = mAddLongTable;
+ mSysMemUsageTableSize = mAddLongTableSize;
+ }
+ long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK;
+ addSysMemUsage(longs, idx, data, dataOff);
+ }
+
+ static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) {
+ final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+ final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+ if (dstCount == 0) {
+ dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount;
+ for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) {
+ dstData[dstOff+i] = addData[addOff+i];
+ }
+ } else if (addCount > 0) {
+ dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount;
+ for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) {
+ if (dstData[dstOff+i] > addData[addOff+i]) {
+ dstData[dstOff+i] = addData[addOff+i];
+ }
+ dstData[dstOff+i+1] = (long)(
+ ((dstData[dstOff+i+1]*(double)dstCount)
+ + (addData[addOff+i+1]*(double)addCount))
+ / (dstCount+addCount) );
+ if (dstData[dstOff+i+2] < addData[addOff+i+2]) {
+ dstData[dstOff+i+2] = addData[addOff+i+2];
+ }
+ }
+ }
}
public static final Parcelable.Creator<ProcessStats> CREATOR
@@ -564,6 +657,164 @@
return totalTime;
}
+ static class PssAggr {
+ long pss = 0;
+ long samples = 0;
+
+ void add(long newPss, long newSamples) {
+ pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
+ / (samples+newSamples);
+ samples += newSamples;
+ }
+ }
+
+ public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) {
+ data.totalTime = 0;
+ for (int i=0; i<STATE_COUNT; i++) {
+ data.processStateWeight[i] = 0;
+ data.processStatePss[i] = 0;
+ data.processStateTime[i] = 0;
+ data.processStateSamples[i] = 0;
+ }
+ for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) {
+ data.sysMemUsage[i] = 0;
+ }
+ data.sysMemCachedWeight = 0;
+ data.sysMemFreeWeight = 0;
+ data.sysMemZRamWeight = 0;
+ data.sysMemKernelWeight = 0;
+ data.sysMemNativeWeight = 0;
+ data.sysMemSamples = 0;
+ long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT];
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ int ent = mSysMemUsageTable[i];
+ long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+ addSysMemUsage(totalMemUsage, 0, longs, idx);
+ }
+ for (int is=0; is<data.screenStates.length; is++) {
+ for (int im=0; im<data.memStates.length; im++) {
+ int memBucket = data.screenStates[is] + data.memStates[im];
+ int stateBucket = memBucket * STATE_COUNT;
+ long memTime = mMemFactorDurations[memBucket];
+ if (mMemFactor == memBucket) {
+ memTime += now - mStartTime;
+ }
+ data.totalTime += memTime;
+ int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket);
+ long[] longs = totalMemUsage;
+ int idx = 0;
+ if (sysIdx >= 0) {
+ int ent = mSysMemUsageTable[sysIdx];
+ long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+ if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) {
+ addSysMemUsage(data.sysMemUsage, 0, longs, idx);
+ longs = tmpLongs;
+ idx = tmpIdx;
+ }
+ }
+ data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE]
+ * (double)memTime;
+ data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
+ * (double)memTime;
+ data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
+ * (double)memTime;
+ data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
+ * (double)memTime;
+ data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
+ * (double)memTime;
+ data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
+ }
+ }
+ ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int iproc=0; iproc<procMap.size(); iproc++) {
+ SparseArray<ProcessState> uids = procMap.valueAt(iproc);
+ for (int iu=0; iu<uids.size(); iu++) {
+ final ProcessState proc = uids.valueAt(iu);
+ final PssAggr fgPss = new PssAggr();
+ final PssAggr bgPss = new PssAggr();
+ final PssAggr cachedPss = new PssAggr();
+ boolean havePss = false;
+ for (int i=0; i<proc.mDurationsTableSize; i++) {
+ int off = proc.mDurationsTable[i];
+ int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ int procState = type % STATE_COUNT;
+ long samples = proc.getPssSampleCount(type);
+ if (samples > 0) {
+ long avg = proc.getPssAverage(type);
+ havePss = true;
+ if (procState <= STATE_IMPORTANT_FOREGROUND) {
+ fgPss.add(avg, samples);
+ } else if (procState <= STATE_RECEIVER) {
+ bgPss.add(avg, samples);
+ } else {
+ cachedPss.add(avg, samples);
+ }
+ }
+ }
+ if (!havePss) {
+ continue;
+ }
+ boolean fgHasBg = false;
+ boolean fgHasCached = false;
+ boolean bgHasCached = false;
+ if (fgPss.samples < 3 && bgPss.samples > 0) {
+ fgHasBg = true;
+ fgPss.add(bgPss.pss, bgPss.samples);
+ }
+ if (fgPss.samples < 3 && cachedPss.samples > 0) {
+ fgHasCached = true;
+ fgPss.add(cachedPss.pss, cachedPss.samples);
+ }
+ if (bgPss.samples < 3 && cachedPss.samples > 0) {
+ bgHasCached = true;
+ bgPss.add(cachedPss.pss, cachedPss.samples);
+ }
+ if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
+ bgPss.add(fgPss.pss, fgPss.samples);
+ }
+ if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
+ cachedPss.add(bgPss.pss, bgPss.samples);
+ }
+ if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
+ cachedPss.add(fgPss.pss, fgPss.samples);
+ }
+ for (int i=0; i<proc.mDurationsTableSize; i++) {
+ final int off = proc.mDurationsTable[i];
+ final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ long time = getLong(off, 0);
+ if (proc.mCurState == type) {
+ time += now - proc.mStartTime;
+ }
+ final int procState = type % STATE_COUNT;
+ data.processStateTime[procState] += time;
+ long samples = proc.getPssSampleCount(type);
+ long avg;
+ if (samples > 0) {
+ avg = proc.getPssAverage(type);
+ } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
+ samples = fgPss.samples;
+ avg = fgPss.pss;
+ } else if (procState <= STATE_RECEIVER) {
+ samples = bgPss.samples;
+ avg = bgPss.pss;
+ } else {
+ samples = cachedPss.samples;
+ avg = cachedPss.pss;
+ }
+ double newAvg = ( (data.processStatePss[procState]
+ * (double)data.processStateSamples[procState])
+ + (avg*(double)samples)
+ ) / (data.processStateSamples[procState]+samples);
+ data.processStatePss[procState] = (long)newAvg;
+ data.processStateSamples[procState] += samples;
+ data.processStateWeight[procState] += avg * (double)time;
+ }
+ }
+ }
+ }
+
static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc,
int[] screenStates, int[] memStates, int[] procStates, long now) {
long totalTime = 0;
@@ -679,6 +930,62 @@
}
}
+ long getSysMemUsageValue(int state, int index) {
+ int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+ return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0;
+ }
+
+ void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label,
+ int bucket, int index) {
+ pw.print(prefix); pw.print(label);
+ pw.print(": ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+ pw.print(" min, ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+ pw.print(" avg, ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+ pw.println(" max");
+ }
+
+ void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates,
+ int[] memStates) {
+ int printedScreen = -1;
+ for (int is=0; is<screenStates.length; is++) {
+ int printedMem = -1;
+ for (int im=0; im<memStates.length; im++) {
+ final int iscreen = screenStates[is];
+ final int imem = memStates[im];
+ final int bucket = ((iscreen + imem) * STATE_COUNT);
+ long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT);
+ if (count > 0) {
+ pw.print(prefix);
+ if (screenStates.length > 1) {
+ printScreenLabel(pw, printedScreen != iscreen
+ ? iscreen : STATE_NOTHING);
+ printedScreen = iscreen;
+ }
+ if (memStates.length > 1) {
+ printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0');
+ printedMem = imem;
+ }
+ pw.print(": ");
+ pw.print(count);
+ pw.println(" samples:");
+ dumpSysMemUsageCategory(pw, prefix, " Cached", bucket,
+ SYS_MEM_USAGE_CACHED_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Free", bucket,
+ SYS_MEM_USAGE_FREE_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " ZRam", bucket,
+ SYS_MEM_USAGE_ZRAM_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Kernel", bucket,
+ SYS_MEM_USAGE_KERNEL_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Native", bucket,
+ SYS_MEM_USAGE_NATIVE_MINIMUM);
+ }
+ }
+ }
+ }
+
static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
int[] memStates, int[] procStates) {
final int NS = screenStates != null ? screenStates.length : 1;
@@ -1088,10 +1395,13 @@
mTimePeriodStartClock = System.currentTimeMillis();
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+ mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
mLongs.clear();
mLongs.add(new long[LONGS_SIZE]);
mNextLong = 0;
Arrays.fill(mMemFactorDurations, 0);
+ mSysMemUsageTable = null;
+ mSysMemUsageTableSize = 0;
mStartTime = 0;
mReadError = null;
mFlags = 0;
@@ -1220,12 +1530,17 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- long now = SystemClock.uptimeMillis();
+ writeToParcel(out, SystemClock.uptimeMillis(), flags);
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel out, long now, int flags) {
out.writeInt(MAGIC);
out.writeInt(PARCEL_VERSION);
out.writeInt(STATE_COUNT);
out.writeInt(ADJ_COUNT);
out.writeInt(PSS_COUNT);
+ out.writeInt(SYS_MEM_USAGE_COUNT);
out.writeInt(LONGS_SIZE);
mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
@@ -1268,8 +1583,9 @@
out.writeLong(mTimePeriodStartClock);
out.writeLong(mTimePeriodStartRealtime);
out.writeLong(mTimePeriodEndRealtime);
+ out.writeLong(mTimePeriodStartUptime);
+ out.writeLong(mTimePeriodEndUptime);
out.writeString(mRuntime);
- out.writeString(mWebView);
out.writeInt(mFlags);
out.writeInt(mLongs.size());
@@ -1287,6 +1603,13 @@
}
writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length);
+ out.writeInt(mSysMemUsageTableSize);
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": "
+ + printLongOffset(mSysMemUsageTable[i]));
+ out.writeInt(mSysMemUsageTable[i]);
+ }
+
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
@@ -1417,6 +1740,9 @@
if (!readCheckedInt(in, PSS_COUNT, "pss count")) {
return;
}
+ if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) {
+ return;
+ }
if (!readCheckedInt(in, LONGS_SIZE, "longs size")) {
return;
}
@@ -1427,8 +1753,9 @@
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = in.readLong();
mTimePeriodEndRealtime = in.readLong();
+ mTimePeriodStartUptime = in.readLong();
+ mTimePeriodEndUptime = in.readLong();
mRuntime = in.readString();
- mWebView = in.readString();
mFlags = in.readInt();
final int NLONGS = in.readInt();
@@ -1447,6 +1774,12 @@
readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
+ mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage");
+ if (mSysMemUsageTable == BAD_TABLE) {
+ return;
+ }
+ mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0;
+
int NPROC = in.readInt();
if (NPROC < 0) {
mReadError = "bad process count: " + NPROC;
@@ -1826,6 +2159,10 @@
boolean dumpAll, boolean activeOnly) {
long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
+ if (mSysMemUsageTable != null) {
+ pw.println("System memory usage:");
+ dumpSysMemUsage(pw, " ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
+ }
ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
boolean printedHeader = false;
boolean sepNeeded = false;
@@ -2089,10 +2426,57 @@
dumpTotalsLocked(pw, now);
}
+ long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight,
+ long totalTime, long curTotalMem, int samples) {
+ if (memWeight != 0) {
+ long mem = (long)(memWeight * 1024 / totalTime);
+ pw.print(prefix);
+ pw.print(label);
+ pw.print(": ");
+ printSizeValue(pw, mem);
+ pw.print(" (");
+ pw.print(samples);
+ pw.print(" samples)");
+ pw.println();
+ return curTotalMem + mem;
+ }
+ return curTotalMem;
+ }
+
void dumpTotalsLocked(PrintWriter pw, long now) {
pw.println("Run time Stats:");
dumpSingleTime(pw, " ", mMemFactorDurations, mMemFactor, mStartTime, now);
pw.println();
+ pw.println("Memory usage:");
+ TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+ ALL_MEM_ADJ);
+ computeTotalMemoryUse(totalMem, now);
+ long totalPss = 0;
+ totalPss = printMemoryCategory(pw, " ", "Kernel ", totalMem.sysMemKernelWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Native ", totalMem.sysMemNativeWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ for (int i=0; i<STATE_COUNT; i++) {
+ // Skip restarting service state -- that is not actually a running process.
+ if (i != STATE_SERVICE_RESTARTING) {
+ totalPss = printMemoryCategory(pw, " ", STATE_NAMES[i],
+ totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
+ totalMem.processStateSamples[i]);
+ }
+ }
+ totalPss = printMemoryCategory(pw, " ", "Cached ", totalMem.sysMemCachedWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Free ", totalMem.sysMemFreeWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ pw.print(" TOTAL : ");
+ printSizeValue(pw, totalPss);
+ pw.println();
+ printMemoryCategory(pw, " ", STATE_NAMES[STATE_SERVICE_RESTARTING],
+ totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
+ totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
+ pw.println();
pw.print(" Start time: ");
pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
pw.println();
@@ -2118,8 +2502,6 @@
}
pw.print(' ');
pw.print(mRuntime);
- pw.print(' ');
- pw.print(mWebView);
pw.println();
}
@@ -2208,7 +2590,7 @@
public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
final long now = SystemClock.uptimeMillis();
final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
- pw.println("vers,4");
+ pw.println("vers,5");
pw.print("period,"); pw.print(mTimePeriodStartClockStr);
pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2229,7 +2611,7 @@
pw.print(",partial");
}
pw.println();
- pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
+ pw.print("config,"); pw.println(mRuntime);
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -2362,6 +2744,53 @@
pw.print("total");
dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor,
mStartTime, now);
+ if (mSysMemUsageTable != null) {
+ pw.print("sysmemusage");
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ int off = mSysMemUsageTable[i];
+ int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ pw.print(",");
+ printProcStateTag(pw, type);
+ for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) {
+ if (j > SYS_MEM_USAGE_CACHED_MINIMUM) {
+ pw.print(":");
+ }
+ pw.print(getLong(off, j));
+ }
+ }
+ }
+ pw.println();
+ TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+ ALL_MEM_ADJ);
+ computeTotalMemoryUse(totalMem, now);
+ pw.print("weights,");
+ pw.print(totalMem.totalTime);
+ pw.print(",");
+ pw.print(totalMem.sysMemCachedWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemFreeWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemZRamWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemKernelWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemNativeWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ for (int i=0; i<STATE_COUNT; i++) {
+ pw.print(",");
+ pw.print(totalMem.processStateWeight[i]);
+ pw.print(":");
+ pw.print(totalMem.processStateSamples[i]);
+ }
pw.println();
}
@@ -2452,6 +2881,15 @@
}
}
+ final public static class ProcessStateHolder {
+ public final int appVersion;
+ public ProcessStats.ProcessState state;
+
+ public ProcessStateHolder(int _appVersion) {
+ appVersion = _appVersion;
+ }
+ }
+
public static final class ProcessState extends DurationsTable {
public ProcessState mCommonProcess;
public final String mPackage;
@@ -2660,7 +3098,7 @@
* @param pkgList Processes to update.
*/
public void setState(int state, int memFactor, long now,
- ArrayMap<String, ProcessState> pkgList) {
+ ArrayMap<String, ProcessStateHolder> pkgList) {
if (state < 0) {
state = mNumStartedServices > 0
? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
@@ -2770,7 +3208,7 @@
}
public void addPss(long pss, long uss, boolean always,
- ArrayMap<String, ProcessState> pkgList) {
+ ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
if (!always) {
if (mLastPssState == mCurState && SystemClock.uptimeMillis()
@@ -2845,7 +3283,7 @@
}
}
- public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) {
+ public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
mCommonProcess.mNumExcessiveWake++;
if (!mCommonProcess.mMultiPackage) {
@@ -2857,7 +3295,7 @@
}
}
- public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) {
+ public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
mCommonProcess.mNumExcessiveCpu++;
if (!mCommonProcess.mMultiPackage) {
@@ -2888,7 +3326,7 @@
}
}
- public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) {
+ public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
ensureNotDead();
mCommonProcess.addCachedKill(1, pss, pss, pss);
if (!mCommonProcess.mMultiPackage) {
@@ -2925,8 +3363,10 @@
return this;
}
- private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) {
- ProcessState proc = pkgList.valueAt(index);
+ private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
+ int index) {
+ ProcessStateHolder holder = pkgList.valueAt(index);
+ ProcessState proc = holder.state;
if (mDead && proc.mCommonProcess != proc) {
// Somehow we are contining to use a process state that is dead, because
// it was not being told it was active during the last commit. We can recover
@@ -2959,7 +3399,7 @@
throw new IllegalStateException("Didn't create per-package process "
+ proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
}
- pkgList.setValueAt(index, proc);
+ holder.state = proc;
}
return proc;
}
@@ -3351,4 +3791,27 @@
}
}
}
+
+ public static class TotalMemoryUseCollection {
+ final int[] screenStates;
+ final int[] memStates;
+
+ public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) {
+ screenStates = _screenStates;
+ memStates = _memStates;
+ }
+
+ public long totalTime;
+ public long[] processStatePss = new long[STATE_COUNT];
+ public double[] processStateWeight = new double[STATE_COUNT];
+ public long[] processStateTime = new long[STATE_COUNT];
+ public int[] processStateSamples = new int[STATE_COUNT];
+ public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT];
+ public double sysMemCachedWeight;
+ public double sysMemFreeWeight;
+ public double sysMemZRamWeight;
+ public double sysMemKernelWeight;
+ public double sysMemNativeWeight;
+ public int sysMemSamples;
+ }
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index ff0ee65..b098de8 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -93,6 +93,9 @@
private File mFullRestoreSetDir;
private HashSet<String> mFullRestorePackages;
+ private FileInputStream mCurFullRestoreStream;
+ private FileOutputStream mFullRestoreSocketStream;
+ private byte[] mFullRestoreBuffer;
public LocalTransport(Context context) {
mContext = context;
@@ -104,34 +107,41 @@
}
}
+ @Override
public String name() {
return new ComponentName(mContext, this.getClass()).flattenToShortString();
}
+ @Override
public Intent configurationIntent() {
// The local transport is not user-configurable
return null;
}
+ @Override
public String currentDestinationString() {
return TRANSPORT_DESTINATION_STRING;
}
+ @Override
public String transportDirName() {
return TRANSPORT_DIR_NAME;
}
+ @Override
public long requestBackupTime() {
// any time is a good time for local backup
return 0;
}
+ @Override
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
deleteContents(mCurrentSetDir);
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
if (DEBUG) {
try {
@@ -191,7 +201,7 @@
entity.write(buf, 0, dataSize);
} catch (IOException e) {
Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
} finally {
entity.close();
}
@@ -199,11 +209,11 @@
entityFile.delete();
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
} catch (IOException e) {
// oops, something went wrong. abort the operation and return error.
Log.v(TAG, "Exception reading backup input:", e);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
@@ -222,6 +232,7 @@
}
}
+ @Override
public int clearBackupData(PackageInfo packageInfo) {
if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
@@ -243,9 +254,10 @@
packageDir.delete();
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int finishBackup() {
if (DEBUG) Log.v(TAG, "finishBackup()");
if (mSocket != null) {
@@ -259,24 +271,27 @@
mFullTargetPackage = null;
mSocket.close();
} catch (IOException e) {
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
} finally {
mSocket = null;
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
// ------------------------------------------------------------------------------------
// Full backup handling
+
+ @Override
public long requestFullBackupTime() {
return 0;
}
+ @Override
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
if (mSocket != null) {
Log.e(TAG, "Attempt to initiate full backup while one is in progress");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
if (DEBUG) {
@@ -291,7 +306,7 @@
mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
} catch (IOException e) {
Log.e(TAG, "Unable to process socket for full backup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullTargetPackage = targetPackage.packageName;
@@ -300,18 +315,19 @@
File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
tarstream = new FileOutputStream(tarball);
} catch (FileNotFoundException e) {
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullBackupOutputStream = new BufferedOutputStream(tarstream);
mFullBackupBuffer = new byte[4096];
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int sendBackupData(int numBytes) {
if (mFullBackupBuffer == null) {
Log.w(TAG, "Attempted sendBackupData before performFullBackup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
if (numBytes > mFullBackupBuffer.length) {
@@ -323,21 +339,23 @@
if (nRead < 0) {
// Something went wrong if we expect data but saw EOD
Log.w(TAG, "Unexpected EOD; failing backup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
numBytes -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
// ------------------------------------------------------------------------------------
// Restore handling
static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ @Override
public RestoreSet[] getAvailableRestoreSets() {
long[] existing = new long[POSSIBLE_SETS.length + 1];
int num = 0;
@@ -358,11 +376,13 @@
return available;
}
+ @Override
public long getCurrentRestoreSet() {
// The current restore set always has the same token
return CURRENT_SET_TOKEN;
}
+ @Override
public int startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "start restore " + token);
mRestorePackages = packages;
@@ -371,7 +391,7 @@
mRestoreSetDir = new File(mDataDir, Long.toString(token));
mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
@Override
@@ -397,6 +417,7 @@
if (maybeFullData.length() > 0) {
if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) = " + name);
mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
+ mCurFullRestoreStream = null; // ensure starting from the ground state
found = true;
}
}
@@ -410,6 +431,7 @@
return RestoreDescription.NO_MORE_PACKAGES;
}
+ @Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
@@ -426,7 +448,7 @@
ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Log.e(TAG, "No keys for package: " + packageDir);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
// We expect at least some data if the directory exists in the first place
@@ -447,10 +469,10 @@
in.close();
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
} catch (IOException e) {
Log.e(TAG, "Unable to read backup records", e);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
@@ -487,38 +509,27 @@
return contents;
}
+ @Override
public void finishRestore() {
if (DEBUG) Log.v(TAG, "finishRestore()");
+ if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
+ resetFullRestoreState();
+ }
+ mRestoreType = 0;
}
// ------------------------------------------------------------------------------------
// Full restore handling
- public int prepareFullRestore(long token, String[] targetPackages) {
- mRestoreSetDir = new File(mDataDir, Long.toString(token));
- mFullRestoreSetDir = new File(mRestoreSetDir, FULL_DATA_DIR);
- mFullRestorePackages = new HashSet<String>();
- if (mFullRestoreSetDir.exists()) {
- List<String> pkgs = Arrays.asList(mFullRestoreSetDir.list());
- HashSet<String> available = new HashSet<String>(pkgs);
-
- for (int i = 0; i < targetPackages.length; i++) {
- if (available.contains(targetPackages[i])) {
- mFullRestorePackages.add(targetPackages[i]);
- }
- }
+ private void resetFullRestoreState() {
+ try {
+ mCurFullRestoreStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to close full restore input stream");
}
- return BackupTransport.TRANSPORT_OK;
- }
-
- /**
- * Ask the transport what package's full data will be restored next. When all apps'
- * data has been delivered, the transport should return {@code null} here.
- * @return The package name of the next application whose data will be restored, or
- * {@code null} if all available package has been delivered.
- */
- public String getNextFullRestorePackage() {
- return null;
+ mCurFullRestoreStream = null;
+ mFullRestoreSocketStream = null;
+ mFullRestoreBuffer = null;
}
/**
@@ -543,7 +554,79 @@
* indicating a fatal error condition that precludes further restore operations
* on the current dataset.
*/
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
- return 0;
+ if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+ throw new IllegalStateException("Asked for full restore data for non-stream package");
+ }
+
+ // first chunk?
+ if (mCurFullRestoreStream == null) {
+ final String name = mRestorePackages[mRestorePackage].packageName;
+ if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
+ File dataset = new File(mRestoreSetFullDir, name);
+ try {
+ mCurFullRestoreStream = new FileInputStream(dataset);
+ } catch (IOException e) {
+ // If we can't open the target package's tarball, we return the single-package
+ // error code and let the caller go on to the next package.
+ Log.e(TAG, "Unable to read archive for " + name);
+ return TRANSPORT_PACKAGE_REJECTED;
+ }
+ mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
+ mFullRestoreBuffer = new byte[32*1024];
+ }
+
+ int nRead;
+ try {
+ nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
+ if (nRead < 0) {
+ // EOF: tell the caller we're done
+ nRead = NO_MORE_DATA;
+ } else if (nRead == 0) {
+ // This shouldn't happen when reading a FileInputStream; we should always
+ // get either a positive nonzero byte count or -1. Log the situation and
+ // treat it as EOF.
+ Log.w(TAG, "read() of archive file returned 0; treating as EOF");
+ nRead = NO_MORE_DATA;
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, " delivering restore chunk: " + nRead);
+ }
+ mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
+ }
+ } catch (IOException e) {
+ return TRANSPORT_ERROR; // Hard error accessing the file; shouldn't happen
+ } finally {
+ // Most transports will need to explicitly close 'socket' here, but this transport
+ // is in the same process as the caller so it can leave it up to the backup manager
+ // to manage both socket fds.
+ }
+
+ return nRead;
}
+
+ /**
+ * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+ * data for restore, it will invoke this method to tell the transport that it should
+ * abandon the data download for the current package. The OS will then either call
+ * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+ * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+ * operation.
+ *
+ * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+ * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+ * transport-level failure. If the transport reports an error here, the entire restore
+ * operation will immediately be finished with no further attempts to restore app data.
+ */
+ @Override
+ public int abortFullRestore() {
+ if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+ throw new IllegalStateException("abortFullRestore() but not currently restoring");
+ }
+ resetFullRestoreState();
+ mRestoreType = 0;
+ return TRANSPORT_OK;
+ }
+
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 240d520..1ccaa0f3 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -291,6 +291,7 @@
final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ long mMobileRadioActiveStartTime;
StopwatchTimer mMobileRadioActiveTimer;
StopwatchTimer mMobileRadioActivePerAppTimer;
LongSamplingCounter mMobileRadioActiveAdjustedTime;
@@ -1425,10 +1426,6 @@
return 0;
}
- long getLastUpdateTimeMs() {
- return mUpdateTime;
- }
-
void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
@@ -2790,11 +2787,11 @@
powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
|| powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
if (active) {
- realElapsedRealtimeMs = elapsedRealtime;
+ mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime;
mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
} else {
realElapsedRealtimeMs = timestampNs / (1000*1000);
- long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+ long lastUpdateTimeMs = mMobileRadioActiveStartTime;
if (realElapsedRealtimeMs < lastUpdateTimeMs) {
Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
+ " is before start time " + lastUpdateTimeMs);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 9998995..c139c9d 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -258,16 +258,16 @@
// can return NULL
static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) {
- switch (src.config()) {
- case SkBitmap::kARGB_8888_Config:
+ switch (src.colorType()) {
+ case kN32_SkColorType:
if (src.isOpaque()) return ToColor_S32_Opaque;
return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw;
- case SkBitmap::kARGB_4444_Config:
+ case kARGB_4444_SkColorType:
if (src.isOpaque()) return ToColor_S4444_Opaque;
return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw;
- case SkBitmap::kRGB_565_Config:
+ case kRGB_565_SkColorType:
return ToColor_S565;
- case SkBitmap::kIndex8_Config:
+ case kIndex_8_SkColorType:
if (src.getColorTable() == NULL) {
return NULL;
}
@@ -291,7 +291,7 @@
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable) {
- SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle);
+ SkColorType colorType = SkBitmapConfigToColorType(static_cast<SkBitmap::Config>(configHandle));
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
if (n < SkAbs32(stride) * (size_t)height) {
@@ -301,12 +301,12 @@
}
// ARGB_4444 is a deprecated format, convert automatically to 8888
- if (config == SkBitmap::kARGB_4444_Config) {
- config = SkBitmap::kARGB_8888_Config;
+ if (colorType == kARGB_4444_SkColorType) {
+ colorType = kN32_SkColorType;
}
SkBitmap bitmap;
- bitmap.setConfig(config, width, height);
+ bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
if (NULL == buff) {
@@ -515,28 +515,29 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
- const bool isMutable = p->readInt32() != 0;
- const SkBitmap::Config config = (SkBitmap::Config)p->readInt32();
- const int width = p->readInt32();
- const int height = p->readInt32();
- const int rowBytes = p->readInt32();
- const int density = p->readInt32();
+ const bool isMutable = p->readInt32() != 0;
+ const SkColorType colorType = (SkColorType)p->readInt32();
+ const SkAlphaType alphaType = (SkAlphaType)p->readInt32();
+ const int width = p->readInt32();
+ const int height = p->readInt32();
+ const int rowBytes = p->readInt32();
+ const int density = p->readInt32();
- if (SkBitmap::kARGB_8888_Config != config &&
- SkBitmap::kRGB_565_Config != config &&
- SkBitmap::kARGB_4444_Config != config &&
- SkBitmap::kIndex8_Config != config &&
- SkBitmap::kA8_Config != config) {
- SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config);
+ if (kN32_SkColorType != colorType &&
+ kRGB_565_SkColorType != colorType &&
+ kARGB_4444_SkColorType != colorType &&
+ kIndex_8_SkColorType != colorType &&
+ kAlpha_8_SkColorType != colorType) {
+ SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType);
return NULL;
}
SkBitmap* bitmap = new SkBitmap;
- bitmap->setConfig(config, width, height, rowBytes);
+ bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes);
SkColorTable* ctable = NULL;
- if (config == SkBitmap::kIndex8_Config) {
+ if (colorType == kIndex_8_SkColorType) {
int count = p->readInt32();
if (count > 0) {
size_t size = count * sizeof(SkPMColor);
@@ -587,13 +588,14 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
p->writeInt32(isMutable);
- p->writeInt32(bitmap->config());
+ p->writeInt32(bitmap->colorType());
+ p->writeInt32(bitmap->alphaType());
p->writeInt32(bitmap->width());
p->writeInt32(bitmap->height());
p->writeInt32(bitmap->rowBytes());
p->writeInt32(density);
- if (bitmap->config() == SkBitmap::kIndex8_Config) {
+ if (bitmap->colorType() == kIndex_8_SkColorType) {
SkColorTable* ctable = bitmap->getColorTable();
if (ctable != NULL) {
int count = ctable->count();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 5106f0d..86ed677 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -386,7 +386,7 @@
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
- outputBitmap->setConfig(SkImageInfo::Make(scaledWidth, scaledHeight,
+ outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
colorType, decodingBitmap.alphaType()));
if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
return nullObjectReturn("allocation failed for scaled bitmap");
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 935e3a0..9e09280 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -703,10 +703,11 @@
jboolean hasAlpha, jlong paintHandle) {
SkCanvas* canvas = getNativeCanvas(canvasHandle);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ SkImageInfo info = SkImageInfo::Make(width, height,
+ hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+ kPremul_SkAlphaType);
SkBitmap bitmap;
- bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config :
- SkBitmap::kRGB_565_Config, width, height);
- if (!bitmap.allocPixels()) {
+ if (!bitmap.allocPixels(info)) {
return;
}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 3a53331..f8bab24 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -36,6 +36,11 @@
using namespace android;
+enum {
+ // Keep up to date with Camera.java
+ CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2,
+};
+
struct fields_t {
jfieldID context;
jfieldID facing;
@@ -466,7 +471,7 @@
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
- jobject weak_this, jint cameraId, jstring clientPackageName)
+ jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
{
// Convert jstring to String16
const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
@@ -474,8 +479,18 @@
String16 clientName(rawClientName, rawClientNameLen);
env->ReleaseStringChars(clientPackageName, rawClientName);
- sp<Camera> camera = Camera::connect(cameraId, clientName,
- Camera::USE_CALLING_UID);
+ sp<Camera> camera;
+ if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) {
+ // Default path: hal version is don't care, do normal camera connect.
+ camera = Camera::connect(cameraId, clientName,
+ Camera::USE_CALLING_UID);
+ } else {
+ jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
+ Camera::USE_CALLING_UID, camera);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ }
if (camera == NULL) {
return -EACCES;
@@ -510,7 +525,6 @@
// finalizer is invoked later.
static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
{
- // TODO: Change to ALOGV
ALOGV("release camera");
JNICameraContext* context = NULL;
sp<Camera> camera;
@@ -891,7 +905,7 @@
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
- "(Ljava/lang/Object;ILjava/lang/String;)I",
+ "(Ljava/lang/Object;IILjava/lang/String;)I",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index ee4c619..849531c 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -287,6 +287,8 @@
env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz,
"errorCallbackFromNative","(I)V"),
check_AudioSystem_Command(err));
+
+ env->DeleteLocalRef(clazz);
}
static jint
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index dc1ea06..4362018 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -388,11 +388,11 @@
static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz,
jlong rendererPtr, jintArray colors, jint offset, jint stride,
jfloat left, jfloat top, jint width, jint height, jboolean hasAlpha, jlong paintPtr) {
+ const SkImageInfo info = SkImageInfo::Make(width, height,
+ hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+ kPremul_SkAlphaType);
SkBitmap* bitmap = new SkBitmap;
- bitmap->setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config,
- width, height);
-
- if (!bitmap->allocPixels()) {
+ if (!bitmap->allocPixels(info)) {
delete bitmap;
return;
}
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
index 0210bd9..5ebed9c 100644
--- a/core/jni/android_view_GraphicBuffer.cpp
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -142,16 +142,16 @@
// Canvas management
// ----------------------------------------------------------------------------
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
+static inline SkColorType convertPixelFormat(int32_t format) {
switch (format) {
case PIXEL_FORMAT_RGBA_8888:
- return SkBitmap::kARGB_8888_Config;
+ return kN32_SkColorType;
case PIXEL_FORMAT_RGBX_8888:
- return SkBitmap::kARGB_8888_Config;
+ return kN32_SkColorType;
case PIXEL_FORMAT_RGB_565:
- return SkBitmap::kRGB_565_Config;
+ return kRGB_565_SkColorType;
default:
- return SkBitmap::kNo_Config;
+ return kUnknown_SkColorType;
}
}
@@ -188,8 +188,10 @@
ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
SkBitmap bitmap;
- bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()),
- buffer->getWidth(), buffer->getHeight(), bytesCount);
+ bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
+ convertPixelFormat(buffer->getPixelFormat()),
+ kPremul_SkAlphaType),
+ bytesCount);
if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
bitmap.setPixels(bits);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 3d14aaf..7018751 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -173,17 +173,17 @@
return value;
}
-static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
+static inline SkColorType convertPixelFormat(PixelFormat format) {
/* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
- we can map to SkBitmap::kARGB_8888_Config, and optionally call
+ we can map to kN32_SkColorType, and optionally call
bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap
(as an accelerator)
*/
switch (format) {
- case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config;
- case PIXEL_FORMAT_RGBA_8888: return SkBitmap::kARGB_8888_Config;
- case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config;
- default: return SkBitmap::kNo_Config;
+ case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType;
+ case PIXEL_FORMAT_RGBA_8888: return kN32_SkColorType;
+ case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType;
+ default: return kUnknown_SkColorType;
}
}
@@ -220,12 +220,16 @@
// Associate a SkCanvas object to this surface
env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format);
+ SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
+ convertPixelFormat(outBuffer.format),
+ kPremul_SkAlphaType);
+ if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
+ info.fAlphaType = kOpaque_SkAlphaType;
+ }
+
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
- bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
- if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
- bitmap.setAlphaType(kOpaque_SkAlphaType);
- }
+ bitmap.setInfo(info, bpr);
if (outBuffer.width > 0 && outBuffer.height > 0) {
bitmap.setPixels(outBuffer.bits);
} else {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index cfc8eb8..9783e91 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -175,7 +175,7 @@
screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
SkBitmap* bitmap = new SkBitmap();
- bitmap->setConfig(screenshotInfo, (size_t)rowBytes);
+ bitmap->setInfo(screenshotInfo, (size_t)rowBytes);
if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) {
// takes ownership of ScreenshotClient
SkMallocPixelRef* pixels = SkMallocPixelRef::NewWithProc(screenshotInfo,
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index c1ab515..5c04a78 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -73,17 +73,28 @@
// Native layer
// ----------------------------------------------------------------------------
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
- switch (format) {
+// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
+static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
+ SkImageInfo info;
+ info.fWidth = buffer.width;
+ info.fHeight = buffer.height;
+ switch (buffer.format) {
case WINDOW_FORMAT_RGBA_8888:
- return SkBitmap::kARGB_8888_Config;
+ info.fColorType = kN32_SkColorType;
+ info.fAlphaType = kPremul_SkAlphaType;
+ break;
case WINDOW_FORMAT_RGBX_8888:
- return SkBitmap::kARGB_8888_Config;
+ info.fColorType = kN32_SkColorType;
+ info.fAlphaType = kOpaque_SkAlphaType;
case WINDOW_FORMAT_RGB_565:
- return SkBitmap::kRGB_565_Config;
+ info.fColorType = kRGB_565_SkColorType;
+ info.fAlphaType = kOpaque_SkAlphaType;
default:
- return SkBitmap::kNo_Config;
+ info.fColorType = kUnknown_SkColorType;
+ info.fAlphaType = kIgnore_SkAlphaType;
+ break;
}
+ return info;
}
/**
@@ -148,11 +159,7 @@
ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format);
SkBitmap bitmap;
- bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount);
-
- if (buffer.format == WINDOW_FORMAT_RGBX_8888) {
- bitmap.setAlphaType(kOpaque_SkAlphaType);
- }
+ bitmap.setInfo(convertPixelFormat(buffer), bytesCount);
if (buffer.width > 0 && buffer.height > 0) {
bitmap.setPixels(buffer.bits);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8b5dff0..fa1a563 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2742,7 +2742,8 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:multiprocess="true"
- android:documentLaunchMode="never">
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true">
<intent-filter>
<action android:name="android.intent.action.CHOOSER" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 7e212be..ace17ae 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,7 +18,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
+ android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100">
<alpha
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
diff --git a/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..4d97045
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..46fb463
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png
new file mode 100644
index 0000000..8866539
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..163f4a0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..bbfb83c7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png
new file mode 100644
index 0000000..2b607df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..21d4d53
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..2aeb9a2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png
new file mode 100644
index 0000000..86316db
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..1b11b59
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..ae0d655
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png
new file mode 100644
index 0000000..e6bd125
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..8774412
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..1375acc
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/lock_task_notify_bg.xml
new file mode 100644
index 0000000..3a8fab5
--- /dev/null
+++ b/core/res/res/drawable/lock_task_notify_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+
+ <solid android:color="@android:color/black" />
+ <corners
+ android:radius="8dip" />
+</shape>
diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml
new file mode 100644
index 0000000..c034536
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_enter.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingBottom="12dp"
+ android:alpha=".8"
+ android:background="@drawable/lock_task_notify_bg" >
+
+ <ImageView
+ android:id="@+id/lock_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:alpha=".8"
+ android:src="@drawable/ic_lock_outline_wht_24dp" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/lock_icon"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="2dp"
+ android:textColor="@android:color/white"
+ android:alpha=".8"
+ android:text="@string/lock_to_app_start" />
+</RelativeLayout>
diff --git a/core/res/res/layout/lock_to_app_exit.xml b/core/res/res/layout/lock_to_app_exit.xml
new file mode 100644
index 0000000..4a60c80
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_exit.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingBottom="12dp"
+ android:alpha=".8"
+ android:background="@drawable/lock_task_notify_bg" >
+
+ <ImageView
+ android:id="@+id/lock_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:alpha=".8"
+ android:src="@drawable/ic_lock_open_wht_24dp" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/lock_icon"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="2dp"
+ android:textColor="@android:color/white"
+ android:alpha=".8"
+ android:text="@string/lock_to_app_exit" />
+</RelativeLayout>
diff --git a/core/res/res/transition/explode.xml b/core/res/res/transition/explode.xml
new file mode 100644
index 0000000..fe22284
--- /dev/null
+++ b/core/res/res/transition/explode.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<explode xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/core/res/res/transition/slide_bottom.xml b/core/res/res/transition/slide_bottom.xml
new file mode 100644
index 0000000..46dc0d6
--- /dev/null
+++ b/core/res/res/transition/slide_bottom.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="bottom"/>
diff --git a/core/res/res/transition/slide_left.xml b/core/res/res/transition/slide_left.xml
new file mode 100644
index 0000000..997bd97
--- /dev/null
+++ b/core/res/res/transition/slide_left.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="left"/>
diff --git a/core/res/res/transition/slide_right.xml b/core/res/res/transition/slide_right.xml
new file mode 100644
index 0000000..98f8f6a
--- /dev/null
+++ b/core/res/res/transition/slide_right.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="right"/>
diff --git a/core/res/res/transition/slide_top.xml b/core/res/res/transition/slide_top.xml
new file mode 100644
index 0000000..07ab945
--- /dev/null
+++ b/core/res/res/transition/slide_top.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="top"/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 198333f..4a6311a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4978,6 +4978,16 @@
<!-- ========================== -->
<eat-comment />
+ <!-- Drawable used to draw vector paths. -->
+ <declare-styleable name="VectorDrawable">
+ <!-- If set, specifies the color to apply to the drawable as a tint. By default,
+ no tint is applied. May be a color state list. -->
+ <attr name="tint" />
+ <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
+ default value is src_in, which treats the drawable as an alpha mask. -->
+ <attr name="tintMode" />
+ </declare-styleable>
+
<!-- Define the virtual size of the drawing surface paths will draw to. -->
<declare-styleable name="VectorDrawableViewport">
<!-- The width of the canvas the drawing is on. -->
@@ -6750,6 +6760,8 @@
<attr name="switchPadding" format="dimension" />
<!-- Whether to split the track and leave a gap for the thumb drawable. -->
<attr name="splitTrack" />
+ <!-- Whether to draw on/off text. -->
+ <attr name="showText" format="boolean" />
</declare-styleable>
<declare-styleable name="Pointer">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4e06d9a..fc1d0df 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -985,6 +985,14 @@
Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} -->
<attr name="autoRemoveFromRecents" format="boolean" />
+ <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the
+ next activity in the task. If the next activity also has this attribute set to true then
+ it will yield the baseIntent to any activity that it launches in the same task. This
+ continues until an activity is encountered which has this attribute set to false. False
+ is the default. This attribute set to true also permits activity's use of the
+ TaskDescription to change labels, colors and icons in the recent task list. -->
+ <attr name="relinquishTaskIdentity" format="boolean" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1653,6 +1661,7 @@
<attr name="documentLaunchMode" />
<attr name="maxRecents" />
<attr name="autoRemoveFromRecents" />
+ <attr name="relinquishTaskIdentity" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 471eece..d350ef2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1597,5 +1597,12 @@
<item>"1,sim,0,modem"</item>
</string-array>
+ <!-- This string array can be overriden to add an additional DRM support for WebView EME. -->
+ <!-- Array of "[keySystemName],[UuidOfMediaDrm]" -->
+ <string-array name="config_keySystemUuidMapping" translatable="false">
+ <!-- Example:
+ <item>"x-com.microsoft.playready,9A04F079-9840-4286-AB92-E65BE0885F95"</item>
+ -->
+ </string-array>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f73c958..c3e4d94 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2209,6 +2209,7 @@
<public type="attr" name="fullBackupOnly" />
<public type="attr" name="propertyXName" />
<public type="attr" name="propertyYName" />
+ <public type="attr" name="relinquishTaskIdentity" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
@@ -2460,5 +2461,26 @@
<!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
<public type="interpolator" name="fast_out_linear_in" />
+ <!-- Used for Activity Transitions, this transition indicates that no Transition
+ should be used. -->
<public type="transition" name="no_transition" id="0x010f0000"/>
+ <!-- A transition that moves and resizes a view -->
+ <public type="transition" name="move"/>
+ <!-- A transition that fades views in and out. -->
+ <public type="transition" name="fade"/>
+ <!-- A transition that moves views in or out of the scene to or from the edges when
+ a view visibility changes. -->
+ <public type="transition" name="explode"/>
+ <!-- A transition that moves views in or out of the scene to or from the bottom edge when
+ a view visibility changes. -->
+ <public type="transition" name="slide_bottom"/>
+ <!-- A transition that moves views in or out of the scene to or from the top edge when
+ a view visibility changes. -->
+ <public type="transition" name="slide_top"/>
+ <!-- A transition that moves views in or out of the scene to or from the right edge when
+ a view visibility changes. -->
+ <public type="transition" name="slide_right"/>
+ <!-- A transition that moves views in or out of the scene to or from the left edge when
+ a view visibility changes. -->
+ <public type="transition" name="slide_left"/>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8c0d23..e017f53 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4746,4 +4746,17 @@
<!-- DO NOT TRANSLATE -->
<string name="day_of_week_label_typeface">sans-serif</string>
+ <!-- Lock-to-app dialog title. -->
+ <string name="lock_to_app_title">Use lock-to-app?</string>
+ <!-- Lock-to-app dialog description. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. -->
+ <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $</string>
+ <!-- Lock-to-app negative response. -->
+ <string name="lock_to_app_negative">NO</string>
+ <!-- Lock-to-app positive response. -->
+ <string name="lock_to_app_positive">START</string>
+ <!-- Starting lock-to-app indication. -->
+ <string name="lock_to_app_start">Start Lock-to-app</string>
+ <!-- Exting lock-to-app indication. -->
+ <string name="lock_to_app_exit">Exit Lock-to-app</string>
+
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 25307b9..75f905c 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -493,11 +493,12 @@
<item name="thumb">@drawable/switch_thumb_material_anim</item>
<item name="splitTrack">true</item>
<item name="switchTextAppearance">@style/TextAppearance.Material.Widget.Switch</item>
- <item name="textOn"></item>
- <item name="textOff"></item>
+ <item name="textOn">@string/capital_on</item>
+ <item name="textOff">@string/capital_off</item>
<item name="switchMinWidth">4dip</item>
<item name="switchPadding">4dip</item>
<item name="background">?attr/selectableItemBackgroundBorderless</item>
+ <item name="showText">false</item>
</style>
<style name="Widget.Material.EditText" parent="Widget.EditText"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1547bbd..3e82d08 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -604,6 +604,14 @@
<java-symbol type="string" name="kilobyteShort" />
<java-symbol type="string" name="last_month" />
<java-symbol type="string" name="launchBrowserDefault" />
+ <java-symbol type="string" name="lock_to_app_title" />
+ <java-symbol type="string" name="lock_to_app_description" />
+ <java-symbol type="string" name="lock_to_app_negative" />
+ <java-symbol type="string" name="lock_to_app_positive" />
+ <java-symbol type="drawable" name="ic_recent" />
+ <java-symbol type="layout" name="lock_to_app_enter" />
+ <java-symbol type="layout" name="lock_to_app_exit" />
+ <java-symbol type="drawable" name="lock_task_notify_bg" />
<java-symbol type="string" name="lockscreen_access_pattern_cell_added" />
<java-symbol type="string" name="lockscreen_access_pattern_cleared" />
<java-symbol type="string" name="lockscreen_access_pattern_detected" />
@@ -1002,6 +1010,7 @@
<java-symbol type="array" name="config_callBarringMMI" />
<java-symbol type="array" name="config_globalActionsList" />
<java-symbol type="array" name="config_telephonyHardware" />
+ <java-symbol type="array" name="config_keySystemUuidMapping" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="indicator_input_error" />
diff --git a/docs/html/preview/images/l-dev-prev.png b/docs/html/preview/images/l-dev-prev.png
index 95bad8c..eae6ede 100644
--- a/docs/html/preview/images/l-dev-prev.png
+++ b/docs/html/preview/images/l-dev-prev.png
Binary files differ
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 368db84..d4610b5 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -163,8 +163,8 @@
platform officially launches.
</div>
- <img src="/preview/images/l-dev-prev.png" style=" margin:10px 0 0 100px" width="700px"/>
- <div class="col-6" style="margin-left:630px; margin-top:-40px">
+ <img src="/preview/images/l-dev-prev.png" style=" margin:0px 0 0 40px" width="860px"/>
+ <div class="col-6" style="margin-left:660px; margin-top:-105px">
<a href="/preview/setup-sdk.html" class="landing-button landing-secondary" style="position:absolute;z-index:100;float:right;margin-top: 0px; background-color:#09c">Get Started</a><!--
<p>Set up your environment and check out all the docs to get up and running.</p>-->
@@ -176,7 +176,7 @@
-<div class="landing-section landing-gray-background" style="margin-top:-80px; padding-bottom:20px">
+<div class="landing-section landing-gray-background" style="margin-top:-135px; padding-bottom:20px">
<div class="wrap">
<div class="cols">
<div class="landing-body" style="margin-top:-80px" >
@@ -186,14 +186,14 @@
<p>A New UI Design</p>
<p class="landing-small">
Create a consistent experience across mobile and the web with
- material design, the new Google-wide standard.
+ <b>material design</b>, the new Google-wide standard.
</p>
<p class="landing-small">
<a href="/preview/material/index.html">Learn about material</a>
</p>
</div>
<div class="col-4">
- <p>A Rehauled Runtime</p>
+ <p>A New Runtime</p>
<p class="landing-small">
Test your apps and get them ready for <b>ART</b> (<b>A</b>ndroid <b>R</b>un<b>t</b>ime),
the default runtime in the next release.
@@ -205,21 +205,21 @@
<div class="col-4">
<p style="width:230px">Enhanced Notifications</p>
<p class="landing-small">
- Get more control over where notifications appear,
- how they look, and automatic syncing to non-handheld devices.
+ Get control over where notifications appear,
+ how they look, and how they sync to non-handheld devices.
</p>
<p class="landing-small">
- <a href="/preview/api-overview.html#UI">Learn more</a>
+ <a href="/preview/api-overview.html#UI">Learn about notifications</a>
</p>
</div>
<div class="col-4">
- <p>Project Volta</p>
+ <p>Increased Efficiency</p>
<p class="landing-small">
- We've tuned the platform to be more energy efficient and
+ <b>Project Volta</b> is our effort to make the platform energy efficient and
to give you more control over resource usage.
</p>
<p class="landing-small">
- <a href="/preview/api-overview.html#Power">Learn more</a>
+ <a href="/preview/api-overview.html#Power">Learn about Project Volta</a>
</p>
</div>
</div>
diff --git a/docs/html/tools/help/adb.jd b/docs/html/tools/help/adb.jd
index f980042..e2dd196 100644
--- a/docs/html/tools/help/adb.jd
+++ b/docs/html/tools/help/adb.jd
@@ -8,6 +8,7 @@
<div id="qv">
<h2>In this document</h2>
<ol>
+ <li><a href="#Enabling">Enabling adb Debugging</a></li>
<li><a href="#issuingcommands">Syntax</a></li>
<li><a href="#commandsummary">Commands</a></li>
<li><a href="#devicestatus">Querying for Emulator/Device Instances</a></li>
@@ -72,6 +73,19 @@
instance from any client (or from a script).</p>
+<h2 id="Enabling">Enabling adb Debugging</h2>
+
+<p>In order to use adb with a device connected over USB, you must enable
+<strong>USB debugging</strong> in the device system settings, under <strong>
+Developer options</strong>.</p>
+
+<p>On Android 4.2 and higher, the Developer options screen is
+hidden by default. To make it visible, go to
+<b>Settings > About phone</b> and tap <b>Build number</b> seven times. Return to the previous
+screen to find <strong>Developer options</strong> at the bottom.</p>
+
+<p>On some devices, the Developer options screen may be located or named differently.</p>
+
<p class="note"><strong>Note:</strong> When you connect a device running Android 4.2.2 or higher
to your computer, the system shows a dialog asking whether to accept an RSA key that allows
debugging through this computer. This security mechanism protects user devices because it ensures
@@ -80,6 +94,11 @@
SDK Platform-tools r16.0.1 and higher) in order to debug on a device running Android 4.2.2 or
higher.</p>
+<p>For more information about connecting to a device over USB, read
+<a href="{@docRoot}tools/device.html">Using Hardware Devices</a>.</p>
+
+
+
<h2 id="issuingcommands">Syntax</h2>
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 005b8ef..28cd869 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -135,7 +135,6 @@
private Paint mStrokePaint; // optional, set by the caller
private ColorFilter mColorFilter; // optional, set by the caller
private int mAlpha = 0xFF; // modified by the caller
- private boolean mDither;
private final Path mPath = new Path();
private final RectF mRect = new RectF();
@@ -543,7 +542,7 @@
if (mLayerPaint == null) {
mLayerPaint = new Paint();
}
- mLayerPaint.setDither(mDither);
+ mLayerPaint.setDither(st.mDither);
mLayerPaint.setAlpha(mAlpha);
mLayerPaint.setColorFilter(mColorFilter);
@@ -561,14 +560,14 @@
individual paints
*/
mFillPaint.setAlpha(currFillAlpha);
- mFillPaint.setDither(mDither);
+ mFillPaint.setDither(st.mDither);
mFillPaint.setColorFilter(mColorFilter);
- if (mColorFilter != null && mGradientState.mColorStateList == null) {
+ if (mColorFilter != null && st.mColorStateList == null) {
mFillPaint.setColor(mAlpha << 24);
}
if (haveStroke) {
mStrokePaint.setAlpha(currStrokeAlpha);
- mStrokePaint.setDither(mDither);
+ mStrokePaint.setDither(st.mDither);
mStrokePaint.setColorFilter(mColorFilter);
}
}
@@ -804,8 +803,8 @@
@Override
public void setDither(boolean dither) {
- if (dither != mDither) {
- mDither = dither;
+ if (dither != mGradientState.mDither) {
+ mGradientState.mDither = dither;
invalidateSelf();
}
}
@@ -1015,7 +1014,7 @@
state.mThemeAttrs = a.extractThemeAttrs();
state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
- mDither = a.getBoolean(R.styleable.GradientDrawable_dither, mDither);
+ state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
if (state.mShape == RING) {
state.mInnerRadius = a.getDimensionPixelSize(
@@ -1459,6 +1458,8 @@
public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
public int mInnerRadius = -1;
public int mThickness = -1;
+ public boolean mDither = false;
+
private float mCenterX = 0.5f;
private float mCenterY = 0.5f;
private float mGradientRadius = 0.5f;
@@ -1510,6 +1511,7 @@
mThicknessRatio = state.mThicknessRatio;
mInnerRadius = state.mInnerRadius;
mThickness = state.mThickness;
+ mDither = state.mDither;
mCenterX = state.mCenterX;
mCenterY = state.mCenterY;
mGradientRadius = state.mGradientRadius;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0512ecc..f2e75a5 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -645,25 +645,29 @@
@Override
public Rect getDirtyBounds() {
- final Rect drawingBounds = mDrawingBounds;
- final Rect dirtyBounds = mDirtyBounds;
- dirtyBounds.set(drawingBounds);
- drawingBounds.setEmpty();
+ if (isProjected()) {
+ final Rect drawingBounds = mDrawingBounds;
+ final Rect dirtyBounds = mDirtyBounds;
+ dirtyBounds.set(drawingBounds);
+ drawingBounds.setEmpty();
- final int cX = (int) mHotspotBounds.exactCenterX();
- final int cY = (int) mHotspotBounds.exactCenterY();
- final Rect rippleBounds = mTempRect;
- final Ripple[] activeRipples = mAnimatingRipples;
- final int N = mAnimatingRipplesCount;
- for (int i = 0; i < N; i++) {
- activeRipples[i].getBounds(rippleBounds);
- rippleBounds.offset(cX, cY);
- drawingBounds.union(rippleBounds);
+ final int cX = (int) mHotspotBounds.exactCenterX();
+ final int cY = (int) mHotspotBounds.exactCenterY();
+ final Rect rippleBounds = mTempRect;
+ final Ripple[] activeRipples = mAnimatingRipples;
+ final int N = mAnimatingRipplesCount;
+ for (int i = 0; i < N; i++) {
+ activeRipples[i].getBounds(rippleBounds);
+ rippleBounds.offset(cX, cY);
+ drawingBounds.union(rippleBounds);
+ }
+
+ dirtyBounds.union(drawingBounds);
+ dirtyBounds.union(super.getDirtyBounds());
+ return dirtyBounds;
+ } else {
+ return getBounds();
}
-
- dirtyBounds.union(drawingBounds);
- dirtyBounds.union(super.getDirtyBounds());
- return dirtyBounds;
}
@Override
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index beb300d..369bb59 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -416,6 +416,9 @@
final ShapeState state = mShapeState;
final Paint paint = state.mPaint;
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
int color = paint.getColor();
color = a.getColor(R.styleable.ShapeDrawable_color, color);
paint.setColor(color);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 7c6c67d..3773a9b 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -286,13 +286,6 @@
}
@Override
- public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
- throws XmlPullParserException, IOException {
- final VPathRenderer p = inflateInternal(res, parser, attrs, theme);
- setPathRenderer(p);
- }
-
- @Override
public boolean canApplyTheme() {
return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
}
@@ -335,13 +328,44 @@
return color;
}
- private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
- Theme theme) throws XmlPullParserException, IOException {
+
+ @Override
+ public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ final TypedArray a = obtainAttributes(res, theme, attrs,R.styleable.VectorDrawable);
+ updateStateFromTypedArray(a);
+ a.recycle();
+
+ final VectorDrawableState state = mVectorState;
+ state.mVPathRenderer = inflateInternal(res, parser, attrs, theme);
+
+ mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ state.mVPathRenderer.setColorFilter(mTintFilter);
+ }
+
+ private void updateStateFromTypedArray(TypedArray a) {
+ final VectorDrawableState state = mVectorState;
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
+ if (tintMode != -1) {
+ state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+ }
+
+ final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
+ if (tint != null) {
+ state.mTint = tint;
+ }
+ }
+
+ private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
final VPathRenderer pathRenderer = new VPathRenderer();
boolean noSizeTag = true;
boolean noViewportTag = true;
- boolean noGroupTag = true;
boolean noPathTag = true;
// Use a stack to help to build the group tree.
@@ -377,7 +401,6 @@
if (newChildGroup.getGroupName() != null) {
mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
}
- noGroupTag = false;
}
} else if (eventType == XmlPullParser.END_TAG) {
final String tagName = parser.getName();
@@ -435,11 +458,8 @@
}
}
- private void setPathRenderer(VPathRenderer pathRenderer) {
- mVectorState.mVPathRenderer = pathRenderer;
- }
-
private static class VectorDrawableState extends ConstantState {
+ int[] mThemeAttrs;
int mChangingConfigurations;
VPathRenderer mVPathRenderer;
Rect mPadding;
@@ -448,6 +468,7 @@
public VectorDrawableState(VectorDrawableState copy) {
if (copy != null) {
+ mThemeAttrs = copy.mThemeAttrs;
mChangingConfigurations = copy.mChangingConfigurations;
// TODO: Make sure the constant state are handled correctly.
mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index eff3011..dc6d852 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -160,6 +160,10 @@
(target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
}
+uint32_t RenderPropertyAnimator::dirtyMask() {
+ return mPropertyAccess->dirtyMask;
+}
+
float RenderPropertyAnimator::getValue(RenderNode* target) const {
return (target->properties().*mPropertyAccess->getter)();
}
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 203cdff..6cb72c4c 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -61,6 +61,8 @@
bool isFinished() { return mPlayState == FINISHED; }
float finalValue() { return mFinalValue; }
+ ANDROID_API virtual uint32_t dirtyMask() { return 0; }
+
protected:
BaseRenderNodeAnimator(float finalValue);
virtual ~BaseRenderNodeAnimator();
@@ -112,6 +114,8 @@
ANDROID_API virtual void onAttached(RenderNode* target);
+ ANDROID_API virtual uint32_t dirtyMask();
+
protected:
virtual float getValue(RenderNode* target) const;
virtual void setValue(RenderNode* target, float value);
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
index 1f84b86..fc0e8a0 100644
--- a/libs/hwui/Interpolator.cpp
+++ b/libs/hwui/Interpolator.cpp
@@ -112,6 +112,10 @@
int i1 = (int) ipart;
int i2 = MathUtils::min(i1 + 1, mSize - 1);
+ LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
+ " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
+ i1, i2, input, lutpos, mSize, mValues, ipart, weight);
+
float v1 = mValues[i1];
float v2 = mValues[i2];
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9dd5aa5..97eb583 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -104,8 +104,7 @@
}
static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
- bitmap.setConfig(SkBitmap::kA8_Config, width, height);
- bitmap.allocPixels();
+ bitmap.allocPixels(SkImageInfo::MakeA8(width, height));
bitmap.eraseColor(0);
}
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b2fe849..2d6d409 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -189,6 +189,8 @@
// UI thread only!
ANDROID_API void removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
mStagingAnimators.erase(animator);
+ // Force a sync of the staging property value
+ mDirtyPropertyFields |= animator->dirtyMask();
mNeedsAnimatorsSync = true;
}
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 1001cae0..9212d0a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -333,8 +333,7 @@
void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap,
uint32_t width, uint32_t height) {
SkBitmap rgbaBitmap;
- rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType());
- rgbaBitmap.allocPixels();
+ rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType()));
rgbaBitmap.eraseColor(0);
SkCanvas canvas(rgbaBitmap);
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 3f6ccc9..063383b 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -206,9 +206,8 @@
} else {
SkBitmap surfaceBitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
- surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
- outBuffer.width, outBuffer.height, bpr);
- surfaceBitmap.setPixels(outBuffer.bits);
+ surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height),
+ outBuffer.bits, bpr);
SkCanvas surfaceCanvas(surfaceBitmap);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d35225a..fb19242 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1254,11 +1254,6 @@
* call {@link #stopBluetoothSco()} to clear the request and turn down the bluetooth connection.
* <p>Even if a SCO connection is established, the following restrictions apply on audio
* output streams so that they can be routed to SCO headset:
- * <p>NOTE: up to and including API version
- * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
- * voice call to the bluetooth headset.
- * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
- * connection is established.
* <ul>
* <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
* <li> the format must be mono </li>
@@ -1274,6 +1269,11 @@
* it will be ignored. Similarly, if a call is received or sent while an application
* is using the SCO connection, the connection will be lost for the application and NOT
* returned automatically when the call ends.
+ * <p>NOTE: up to and including API version
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
+ * voice call to the bluetooth headset.
+ * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
+ * connection is established.
* @see #stopBluetoothSco()
* @see #ACTION_SCO_AUDIO_STATE_UPDATED
*/
@@ -1287,13 +1287,38 @@
}
/**
+ * Start bluetooth SCO audio connection in virtual call mode.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+ * <p>Similar to {@link #startBluetoothSco()} with explicit selection of virtual call mode.
+ * Telephony and communication applications (VoIP, Video Chat) should preferably select
+ * virtual call mode.
+ * Applications using voice input for search or commands should first try raw audio connection
+ * with {@link #startBluetoothSco()} and fall back to startBluetoothScoVirtualCall() in case of
+ * failure.
+ * @see #startBluetoothSco()
+ * @see #stopBluetoothSco()
+ * @see #ACTION_SCO_AUDIO_STATE_UPDATED
+ */
+ public void startBluetoothScoVirtualCall() {
+ IAudioService service = getService();
+ try {
+ service.startBluetoothScoVirtualCall(mICallBack);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in startBluetoothScoVirtualCall", e);
+ }
+ }
+
+ /**
* Stop bluetooth SCO audio connection.
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
* <p>This method must be called by applications having requested the use of
- * bluetooth SCO audio with {@link #startBluetoothSco()}
- * when finished with the SCO connection or if connection fails.
+ * bluetooth SCO audio with {@link #startBluetoothSco()} or
+ * {@link #startBluetoothScoVirtualCall()} when finished with the SCO connection or
+ * if connection fails.
* @see #startBluetoothSco()
+ * @see #startBluetoothScoVirtualCall()
*/
public void stopBluetoothSco(){
IAudioService service = getService();
@@ -2169,48 +2194,8 @@
Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
return;
}
- IAudioService service = getService();
- try {
- // pi != null, this is currently still needed to support across
- // reboot launching of the last app.
- service.registerMediaButtonIntent(pi, eventReceiver,
- eventReceiver == null ? mToken : null);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
- }
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.addMediaButtonListener(pi, mContext);
- }
-
- /**
- * @hide
- * Used internally by telephony package to register an intent receiver for ACTION_MEDIA_BUTTON.
- * @param eventReceiver the component that will receive the media button key events,
- * no-op if eventReceiver is null
- */
- public void registerMediaButtonEventReceiverForCalls(ComponentName eventReceiver) {
- if (eventReceiver == null) {
- return;
- }
- IAudioService service = getService();
- try {
- // eventReceiver != null
- service.registerMediaButtonEventReceiverForCalls(eventReceiver);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerMediaButtonEventReceiverForCalls", e);
- }
- }
-
- /**
- * @hide
- */
- public void unregisterMediaButtonEventReceiverForCalls() {
- IAudioService service = getService();
- try {
- service.unregisterMediaButtonEventReceiverForCalls();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiverForCalls", e);
- }
+ helper.addMediaButtonListener(pi, eventReceiver, mContext);
}
/**
@@ -2247,12 +2232,6 @@
* @hide
*/
public void unregisterMediaButtonIntent(PendingIntent pi) {
- IAudioService service = getService();
- try {
- service.unregisterMediaButtonIntent(pi);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
- }
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
helper.removeMediaButtonListener(pi);
}
@@ -2418,46 +2397,6 @@
}
/**
- * @hide
- * Request the user of a RemoteControlClient to seek to the given playback position.
- * @param generationId the RemoteControlClient generation counter for which this request is
- * issued. Requests for an older generation than current one will be ignored.
- * @param timeMs the time in ms to seek to, must be positive.
- */
- public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
- if (timeMs < 0) {
- return;
- }
- IAudioService service = getService();
- try {
- service.setRemoteControlClientPlaybackPosition(generationId, timeMs);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", "
- + timeMs + ")", e);
- }
- }
-
- /**
- * @hide
- * Notify the user of a RemoteControlClient that it should update its metadata with the
- * new value for the given key.
- * @param generationId the RemoteControlClient generation counter for which this request is
- * issued. Requests for an older generation than current one will be ignored.
- * @param key the metadata key for which a new value exists
- * @param value the new metadata value
- */
- public void updateRemoteControlClientMetadata(int generationId, int key,
- Rating value) {
- IAudioService service = getService();
- try {
- service.updateRemoteControlClientMetadata(generationId, key, value);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in updateRemoteControlClientMetadata("+ generationId + ", "
- + key +", " + value + ")", e);
- }
- }
-
- /**
* @hide
* Reload audio settings. This method is called by Settings backup
* agent when audio settings are restored and causes the AudioService
@@ -2873,12 +2812,9 @@
* @hide
*/
public int getRemoteStreamVolume() {
- try {
- return getService().getRemoteStreamVolume();
- } catch (RemoteException e) {
- Log.w(TAG, "Error getting remote stream volume", e);
- return 0;
- }
+ // TODO STOPSHIP switch callers to use media sessions instead
+ Log.e(TAG, "Need to implement new Remote Volume!");
+ return 0;
}
/**
@@ -2886,12 +2822,9 @@
* @hide
*/
public int getRemoteStreamMaxVolume() {
- try {
- return getService().getRemoteStreamMaxVolume();
- } catch (RemoteException e) {
- Log.w(TAG, "Error getting remote stream max volume", e);
- return 0;
- }
+ // TODO STOPSHIP switch callers to use media sessions instead
+ Log.e(TAG, "Need to implement new Remote Volume!");
+ return 0;
}
/**
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 48479a4..0c224a6 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -2056,7 +2056,19 @@
}
/** @see AudioManager#startBluetoothSco() */
- public void startBluetoothSco(IBinder cb, int targetSdkVersion){
+ public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ int scoAudioMode =
+ (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
+ SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+ startBluetoothScoInt(cb, scoAudioMode);
+ }
+
+ /** @see AudioManager#startBluetoothScoVirtualCall() */
+ public void startBluetoothScoVirtualCall(IBinder cb) {
+ startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+ }
+
+ void startBluetoothScoInt(IBinder cb, int scoAudioMode){
if (!checkAudioSettingsPermission("startBluetoothSco()") ||
!mSystemReady) {
return;
@@ -2068,7 +2080,7 @@
// The caller identity must be cleared after getScoClient() because it is needed if a new
// client is created.
final long ident = Binder.clearCallingIdentity();
- client.incCount(targetSdkVersion);
+ client.incCount(scoAudioMode);
Binder.restoreCallingIdentity(ident);
}
@@ -2114,9 +2126,9 @@
}
}
- public void incCount(int targetSdkVersion) {
+ public void incCount(int scoAudioMode) {
synchronized(mScoClients) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, targetSdkVersion);
+ requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
if (mStartcount == 0) {
try {
mCb.linkToDeath(this, 0);
@@ -2186,7 +2198,7 @@
}
}
- private void requestScoState(int state, int targetSdkVersion) {
+ private void requestScoState(int state, int scoAudioMode) {
checkScoAudioState();
if (totalCount() == 0) {
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
@@ -2201,9 +2213,7 @@
(mScoAudioState == SCO_STATE_INACTIVE ||
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_INACTIVE) {
- mScoAudioMode =
- (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
- SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+ mScoAudioMode = scoAudioMode;
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
boolean status;
if (mScoAudioMode == SCO_MODE_RAW) {
@@ -4402,68 +4412,12 @@
mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
}
- public void registerMediaButtonEventReceiverForCalls(ComponentName c) {
- mMediaFocusControl.registerMediaButtonEventReceiverForCalls(c);
- }
-
- public void unregisterMediaButtonEventReceiverForCalls() {
- mMediaFocusControl.unregisterMediaButtonEventReceiverForCalls();
- }
-
- public void registerMediaButtonIntent(PendingIntent pi, ComponentName c, IBinder token) {
- mMediaFocusControl.registerMediaButtonIntent(pi, c, token);
- }
-
- public void unregisterMediaButtonIntent(PendingIntent pi) {
- mMediaFocusControl.unregisterMediaButtonIntent(pi);
- }
-
- public int registerRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient, String callingPckg) {
- return mMediaFocusControl.registerRemoteControlClient(mediaIntent, rcClient, callingPckg);
- }
-
- public void unregisterRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient) {
- mMediaFocusControl.unregisterRemoteControlClient(mediaIntent, rcClient);
- }
-
- public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
- mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs);
- }
-
- public void updateRemoteControlClientMetadata(int generationId, int key, Rating value) {
- mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value);
- }
-
- public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
- mMediaFocusControl.registerRemoteVolumeObserverForRcc(rccId, rvo);
- }
-
- @Override
- public int getRemoteStreamVolume() {
- return mMediaFocusControl.getRemoteStreamVolume();
- }
-
- @Override
- public int getRemoteStreamMaxVolume() {
- return mMediaFocusControl.getRemoteStreamMaxVolume();
- }
-
@Override
public void setRemoteStreamVolume(int index) {
enforceSelfOrSystemUI("set the remote stream volume");
mMediaFocusControl.setRemoteStreamVolume(index);
}
- public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
- mMediaFocusControl.setPlaybackStateForRcc(rccId, state, timeMs, speed);
- }
-
- public void setPlaybackInfoForRcc(int rccId, int what, int value) {
- mMediaFocusControl.setPlaybackInfoForRcc(rccId, what, value);
- }
-
//==========================================================================================
// Audio Focus
//==========================================================================================
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 169631e..4dcdd19 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -122,12 +122,6 @@
int getCurrentAudioFocus();
- void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c, IBinder token);
- oneway void unregisterMediaButtonIntent(in PendingIntent pi);
-
- oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
- oneway void unregisterMediaButtonEventReceiverForCalls();
-
/**
* Register an IRemoteControlDisplay.
* Success of registration is subject to a check on
@@ -180,43 +174,9 @@
*/
oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd,
boolean wantsSync);
- /**
- * Request the user of a RemoteControlClient to seek to the given playback position.
- * @param generationId the RemoteControlClient generation counter for which this request is
- * issued. Requests for an older generation than current one will be ignored.
- * @param timeMs the time in ms to seek to, must be positive.
- */
- void setRemoteControlClientPlaybackPosition(int generationId, long timeMs);
- /**
- * Notify the user of a RemoteControlClient that it should update its metadata with the
- * new value for the given key.
- * @param generationId the RemoteControlClient generation counter for which this request is
- * issued. Requests for an older generation than current one will be ignored.
- * @param key the metadata key for which a new value exists
- * @param value the new metadata value
- */
- void updateRemoteControlClientMetadata(int generationId, int key, in Rating value);
-
- /**
- * Do not use directly, use instead
- * {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)}
- */
- int registerRemoteControlClient(in PendingIntent mediaIntent,
- in IRemoteControlClient rcClient, in String callingPackageName);
- /**
- * Do not use directly, use instead
- * {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)}
- */
- oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
- in IRemoteControlClient rcClient);
-
- oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
- void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed);
- int getRemoteStreamMaxVolume();
- int getRemoteStreamVolume();
- oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo);
void startBluetoothSco(IBinder cb, int targetSdkVersion);
+ void startBluetoothScoVirtualCall(IBinder cb);
void stopBluetoothSco(IBinder cb);
void forceVolumeControlStream(int streamType, IBinder cb);
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 440653a..6559bc5 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -181,6 +181,27 @@
}
/**
+ * Thrown when an unrecoverable failure occurs during a MediaDrm operation.
+ * Extends java.lang.IllegalStateException with the addition of an error
+ * code that may be useful in diagnosing the failure.
+ */
+ public static final class MediaDrmStateException extends java.lang.IllegalStateException {
+ private final int mErrorCode;
+
+ public MediaDrmStateException(int errorCode, String detailMessage) {
+ super(detailMessage);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * Retrieve the associated error code
+ */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+
+ /**
* Register a callback to be invoked when an event occurs
*
* @param listener the callback that will be run
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 1c73c05..a4a7c4e 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -379,32 +379,11 @@
onReevaluateRemote();
break;
- case MSG_RCC_NEW_PLAYBACK_INFO:
- onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */,
- ((Integer)msg.obj).intValue() /* value */);
- break;
-
case MSG_RCC_NEW_VOLUME_OBS:
onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
(IRemoteVolumeObserver)msg.obj /* rvo */);
break;
- case MSG_RCC_NEW_PLAYBACK_STATE:
- onNewPlaybackStateForRcc(msg.arg1 /* rccId */,
- msg.arg2 /* state */,
- (PlayerRecord.RccPlaybackState)msg.obj /* newState */);
- break;
-
- case MSG_RCC_SEEK_REQUEST:
- onSetRemoteControlClientPlaybackPosition(
- msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */);
- break;
-
- case MSG_RCC_UPDATE_METADATA:
- onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/,
- (Rating) msg.obj /* value */);
- break;
-
case MSG_RCDISPLAY_INIT_INFO:
// msg.obj is guaranteed to be non null
onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
@@ -2003,217 +1982,6 @@
}
}
- protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
- // ignore position change requests if invalid generation ID
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if (mCurrentRcClientGen != generationId) {
- return;
- }
- }
- }
- // discard any unprocessed seek request in the message queue, and replace with latest
- sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */,
- 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */);
- }
-
- private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
- if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId +
- ", timeMs=" + timeMs + ")");
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) {
- // tell the current client to seek to the requested location
- try {
- mCurrentRcClient.seekTo(generationId, timeMs);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: "+e);
- mCurrentRcClient = null;
- }
- }
- }
- }
- }
-
- protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) {
- sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE,
- genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */);
- }
-
- private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
- if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
- ", what=" + key + ",rating=" + value + ")");
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
- try {
- switch (key) {
- case MediaMetadataEditor.RATING_KEY_BY_USER:
- mCurrentRcClient.updateMetadata(genId, key, value);
- break;
- default:
- Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
- + genId);
- break;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead", e);
- mCurrentRcClient = null;
- }
- }
- }
- }
- }
-
- protected void setPlaybackInfoForRcc(int rccId, int what, int value) {
- sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE,
- rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */);
- }
-
- // handler for MSG_RCC_NEW_PLAYBACK_INFO
- private void onNewPlaybackInfoForRcc(int rccId, int key, int value) {
- if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId +
- ", what=" + key + ",val=" + value + ")");
- synchronized(mPRStack) {
- // iterating from top of stack as playback information changes are more likely
- // on entries at the top of the remote control stack
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if (prse.getRccId() == rccId) {
- switch (key) {
- case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
- prse.mPlaybackType = value;
- postReevaluateRemote();
- break;
- case RemoteControlClient.PLAYBACKINFO_VOLUME:
- prse.mPlaybackVolume = value;
- synchronized (mMainRemote) {
- if (rccId == mMainRemote.mRccId) {
- mMainRemote.mVolume = value;
- mVolumeController.postHasNewRemotePlaybackInfo();
- }
- }
- break;
- case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
- prse.mPlaybackVolumeMax = value;
- synchronized (mMainRemote) {
- if (rccId == mMainRemote.mRccId) {
- mMainRemote.mVolumeMax = value;
- mVolumeController.postHasNewRemotePlaybackInfo();
- }
- }
- break;
- case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
- prse.mPlaybackVolumeHandling = value;
- synchronized (mMainRemote) {
- if (rccId == mMainRemote.mRccId) {
- mMainRemote.mVolumeHandling = value;
- mVolumeController.postHasNewRemotePlaybackInfo();
- }
- }
- break;
- case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
- prse.mPlaybackStream = value;
- break;
- default:
- Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
- break;
- }
- return;
- }
- }//for
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index mPRStack on onNewPlaybackInfoForRcc, lock error? ", e);
- }
- }
- }
-
- protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
- sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
- rccId /* arg1 */, state /* arg2 */,
- new PlayerRecord.RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
- }
-
- private void onNewPlaybackStateForRcc(int rccId, int state,
- PlayerRecord.RccPlaybackState newState) {
- if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
- + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
- synchronized(mPRStack) {
- if (mPRStack.empty()) {
- return;
- }
- PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
- PlayerRecord prse = null;
- int lastPlayingIndex = mPRStack.size();
- int inStackIndex = -1;
- try {
- // go through the stack from the top to figure out who's playing, and the position
- // of this RemoteControlClient (note that it may not be in the stack)
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- prse = mPRStack.elementAt(index);
- if (prse.getRccId() == rccId) {
- inStackIndex = index;
- prse.mPlaybackState = newState;
- }
- if (prse.isPlaybackActive()) {
- lastPlayingIndex = index;
- }
- }
-
- if (inStackIndex != -1) {
- // is in the stack
- prse = mPRStack.elementAt(inStackIndex);
- synchronized (mMainRemote) {
- if (rccId == mMainRemote.mRccId) {
- mMainRemoteIsActive = isPlaystateActive(state);
- postReevaluateRemote();
- }
- }
- if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
- // remove it from its old location in the stack
- mPRStack.removeElementAt(inStackIndex);
- if (prse.isPlaybackActive()) {
- // and put it at the top
- mPRStack.push(prse);
- } else {
- // and put it after the ones with active playback
- if (inStackIndex > lastPlayingIndex) {
- mPRStack.add(lastPlayingIndex, prse);
- } else {
- mPRStack.add(lastPlayingIndex - 1, prse);
- }
- }
- }
-
- if (oldTopPrse != mPRStack.lastElement()) {
- // the top of the stack changed:
- final ComponentName target =
- mPRStack.lastElement().getMediaButtonReceiver();
- if (target != null) {
- // post message to persist the default media button receiver
- mEventHandler.sendMessage( mEventHandler.obtainMessage(
- MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
- }
- // reevaluate the display
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification or bad index
- Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
- + " size=" + mPRStack.size()
- + "accessing PlayerRecord stack in onNewPlaybackStateForRcc", e);
- }
- }
- }
-
- protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
- sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE,
- rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */);
- }
-
// handler for MSG_RCC_NEW_VOLUME_OBS
private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
synchronized(mPRStack) {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index ddd5b72..3336694 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -29,7 +29,6 @@
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplayStatus;
import android.media.session.MediaSession;
-import android.media.session.RemoteVolumeProvider;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
@@ -2204,10 +2203,10 @@
return;
}
if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
- int volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_FIXED;
+ int volumeControl = VolumeProvider.VOLUME_CONTROL_FIXED;
switch (mVolumeHandling) {
case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
- volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+ volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
break;
case RemoteControlClient.PLAYBACK_VOLUME_FIXED:
default:
@@ -2226,7 +2225,7 @@
}
}
- class SessionVolumeProvider extends RemoteVolumeProvider {
+ class SessionVolumeProvider extends VolumeProvider {
public SessionVolumeProvider(int volumeControl, int maxVolume) {
super(volumeControl, maxVolume);
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 76c7299..be96398 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -361,18 +361,10 @@
if (timeMs < 0) {
throw new IllegalArgumentException("illegal negative time value");
}
- if (USE_SESSIONS) {
- synchronized (mInfoLock) {
- if (mCurrentSession != null) {
- mCurrentSession.getTransportControls().seekTo(timeMs);
- }
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ mCurrentSession.getTransportControls().seekTo(timeMs);
}
- } else {
- final int genId;
- synchronized (mGenLock) {
- genId = mClientGenerationIdCurrent;
- }
- mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
}
return true;
}
@@ -534,34 +526,15 @@
if (!mMetadataChanged) {
return;
}
- if (USE_SESSIONS) {
- synchronized (mInfoLock) {
- if (mCurrentSession != null) {
- if (mEditorMetadata.containsKey(
- String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
- Rating rating = (Rating) getObject(
- MediaMetadataEditor.RATING_KEY_BY_USER, null);
- if (rating != null) {
- mCurrentSession.getTransportControls().setRating(rating);
- }
- }
- }
- }
- } else {
- final int genId;
- synchronized(mGenLock) {
- genId = mClientGenerationIdCurrent;
- }
- synchronized(mInfoLock) {
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
if (mEditorMetadata.containsKey(
String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
Rating rating = (Rating) getObject(
MediaMetadataEditor.RATING_KEY_BY_USER, null);
- mAudioManager.updateRemoteControlClientMetadata(genId,
- MediaMetadataEditor.RATING_KEY_BY_USER,
- rating);
- } else {
- Log.e(TAG, "no metadata to apply");
+ if (rating != null) {
+ mCurrentSession.getTransportControls().setRating(rating);
+ }
}
}
}
diff --git a/media/java/android/media/session/RemoteVolumeProvider.java b/media/java/android/media/VolumeProvider.java
similarity index 92%
rename from media/java/android/media/session/RemoteVolumeProvider.java
rename to media/java/android/media/VolumeProvider.java
index 606b1d7..7d93b40 100644
--- a/media/java/android/media/session/RemoteVolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.media.session;
+package android.media;
+import android.media.session.MediaSession;
import android.os.RemoteException;
import android.util.Log;
@@ -24,8 +25,8 @@
* You can set a volume provider on a session by calling
* {@link MediaSession#setPlaybackToRemote}.
*/
-public abstract class RemoteVolumeProvider {
- private static final String TAG = "RemoteVolumeProvider";
+public abstract class VolumeProvider {
+ private static final String TAG = "VolumeProvider";
/**
* The volume is fixed and can not be modified. Requests to change volume
@@ -60,7 +61,7 @@
* this provider.
* @param maxVolume The maximum allowed volume.
*/
- public RemoteVolumeProvider(int volumeControl, int maxVolume) {
+ public VolumeProvider(int volumeControl, int maxVolume) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
}
@@ -117,7 +118,7 @@
/**
* @hide
*/
- void setSession(MediaSession session) {
+ public void setSession(MediaSession session) {
mSession = session;
}
}
\ No newline at end of file
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 1cfc5bc..5bc0de4 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -15,6 +15,7 @@
package android.media.session;
+import android.content.ComponentName;
import android.media.MediaMetadata;
import android.media.session.ISessionController;
import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@
ISessionController getController();
void setFlags(int flags);
void setActive(boolean active);
+ void setMediaButtonReceiver(in ComponentName mbr);
void destroy();
// These commands are for setting up and communicating with routes
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index f0cd785..b4c11f6 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -20,6 +20,7 @@
import android.media.Rating;
import android.media.session.ISessionControllerCallback;
import android.media.session.MediaSessionInfo;
+import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -38,6 +39,9 @@
void showRoutePicker();
MediaSessionInfo getSessionInfo();
long getFlags();
+ ParcelableVolumeInfo getVolumeAttributes();
+ void adjustVolumeBy(int delta, int flags);
+ void setVolumeTo(int value, int flags);
// These commands are for the TransportController
void play();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 87a43e4..84dad25 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -18,6 +18,7 @@
import android.media.MediaMetadata;
import android.media.Rating;
+import android.media.VolumeProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -183,6 +184,23 @@
}
/**
+ * Get the current volume info for this session.
+ *
+ * @return The current volume info or null.
+ */
+ public VolumeInfo getVolumeInfo() {
+ try {
+ ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
+ return new VolumeInfo(result.volumeType, result.audioStream, result.controlType,
+ result.maxVolume, result.currentVolume);
+
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling getVolumeInfo.", e);
+ }
+ return null;
+ }
+
+ /**
* Adds a callback to receive updates from the Session. Updates will be
* posted on the caller's thread.
*
@@ -509,6 +527,85 @@
}
}
+ /**
+ * Holds information about the way volume is handled for this session.
+ */
+ public static final class VolumeInfo {
+ private final int mVolumeType;
+ private final int mAudioStream;
+ private final int mVolumeControl;
+ private final int mMaxVolume;
+ private final int mCurrentVolume;
+
+ /**
+ * @hide
+ */
+ public VolumeInfo(int type, int stream, int control, int max, int current) {
+ mVolumeType = type;
+ mAudioStream = stream;
+ mVolumeControl = control;
+ mMaxVolume = max;
+ mCurrentVolume = current;
+ }
+
+ /**
+ * Get the type of volume handling, either local or remote. One of:
+ * <ul>
+ * <li>{@link MediaSession#VOLUME_TYPE_LOCAL}</li>
+ * <li>{@link MediaSession#VOLUME_TYPE_REMOTE}</li>
+ * </ul>
+ *
+ * @return The type of volume handling this session is using.
+ */
+ public int getVolumeType() {
+ return mVolumeType;
+ }
+
+ /**
+ * Get the stream this is currently controlling volume on. When the volume
+ * type is {@link MediaSession#VOLUME_TYPE_REMOTE} this value does not
+ * have meaning and should be ignored.
+ *
+ * @return The stream this session is playing on.
+ */
+ public int getAudioStream() {
+ return mAudioStream;
+ }
+
+ /**
+ * Get the type of volume control that can be used. One of:
+ * <ul>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+ * </ul>
+ *
+ * @return The type of volume control that may be used with this
+ * session.
+ */
+ public int getVolumeControl() {
+ return mVolumeControl;
+ }
+
+ /**
+ * Get the maximum volume that may be set for this session.
+ *
+ * @return The maximum allowed volume where this session is playing.
+ */
+ public int getMaxVolume() {
+ return mMaxVolume;
+ }
+
+ /**
+ * Get the current volume for this session.
+ *
+ * @return The current volume where this session is playing.
+ */
+ public int getCurrentVolume() {
+ return mCurrentVolume;
+ }
+ }
+
private final static class CallbackStub extends ISessionControllerCallback.Stub {
private final WeakReference<MediaController> mController;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4ba1351..406b1c3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,10 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
+import android.media.VolumeProvider;
import android.media.session.ISessionController;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -125,18 +127,12 @@
public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
/**
- * The session uses local playback. Used for configuring volume handling
- * with the system.
- *
- * @hide
+ * The session uses local playback.
*/
public static final int VOLUME_TYPE_LOCAL = 1;
/**
- * The session uses remote playback. Used for configuring volume handling
- * with the system.
- *
- * @hide
+ * The session uses remote playback.
*/
public static final int VOLUME_TYPE_REMOTE = 2;
@@ -155,7 +151,7 @@
= new ArrayMap<String, RouteInterface.EventListener>();
private Route mRoute;
- private RemoteVolumeProvider mVolumeProvider;
+ private VolumeProvider mVolumeProvider;
private boolean mActive = false;;
@@ -232,6 +228,21 @@
}
/**
+ * Set a media button event receiver component to use to restart playback
+ * after an app has been stopped.
+ *
+ * @param mbr The receiver component to send the media button event to.
+ * @hide
+ */
+ public void setMediaButtonReceiver(ComponentName mbr) {
+ try {
+ mBinder.setMediaButtonReceiver(mbr);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+ }
+ }
+
+ /**
* Set any flags for the session.
*
* @param flags The flags to set for this session.
@@ -272,7 +283,7 @@
* @param volumeProvider The provider that will handle volume changes. May
* not be null.
*/
- public void setPlaybackToRemote(RemoteVolumeProvider volumeProvider) {
+ public void setPlaybackToRemote(VolumeProvider volumeProvider) {
if (volumeProvider == null) {
throw new IllegalArgumentException("volumeProvider may not be null!");
}
@@ -524,12 +535,12 @@
}
/**
- * Notify the system that the remove volume changed.
+ * Notify the system that the remote volume changed.
*
* @param provider The provider that is handling volume changes.
* @hide
*/
- void notifyRemoteVolumeChanged(RemoteVolumeProvider provider) {
+ public void notifyRemoteVolumeChanged(VolumeProvider provider) {
if (provider == null || provider != mVolumeProvider) {
Log.w(TAG, "Received update from stale volume provider");
return;
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 801844f..838b857 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.MediaMetadata;
@@ -214,7 +215,7 @@
}
}
- public void addMediaButtonListener(PendingIntent pi,
+ public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
Context context) {
if (pi == null) {
Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
@@ -238,6 +239,7 @@
holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
+ holder.mSession.setMediaButtonReceiver(mbrComponent);
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener added " + pi);
}
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/media/java/android/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..c4250f0
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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.media.session;
+
+parcelable ParcelableVolumeInfo;
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.java b/media/java/android/media/session/ParcelableVolumeInfo.java
new file mode 100644
index 0000000..166ccd3
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.java
@@ -0,0 +1,78 @@
+/* Copyright 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.media.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class for passing information about the audio configuration of a
+ * session. The public implementation is {@link MediaController.VolumeInfo}.
+ *
+ * @hide
+ */
+public class ParcelableVolumeInfo implements Parcelable {
+ public int volumeType;
+ public int audioStream;
+ public int controlType;
+ public int maxVolume;
+ public int currentVolume;
+
+ public ParcelableVolumeInfo(int volumeType, int audioStream, int controlType, int maxVolume,
+ int currentVolume) {
+ this.volumeType = volumeType;
+ this.audioStream = audioStream;
+ this.controlType = controlType;
+ this.maxVolume = maxVolume;
+ this.currentVolume = currentVolume;
+ }
+
+ public ParcelableVolumeInfo(Parcel from) {
+ volumeType = from.readInt();
+ audioStream = from.readInt();
+ controlType = from.readInt();
+ maxVolume = from.readInt();
+ currentVolume = from.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(volumeType);
+ dest.writeInt(audioStream);
+ dest.writeInt(controlType);
+ dest.writeInt(maxVolume);
+ dest.writeInt(currentVolume);
+ }
+
+
+ public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
+ = new Parcelable.Creator<ParcelableVolumeInfo>() {
+ @Override
+ public ParcelableVolumeInfo createFromParcel(Parcel in) {
+ return new ParcelableVolumeInfo(in);
+ }
+
+ @Override
+ public ParcelableVolumeInfo[] newArray(int size) {
+ return new ParcelableVolumeInfo[size];
+ }
+ };
+}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 4fbd2a4..5f27b16 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -110,6 +110,11 @@
jfieldID certificateData;
};
+struct StateExceptionFields {
+ jmethodID init;
+ jclass classId;
+};
+
struct fields_t {
jfieldID context;
jmethodID post_event;
@@ -121,6 +126,7 @@
IteratorFields iterator;
EntryFields entry;
CertificateFields certificate;
+ StateExceptionFields stateException;
jclass certificateClassId;
jclass hashmapClassId;
jclass arraylistClassId;
@@ -212,6 +218,14 @@
}
}
+static void throwStateException(JNIEnv *env, const char *msg, status_t err) {
+ ALOGE("Illegal state exception: %s (%d)", msg, err);
+
+ jobject exception = env->NewObject(gFields.stateException.classId,
+ gFields.stateException.init, static_cast<int>(err),
+ env->NewStringUTF(msg));
+ env->Throw(static_cast<jthrowable>(exception));
+}
static bool throwExceptionAsNecessary(
JNIEnv *env, status_t err, const char *msg = NULL) {
@@ -275,8 +289,7 @@
msg = errbuf.string();
}
}
- ALOGE("Illegal state exception: %s", msg);
- jniThrowException(env, "java/lang/IllegalStateException", msg);
+ throwStateException(env, msg, err);
return true;
}
return false;
@@ -608,6 +621,10 @@
FIND_CLASS(clazz, "java/util/ArrayList");
gFields.arraylistClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+ GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
}
static void android_media_MediaDrm_native_setup(
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 7dba21d..b6bb578 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -205,6 +205,37 @@
}
}
+ @SmallTest
+ public void testConnectLegacy() throws Exception {
+ final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+ for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+ ICamera cameraUser = null;
+ ICameraClient dummyCallbacks = new DummyCameraClient();
+
+ String clientPackageName = getContext().getPackageName();
+
+ BinderHolder holder = new BinderHolder();
+
+ try {
+ CameraBinderDecorator.newInstance(mUtils.getCameraService())
+ .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
+ clientPackageName,
+ CameraBinderTestUtils.USE_CALLING_UID, holder);
+ cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+ assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
+
+ Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
+ } catch (RuntimeException e) {
+ // Not all camera device support openLegacy.
+ Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+ } finally {
+ if (cameraUser != null) {
+ cameraUser.disconnect();
+ }
+ }
+ }
+ }
+
static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
/*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
new file mode 100644
index 0000000..14bbe44
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest.unit;
+
+import android.hardware.Camera;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * <pre>
+ * adb shell am instrument \
+ * -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
+ * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ * </pre>
+ */
+public class CameraOpenTest extends junit.framework.TestCase {
+ private static String TAG = "CameraOpenTest";
+
+ private Camera mCamera;
+
+ /**
+ * Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
+ */
+ @SmallTest
+ public void testOpenLegacy() {
+ int nCameras = Camera.getNumberOfCameras();
+ for (int id = 0; id < nCameras; id++) {
+ try {
+ mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
+ } catch (RuntimeException e) {
+ Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+ } finally {
+ if (mCamera != null) {
+ mCamera.release();
+ }
+ }
+ }
+ }
+}
diff --git a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
index b769a0c..acb7767 100644
--- a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
+++ b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
@@ -125,6 +125,11 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/audiomanagertwo" />
+ <CheckBox
+ android:id="@+id/useVirtualCallCheckBox"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/useVirtualCallCheckText" />
</LinearLayout>
diff --git a/media/tests/ScoAudioTest/res/values/strings.xml b/media/tests/ScoAudioTest/res/values/strings.xml
index c3ff6d5..b0284e2 100644
--- a/media/tests/ScoAudioTest/res/values/strings.xml
+++ b/media/tests/ScoAudioTest/res/values/strings.xml
@@ -10,5 +10,5 @@
<string name="tts_speak">Speak TTS</string>
<string name="tts_to_file">TTS to file</string>
<string name="audiomanagertwo">Use different AudioManager for starting SCO</string>
-
+ <string name="useVirtualCallCheckText">Use Virtual Call</string>
</resources>
diff --git a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
index 0304640..7e21876 100644
--- a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
+++ b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
@@ -207,16 +207,25 @@
if (mForceScoOn != isChecked) {
mForceScoOn = isChecked;
AudioManager mngr = mAudioManager;
+ boolean useVirtualCall = false;
CheckBox box = (CheckBox) findViewById(R.id.useSecondAudioManager);
if (box.isChecked()) {
Log.i(TAG, "Using 2nd audio manager");
mngr = mAudioManager2;
}
+ box = (CheckBox) findViewById(R.id.useVirtualCallCheckBox);
+ useVirtualCall = box.isChecked();
if (mForceScoOn) {
- Log.e(TAG, "startBluetoothSco() IN");
- mngr.startBluetoothSco();
- Log.e(TAG, "startBluetoothSco() OUT");
+ if (useVirtualCall) {
+ Log.e(TAG, "startBluetoothScoVirtualCall() IN");
+ mngr.startBluetoothScoVirtualCall();
+ Log.e(TAG, "startBluetoothScoVirtualCall() OUT");
+ } else {
+ Log.e(TAG, "startBluetoothSco() IN");
+ mngr.startBluetoothSco();
+ Log.e(TAG, "startBluetoothSco() OUT");
+ }
} else {
Log.e(TAG, "stopBluetoothSco() IN");
mngr.stopBluetoothSco();
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
index a4d292a..b8b6c04 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
@@ -169,15 +169,10 @@
return true;
}
-void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref,
+void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkColorType pref,
int width, int height) {
- bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType);
-}
-
-SkBitmap::Config OmxJpegImageDecoder::getColorSpaceConfig(
- SkBitmap::Config pref) {
-
- // Set the color space to ARGB_8888 for now
+ // Set the color space to ARGB_8888 for now (ignoring pref)
// because of limitation in hardware support.
- return SkBitmap::kARGB_8888_Config;
+ bm->setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType);
}
+
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
index e431e72..e487245 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
@@ -49,9 +49,7 @@
sp<MediaSource> getDecoder(OMXClient* client, const sp<MediaSource>& source);
bool decodeSource(sp<MediaSource> decoder, const sp<MediaSource>& source,
SkBitmap* bm);
- void configBitmapSize(SkBitmap* bm, SkBitmap::Config pref, int width,
- int height);
- SkBitmap::Config getColorSpaceConfig(SkBitmap::Config pref);
+ void configBitmapSize(SkBitmap* bm, SkColorType, int width, int height);
OMXClient mClient;
};
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index d2bf30c..ab18271 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -71,6 +71,7 @@
private AppWidgetHost mAppWidgetHost;
private AppWidgetManager mAppWidgetManager;
private KeyguardWidgetPager mAppWidgetContainer;
+ // TODO remove transport control references, these don't exist anymore
private KeyguardTransportControlView mTransportControl;
private int mAppWidgetToShow;
@@ -235,36 +236,6 @@
mKeyguardMultiUserSelectorView.finalizeActiveUserView(true);
}
}
- @Override
- public void onMusicClientIdChanged(
- int clientGeneration, boolean clearing, android.app.PendingIntent intent) {
- // Set transport state to invisible until we know music is playing (below)
- if (DEBUGXPORT && (mClientGeneration != clientGeneration || clearing)) {
- Log.v(TAG, (clearing ? "hide" : "show") + " transport, gen:" + clientGeneration);
- }
- mClientGeneration = clientGeneration;
- final int newState = (clearing ? TRANSPORT_GONE
- : (mTransportState == TRANSPORT_VISIBLE ?
- TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE));
- if (newState != mTransportState) {
- mTransportState = newState;
- if (DEBUGXPORT) Log.v(TAG, "update widget: transport state changed");
- KeyguardHostView.this.post(mSwitchPageRunnable);
- }
- }
- @Override
- public void onMusicPlaybackStateChanged(int playbackState, long eventTime) {
- if (DEBUGXPORT) Log.v(TAG, "music state changed: " + playbackState);
- if (mTransportState != TRANSPORT_GONE) {
- final int newState = (isMusicPlaying(playbackState) ?
- TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE);
- if (newState != mTransportState) {
- mTransportState = newState;
- if (DEBUGXPORT) Log.v(TAG, "update widget: play state changed");
- KeyguardHostView.this.post(mSwitchPageRunnable);
- }
- }
- }
};
private static final boolean isMusicPlaying(int playbackState) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index 7918755..38316ff 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -166,6 +166,11 @@
return info;
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
// This is an optimization to ensure we only recompute the patterns when the inputs change.
private static final class Patterns {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 668e1ef..bf34705 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -91,8 +91,6 @@
private static final int MSG_KEYGUARD_VISIBILITY_CHANGED = 312;
protected static final int MSG_BOOT_COMPLETED = 313;
private static final int MSG_USER_SWITCH_COMPLETE = 314;
- private static final int MSG_SET_CURRENT_CLIENT_ID = 315;
- protected static final int MSG_SET_PLAYBACK_STATE = 316;
protected static final int MSG_USER_INFO_CHANGED = 317;
protected static final int MSG_REPORT_EMERGENCY_CALL_ACTION = 318;
private static final int MSG_SCREEN_TURNED_ON = 319;
@@ -184,12 +182,6 @@
case MSG_BOOT_COMPLETED:
handleBootCompleted();
break;
- case MSG_SET_CURRENT_CLIENT_ID:
- handleSetGenerationId(msg.arg1, msg.arg2 != 0, (PendingIntent) msg.obj);
- break;
- case MSG_SET_PLAYBACK_STATE:
- handleSetPlaybackState(msg.arg1, msg.arg2, (Long) msg.obj);
- break;
case MSG_USER_INFO_CHANGED:
handleUserInfoChanged(msg.arg1);
break;
@@ -206,8 +198,6 @@
}
};
- private AudioManager mAudioManager;
-
private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
@Override
@@ -257,49 +247,6 @@
private DisplayClientState mDisplayClientState = new DisplayClientState();
- /**
- * This currently implements the bare minimum required to enable showing and hiding
- * KeyguardTransportControl. There's a lot of client state to maintain which is why
- * KeyguardTransportControl maintains an independent connection while it's showing.
- */
- private final IRemoteControlDisplay.Stub mRemoteControlDisplay =
- new IRemoteControlDisplay.Stub() {
-
- public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
- long currentPosMs, float speed) {
- Message msg = mHandler.obtainMessage(MSG_SET_PLAYBACK_STATE,
- generationId, state, stateChangeTimeMs);
- mHandler.sendMessage(msg);
- }
-
- public void setMetadata(int generationId, Bundle metadata) {
-
- }
-
- public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
-
- }
-
- public void setArtwork(int generationId, Bitmap bitmap) {
-
- }
-
- public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
-
- }
-
- public void setEnabled(boolean enabled) {
- // no-op: this RemoteControlDisplay is not subject to being disabled.
- }
-
- public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
- boolean clearing) throws RemoteException {
- Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,
- clientGeneration, (clearing ? 1 : 0), mediaIntent);
- mHandler.sendMessage(msg);
- }
- };
-
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
@@ -501,38 +448,6 @@
}
}
- protected void handleSetGenerationId(int clientGeneration, boolean clearing, PendingIntent p) {
- mDisplayClientState.clientGeneration = clientGeneration;
- mDisplayClientState.clearing = clearing;
- mDisplayClientState.intent = p;
- if (DEBUG)
- Log.v(TAG, "handleSetGenerationId(g=" + clientGeneration + ", clear=" + clearing + ")");
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onMusicClientIdChanged(clientGeneration, clearing, p);
- }
- }
- }
-
- protected void handleSetPlaybackState(int generationId, int playbackState, long eventTime) {
- if (DEBUG)
- Log.v(TAG, "handleSetPlaybackState(gen=" + generationId
- + ", state=" + playbackState + ", t=" + eventTime + ")");
- mDisplayClientState.playbackState = playbackState;
- mDisplayClientState.playbackEventTime = eventTime;
- if (generationId == mDisplayClientState.clientGeneration) {
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onMusicPlaybackStateChanged(playbackState, eventTime);
- }
- }
- } else {
- Log.w(TAG, "Ignoring generation id " + generationId + " because it's not current");
- }
- }
-
private void handleUserInfoChanged(int userId) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -694,8 +609,6 @@
protected void handleBootCompleted() {
if (mBootCompleted) return;
mBootCompleted = true;
- mAudioManager = new AudioManager(mContext);
- mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1013,12 +926,6 @@
callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
callback.onClockVisibilityChanged();
callback.onSimStateChanged(mSimState);
- callback.onMusicClientIdChanged(
- mDisplayClientState.clientGeneration,
- mDisplayClientState.clearing,
- mDisplayClientState.intent);
- callback.onMusicPlaybackStateChanged(mDisplayClientState.playbackState,
- mDisplayClientState.playbackEventTime);
}
public void sendKeyguardVisibilityChanged(boolean showing) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index bcdf18f..01600d2 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -144,18 +144,6 @@
public void onBootCompleted() { }
/**
- * Called when audio client attaches or detaches from AudioManager.
- */
- public void onMusicClientIdChanged(int clientGeneration, boolean clearing, PendingIntent intent) { }
-
- /**
- * Called when the audio playback state changes.
- * @param playbackState
- * @param eventTime
- */
- public void onMusicPlaybackStateChanged(int playbackState, long eventTime) { }
-
- /**
* Called when the emergency call button is pressed.
*/
void onEmergencyCallAction() { }
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
index a7e8514..4a4c1c1 100644
--- a/packages/SystemUI/res/drawable/ic_account_circle.xml
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -22,7 +22,13 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
+ <group
+ android:scaleX="1.2"
+ android:scaleY="1.2"
+ android:pivotX="12.0"
+ android:pivotY="12.0">
<path
android:fill="#FFFFFFFF"
android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,5.0c1.7,0.0 3.0,1.3 3.0,3.0c0.0,1.7 -1.3,3.0 -3.0,3.0c-1.7,0.0 -3.0,-1.3 -3.0,-3.0C9.0,6.3 10.3,5.0 12.0,5.0zM12.0,19.2c-2.5,0.0 -4.7,-1.3 -6.0,-3.2c0.0,-2.0 4.0,-3.1 6.0,-3.1c2.0,0.0 6.0,1.1 6.0,3.1C16.7,17.9 14.5,19.2 12.0,19.2z"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/notification_scrim.xml b/packages/SystemUI/res/drawable/notification_scrim.xml
new file mode 100644
index 0000000..ff7e31f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_scrim.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#34000000" />
+ <corners android:radius="@*android:dimen/notification_material_rounded_rect_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
new file mode 100644
index 0000000..5648065
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_user_switcher"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:gravity="end"
+ android:visibility="gone"
+ android:paddingTop="4dp">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
new file mode 100644
index 0000000..691a80e
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:layout_marginEnd="8dp"
+ android:gravity="center_vertical"
+ android:clickable="true"
+ android:background="@drawable/ripple_drawable">
+ <TextView android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.UserName"
+ />
+ <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/picture"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null"
+ sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
+ sysui:activeFrameColor="@color/current_user_border_color" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index cde83bf..b54ba1a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -24,6 +24,7 @@
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@android:color/transparent"
>
<include
@@ -91,6 +92,14 @@
<include layout="@layout/status_bar_expanded_header" />
+ <ViewStub
+ android:id="@+id/keyguard_user_switcher"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
+ android:layout_gravity="end"
+ android:layout="@layout/keyguard_user_switcher" />
+
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 353368b..2e4c0ef 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -73,9 +73,9 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/status_bar_header_height"
android:layout_toStartOf="@id/multi_user_switch"
- android:layout_marginEnd="2dp"
+ android:layout_alignWithParentIfMissing="true"
android:layout_marginStart="16dp"
- />
+ android:paddingEnd="2dp" />
<TextView
android:id="@+id/header_charging_info"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index c442f79..f0f50e1 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -49,4 +49,10 @@
android:layout_width="120dp"
android:layout_height="wrap_content"
/>
+
+ <com.android.systemui.statusbar.NotificationScrimView
+ android:id="@+id/scrim_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
</com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 5fabd3e..7663d54 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -48,4 +48,9 @@
android:padding="2dp"
/>
+ <com.android.systemui.statusbar.NotificationScrimView
+ android:id="@+id/scrim_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
</com.android.systemui.statusbar.ExpandableNotificationRow>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 47581a9..9f4c364 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,7 @@
<!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
card. -->
<integer name="keyguard_max_notification_count">5</integer>
+
+ <!-- Set to true to enable the user switcher on the keyguard. -->
+ <bool name="config_keyguardUserSwitcher">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index c453618..8473d96 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -54,5 +54,10 @@
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
+ <declare-styleable name="UserAvatarView">
+ <attr name="frameWidth" format="dimension" />
+ <attr name="activeFrameColor" format="color" />
+ <attr name="frameColor" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3bd8689..4e38da6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -60,6 +60,9 @@
<color name="keyguard_affordance">#ffffffff</color>
+ <!-- The color of the circle around the primary user in the user switcher -->
+ <color name="current_user_border_color">@color/primary_color</color>
+
<!-- Our material color palette (deep teal) -->
<color name="primary_color">#ff7fcac3</color>
<color name="background_color_1">#ff384248</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c64a182..48b327d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -154,5 +154,8 @@
Notification.tickerText across the status bar for what seems like an
eternity. -->
<bool name="enable_ticker">false</bool>
+
+ <!-- Set to true to enable the user switcher on the keyguard. -->
+ <bool name="config_keyguardUserSwitcher">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bbcc9c1..36c1994 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -320,6 +320,9 @@
device. -->
<dimen name="unlock_move_distance">75dp</dimen>
+ <!-- Distance after which the scrim starts fading in when dragging down the quick settings -->
+ <dimen name="notification_scrim_wait_distance">100dp</dimen>
+
<!-- Move distance for the unlock hint animation on the lockscreen -->
<dimen name="hint_move_distance">75dp</dimen>
@@ -332,4 +335,11 @@
<!-- end margin for multi user switch in expanded quick settings -->
<dimen name="multi_user_switch_expanded_margin">8dp</dimen>
+
+ <!-- end margin for system icons if multi user switch is hidden -->
+ <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
+
+ <!-- The thickness of the colored border around the current user. -->
+ <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c117eba..e5d5b03 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -100,6 +100,13 @@
<style name="TextAppearance.StatusBar.Expanded.Network.EmergencyOnly">
</style>
+ <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textStyle">normal</item>
+ <item name="android:textColor">#ffffff</item>
+ </style>
+ <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
+
<style name="TextAppearance" />
<style name="TextAppearance.QuickSettings" />
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 8d19f50..f6f78e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -119,6 +119,7 @@
private NotificationBackgroundView mBackgroundNormal;
private NotificationBackgroundView mBackgroundDimmed;
+ private NotificationScrimView mScrimView;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
private PorterDuffColorFilter mAppearAnimationFilter;
@@ -153,6 +154,7 @@
mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
updateBackground();
updateBackgroundResources();
+ mScrimView = (NotificationScrimView) findViewById(R.id.scrim_view);
}
private final Runnable mTapTimeoutRunnable = new Runnable() {
@@ -379,6 +381,7 @@
setPivotY(actualHeight / 2);
mBackgroundNormal.setActualHeight(actualHeight);
mBackgroundDimmed.setActualHeight(actualHeight);
+ mScrimView.setActualHeight(actualHeight);
}
@Override
@@ -386,6 +389,7 @@
super.setClipTopAmount(clipTopAmount);
mBackgroundNormal.setClipTopAmount(clipTopAmount);
mBackgroundDimmed.setClipTopAmount(clipTopAmount);
+ mScrimView.setClipTopAmount(clipTopAmount);
}
@Override
@@ -405,6 +409,11 @@
}
}
+ @Override
+ public void setScrimAmount(float scrimAmount) {
+ mScrimView.setAlpha(scrimAmount);
+ }
+
private void startAppearAnimation(boolean isAppearing,
float translationDirection, long delay, final Runnable onFinishedRunnable) {
if (mAppearAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index ac2537c..4d4a8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -223,6 +223,8 @@
public abstract void performAddAnimation(long delay);
+ public abstract void setScrimAmount(float scrimAmount);
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
new file mode 100644
index 0000000..440b2c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.keyguard.R;
+
+/**
+ * A view that can be used for both the dimmed and normal background of an notification.
+ */
+public class NotificationScrimView extends View {
+
+ private Drawable mBackground;
+ private int mClipTopAmount;
+ private int mActualHeight;
+
+ public NotificationScrimView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBackground = getResources().getDrawable(R.drawable.notification_scrim);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ draw(canvas, mBackground);
+ }
+
+ private void draw(Canvas canvas, Drawable drawable) {
+ if (drawable != null) {
+ drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+ drawable.draw(canvas);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mBackground;
+ }
+
+ public void setActualHeight(int actualHeight) {
+ mActualHeight = actualHeight;
+ invalidate();
+ }
+
+ public int getActualHeight() {
+ return mActualHeight;
+ }
+
+ public void setClipTopAmount(int clipTopAmount) {
+ mClipTopAmount = clipTopAmount;
+ invalidate();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+
+ // Prevents this view from creating a layer when alpha is animating.
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
index f80f0fd..650abaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -121,4 +121,9 @@
public void performAddAnimation(long delay) {
performVisibilityAnimation(true);
}
+
+ @Override
+ public void setScrimAmount(float scrimAmount) {
+ // We don't need to scrim the speedbumps
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 97aa993..63698e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -58,6 +58,7 @@
private ImageView mCameraImageView;
private ImageView mPhoneImageView;
private ImageView mLockIcon;
+ private View mIndicationText;
private ActivityStarter mActivityStarter;
private UnlockMethodCache mUnlockMethodCache;
@@ -87,6 +88,7 @@
mCameraImageView = (ImageView) findViewById(R.id.camera_button);
mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+ mIndicationText = findViewById(R.id.keyguard_indication_text);
watchForCameraPolicyChanges();
watchForAccessibilityChanges();
updateCameraVisibility();
@@ -231,6 +233,10 @@
return mLockIcon;
}
+ public View getIndicationView() {
+ return mIndicationText;
+ }
+
@Override
public void onMethodSecureChanged(boolean methodSecure) {
updateTrust();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 6a83a5e..319096d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.graphics.Path;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
@@ -31,6 +32,10 @@
private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f;
private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f;
+ private static final float CLOCK_SCALE_FADE_START = 0.95f;
+ private static final float CLOCK_SCALE_FADE_END = 0.75f;
+ private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f;
+
private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
@@ -61,6 +66,8 @@
sSlowDownInterpolator = new PathInterpolator(path);
}
+ private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
+
/**
* Refreshes the dimension values.
*/
@@ -87,18 +94,29 @@
}
public void run(Result result) {
- int y = getClockY() - mKeyguardStatusHeight/2;
+ int y = getClockY() - mKeyguardStatusHeight / 2;
float clockAdjustment = getClockYExpansionAdjustment();
float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier();
result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier);
int clockNotificationsPadding = getClockNotificationsPadding()
+ result.stackScrollerPaddingAdjustment;
int padding = y + clockNotificationsPadding;
- y += clockAdjustment;
result.clockY = y;
result.stackScrollerPadding = mKeyguardStatusHeight + padding;
- result.clockAlpha = getClockAlpha(result.stackScrollerPadding
- - (y + mKeyguardStatusHeight));
+ result.clockScale = getClockScale(result.stackScrollerPadding,
+ result.clockY,
+ y + getClockNotificationsPadding() + mKeyguardStatusHeight);
+ result.clockAlpha = getClockAlpha(result.clockScale);
+ }
+
+ private float getClockScale(int notificationPadding, int clockY, int startPadding) {
+ float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f;
+ float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier;
+ float distanceToScaleEnd = notificationPadding - scaleEnd;
+ float progress = distanceToScaleEnd / (startPadding - scaleEnd);
+ progress = Math.max(0.0f, Math.min(progress, 1.0f));
+ progress = mAccelerateInterpolator.getInterpolation(progress);
+ return progress;
}
private int getClockNotificationsPadding() {
@@ -144,11 +162,12 @@
+ t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX;
}
- private float getClockAlpha(int clockNotificationPadding) {
- float t = getNotificationAmountT();
- t = (float) Math.pow(t, 0.3f);
- float multiplier = 1 + 2 * (1 - t);
- float alpha = 1 + (float) clockNotificationPadding * multiplier / mKeyguardStatusHeight * 3;
+ private float getClockAlpha(float scale) {
+ float fadeEnd = getNotificationAmountT() == 0.0f
+ ? CLOCK_SCALE_FADE_END_NO_NOTIFS
+ : CLOCK_SCALE_FADE_END;
+ float alpha = (scale - fadeEnd)
+ / (CLOCK_SCALE_FADE_START - fadeEnd);
return Math.max(0, Math.min(1, alpha));
}
@@ -168,6 +187,11 @@
public int clockY;
/**
+ * The scale of the Clock
+ */
+ public float clockScale;
+
+ /**
* The alpha value of the clock.
*/
public float clockAlpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index dde95bf..a6ce5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
@@ -48,6 +49,11 @@
View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
KeyguardPageSwipeHelper.Callback {
+ // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+ // changed.
+ private static final int CAP_HEIGHT = 1456;
+ private static final int FONT_HEIGHT = 2163;
+
private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
private KeyguardPageSwipeHelper mPageSwiper;
@@ -56,7 +62,7 @@
private View mQsPanel;
private View mKeyguardStatusView;
private ObservableScrollView mScrollView;
- private View mStackScrollerContainer;
+ private TextView mClockView;
private NotificationStackScrollLayout mNotificationStackScroller;
private int mNotificationTopPadding;
@@ -105,9 +111,11 @@
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsSwipedHorizontally;
private boolean mIsExpanding;
- private KeyguardBottomAreaView mKeyguardBottomArea;
+
private boolean mBlockTouches;
private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
+ private int mNotificationScrimWaitDistance;
+ private boolean mOnNotificationsOnDown;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -135,9 +143,9 @@
mHeader.getBackgroundView().setOnClickListener(this);
mHeader.setOverlayParent(this);
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
- mStackScrollerContainer = findViewById(R.id.notification_container_parent);
mQsContainer = findViewById(R.id.quick_settings_container);
mQsPanel = findViewById(R.id.quick_settings_panel);
+ mClockView = (TextView) findViewById(R.id.clock_view);
mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
mScrollView.setListener(this);
mNotificationStackScroller = (NotificationStackScrollLayout)
@@ -169,12 +177,19 @@
getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
mClockPositionAlgorithm.loadDimens(getResources());
+ mNotificationScrimWaitDistance =
+ getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ // Update Clock Pivot
+ mKeyguardStatusView.setPivotX(getWidth() / 2);
+ mKeyguardStatusView.setPivotY(
+ (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
+
// Calculate quick setting heights.
mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
@@ -197,7 +212,7 @@
* showing.
*/
private void positionClockAndNotifications() {
- boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+ boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
int bottom = mStackScrollerOverscrolling
@@ -215,17 +230,17 @@
getHeight(),
mKeyguardStatusView.getHeight());
mClockPositionAlgorithm.run(mClockPositionResult);
- if (animateClock || mClockAnimator != null) {
+ if (animate || mClockAnimator != null) {
startClockAnimation(mClockPositionResult.clockY);
} else {
mKeyguardStatusView.setY(mClockPositionResult.clockY);
}
- applyClockAlpha(mClockPositionResult.clockAlpha);
+ updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
}
mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
- requestScrollerTopPaddingUpdate(animateClock);
+ requestScrollerTopPaddingUpdate(animate);
}
private void startClockAnimation(int y) {
@@ -258,13 +273,10 @@
});
}
- private void applyClockAlpha(float alpha) {
- if (alpha != 1.0f) {
- mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
- } else {
- mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
- }
+ private void updateClock(float alpha, float scale) {
mKeyguardStatusView.setAlpha(alpha);
+ mKeyguardStatusView.setScaleX(scale);
+ mKeyguardStatusView.setScaleY(scale);
}
public void animateToFullShade() {
@@ -344,6 +356,7 @@
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
+ mOnNotificationsOnDown = isOnNotifications(x, y);
if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
@@ -391,6 +404,8 @@
if (mQsTracking) {
flingQsWithCurrentVelocity();
mQsTracking = false;
+ } else if (mQsFullyExpanded && mOnNotificationsOnDown) {
+ flingSettings(0 /* vel */, false /* expand */);
}
mIntercepting = false;
break;
@@ -398,6 +413,10 @@
return !mQsExpanded && super.onInterceptTouchEvent(event);
}
+ private boolean isOnNotifications(float x, float y) {
+ return mNotificationStackScroller.getChildAtPosition(x, y) != null;
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -577,9 +596,17 @@
mHeader.setExpansion(height - mQsPeekHeight);
setQsTranslation(height);
requestScrollerTopPaddingUpdate(false /* animate */);
+ updateNotificationScrim(height);
mStatusBar.userActivity();
}
+ private void updateNotificationScrim(float height) {
+ int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
+ float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
+ progress = Math.max(0.0f, Math.min(progress, 1.0f));
+ mNotificationStackScroller.setScrimAlpha(progress);
+ }
+
private void setQsTranslation(float height) {
mQsContainer.setY(height - mQsContainer.getHeight());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 1f3098d..12aa004 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -62,6 +62,7 @@
protected int mTouchSlop;
protected boolean mHintAnimationRunning;
private boolean mOverExpandedBeforeFling;
+ private float mOriginalIndicationY;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -82,6 +83,7 @@
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mBounceInterpolator;
+ protected KeyguardBottomAreaView mKeyguardBottomArea;
protected void onExpandingFinished() {
mBar.onExpandingFinished();
@@ -652,6 +654,22 @@
});
animator.start();
mHeightAnimator = animator;
+ mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY();
+ mKeyguardBottomArea.getIndicationView().animate()
+ .y(mOriginalIndicationY - mHintDistance)
+ .setDuration(250)
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardBottomArea.getIndicationView().animate()
+ .y(mOriginalIndicationY)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start();
+ }
+ })
+ .start();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 1da7dab..7016c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -35,6 +35,7 @@
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -64,6 +65,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.provider.Settings.SettingNotFoundException;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -82,7 +84,6 @@
import android.view.ViewGroup.LayoutParams;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
-import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
@@ -123,6 +124,7 @@
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.DateView;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
@@ -211,6 +213,7 @@
ZenModeController mZenModeController;
CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
+ KeyguardUserSwitcher mKeyguardUserSwitcher;
int mNaturalBarHeight = -1;
int mIconSize = -1;
@@ -260,6 +263,10 @@
boolean mLeaveOpenOnKeyguardHide;
KeyguardIndicationController mKeyguardIndicationController;
+ private boolean mKeyguardFadingAway;
+ private long mKeyguardFadingAwayDelay;
+ private long mKeyguardFadingAwayDuration;
+
int mKeyguardMaxNotificationCount;
View mDateTimeView;
@@ -399,7 +406,9 @@
private boolean mSettingsCancelled;
private boolean mSettingsClosing;
private boolean mVisible;
+ private boolean mWaitingForKeyguardExit;
+ private Interpolator mLinearOutSlowIn;
private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
private Interpolator mAlphaIn = new PathInterpolator(0f, 0f, 0.8f, 1f);
@@ -713,6 +722,8 @@
final SignalClusterView signalCluster =
(SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
+ mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
+ (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader);
mNetworkController.addSignalCluster(signalCluster);
signalCluster.setNetworkController(mNetworkController);
@@ -893,6 +904,14 @@
}
};
+ private View.OnLongClickListener mLockToAppClickListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ toggleLockedApp();
+ return true;
+ }
+ };
+
private int mShowSearchHoldoff = 0;
private Runnable mShowSearchPanel = new Runnable() {
public void run() {
@@ -936,6 +955,8 @@
mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
+ mNavigationBarView.getRecentsButton().setLongClickable(true);
+ mNavigationBarView.getRecentsButton().setOnLongClickListener(mLockToAppClickListener);
mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
updateSearchPanel();
}
@@ -1453,7 +1474,7 @@
}
private int adjustDisableFlags(int state) {
- if (mExpandedVisible) {
+ if (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit) {
state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
state |= StatusBarManager.DISABLE_SYSTEM_INFO;
}
@@ -1501,19 +1522,9 @@
if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
mSystemIconArea.animate().cancel();
if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
- mSystemIconArea.animate()
- .alpha(0f)
- .withLayer()
- .setDuration(160)
- .setInterpolator(mAlphaIn)
- .setListener(mMakeIconsInvisible);
+ animateStatusBarHide(mSystemIconArea);
} else {
- mSystemIconArea.setVisibility(View.VISIBLE);
- mSystemIconArea.animate()
- .alpha(1f)
- .withLayer()
- .setInterpolator(mAlphaOut)
- .setDuration(320);
+ animateStatusBarShow(mSystemIconArea);
}
}
@@ -1546,25 +1557,48 @@
if (mTicking) {
haltTicker();
}
-
- mNotificationIcons.animate()
- .alpha(0f)
- .withLayer()
- .setDuration(160)
- .setInterpolator(mAlphaIn)
- .setListener(mMakeIconsInvisible)
- .start();
+ animateStatusBarHide(mNotificationIcons);
} else {
- mNotificationIcons.setVisibility(View.VISIBLE);
- mNotificationIcons.animate()
- .alpha(1f)
- .withLayer()
- .setInterpolator(mAlphaOut)
- .setDuration(320);
+ animateStatusBarShow(mNotificationIcons);
}
}
}
+ /**
+ * Animates {@code v}, a view that is part of the status bar, out.
+ */
+ private void animateStatusBarHide(View v) {
+ v.animate()
+ .alpha(0f)
+ .withLayer()
+ .setDuration(160)
+ .setInterpolator(mAlphaIn)
+ .setStartDelay(0)
+ .setListener(mMakeIconsInvisible)
+ .start();
+ }
+
+ /**
+ * Animates {@code v}, a view that is part of the status bar, in.
+ */
+ private void animateStatusBarShow(View v) {
+ v.setVisibility(View.VISIBLE);
+ v.animate()
+ .alpha(1f)
+ .withLayer()
+ .setInterpolator(mAlphaOut)
+ .setDuration(320)
+ .setStartDelay(0);
+
+ // Synchronize the motion with the Keyguard fading if necessary.
+ if (mKeyguardFadingAway) {
+ v.animate()
+ .setDuration(mKeyguardFadingAwayDuration)
+ .setInterpolator(mLinearOutSlowIn)
+ .setStartDelay(mKeyguardFadingAwayDelay);
+ }
+ }
+
@Override
protected BaseStatusBar.H createHandler() {
return new PhoneStatusBar.H();
@@ -1666,6 +1700,7 @@
mStatusBarWindowManager.setStatusBarExpanded(true);
visibilityChanged(true);
+ mWaitingForKeyguardExit = false;
disable(mDisabledUnmodified);
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
@@ -1859,8 +1894,8 @@
}
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
- disable(mDisabledUnmodified);
showBouncer();
+ disable(mDisabledUnmodified);
}
public boolean interceptTouchEvent(MotionEvent event) {
@@ -2574,6 +2609,8 @@
if (mQSPanel != null) mQSPanel.updateResources();
loadDimens();
+ mLinearOutSlowIn = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in);
}
protected void loadDimens() {
@@ -2901,6 +2938,27 @@
updateKeyguardState();
}
+ /**
+ * Notifies the status bar the Keyguard is fading away with the specified timings.
+ *
+ * @param delay the animation delay in miliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ public void setKeyguardFadingAway(long delay, long fadeoutDuration) {
+ mKeyguardFadingAway = true;
+ mKeyguardFadingAwayDelay = delay;
+ mKeyguardFadingAwayDuration = fadeoutDuration;
+ mWaitingForKeyguardExit = false;
+ disable(mDisabledUnmodified);
+ }
+
+ /**
+ * Notifies that the Keyguard fading away animation is done.
+ */
+ public void finishKeyguardFadingAway() {
+ mKeyguardFadingAway = false;
+ }
+
private void updatePublicMode() {
setLockscreenPublicMode(
(mStatusBarKeyguardViewManager.isShowing() ||
@@ -2913,9 +2971,11 @@
mKeyguardStatusView.setVisibility(View.VISIBLE);
mKeyguardIndicationController.setVisible(true);
mNotificationPanel.resetViews();
+ mKeyguardUserSwitcher.setKeyguard(true);
} else {
mKeyguardStatusView.setVisibility(View.GONE);
mKeyguardIndicationController.setVisible(false);
+ mKeyguardUserSwitcher.setKeyguard(false);
}
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.setVisibility(View.VISIBLE);
@@ -2975,6 +3035,7 @@
private void showBouncer() {
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+ mWaitingForKeyguardExit = true;
mStatusBarKeyguardViewManager.dismiss();
}
}
@@ -3125,6 +3186,12 @@
mSystemIconArea.addView(mSystemIcons, 0);
}
+ @Override
+ public void setBouncerShowing(boolean bouncerShowing) {
+ super.setBouncerShowing(bouncerShowing);
+ disable(mDisabledUnmodified);
+ }
+
public void onScreenTurnedOff() {
mStackScroller.setAnimationsEnabled(false);
}
@@ -3133,6 +3200,28 @@
mStackScroller.setAnimationsEnabled(true);
}
+ public void toggleLockedApp() {
+ Log.d(TAG, "Trying to toggle lock-to-app");
+ try {
+ IActivityManager activityManager = ActivityManagerNative.getDefault();
+ if (activityManager.isInLockTaskMode()) {
+ activityManager.stopLockTaskModeOnCurrent();
+ } else {
+ try {
+ boolean lockToAppEnabled = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.LOCK_TO_APP_ENABLED) != 0;
+ if (lockToAppEnabled) {
+ activityManager.startLockTaskModeOnCurrent();
+ }
+ } catch (SettingNotFoundException e) {
+ // No setting, not enabled.
+ }
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to toggle Lock-to-app", e);
+ }
+ }
+
private final Runnable mUserActivity = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 1712124..fc10a08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -63,6 +63,7 @@
private boolean mShowEmergencyCallsOnly;
private boolean mShowChargingInfo;
+ private boolean mKeyguardUserSwitcherShowing;
private int mCollapsedHeight;
private int mExpandedHeight;
@@ -72,6 +73,7 @@
private int mNormalWidth;
private int mPadding;
private int mMultiUserExpandedMargin;
+ private int mSystemIconsSwitcherHiddenExpandedMargin;
private ActivityStarter mActivityStarter;
private BrightnessController mBrightnessController;
@@ -125,7 +127,8 @@
mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding);
mMultiUserExpandedMargin =
getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
-
+ mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize(
+ R.dimen.system_icons_switcher_hidden_expanded_margin);
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -216,12 +219,15 @@
? VISIBLE : GONE);
mChargingInfo.setVisibility(mExpanded && !mOverscrolled && mShowChargingInfo
&& !mShowEmergencyCallsOnly ? VISIBLE : GONE);
+ mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing
+ ? VISIBLE : GONE);
}
private void updateSystemIconsLayoutParams() {
RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsContainer.getLayoutParams();
boolean systemIconsAboveClock = mExpanded && !mOverscrolled
&& mShowChargingInfo && !mShowEmergencyCallsOnly;
+ lp.setMarginEnd(0);
if (systemIconsAboveClock) {
lp.addRule(ALIGN_PARENT_START);
lp.removeRule(START_OF);
@@ -230,7 +236,11 @@
? mSettingsButton.getId()
: mMultiUserSwitch.getId());
lp.removeRule(ALIGN_PARENT_START);
+ if (mMultiUserSwitch.getVisibility() == GONE) {
+ lp.setMarginEnd(mSystemIconsSwitcherHiddenExpandedMargin);
+ }
}
+ mSystemIconsContainer.setLayoutParams(lp);
RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams();
if (systemIconsAboveClock) {
@@ -238,6 +248,7 @@
} else {
clockLp.addRule(BELOW, mEmergencyCallsOnly.getId());
}
+ mDateTime.setLayoutParams(clockLp);
}
private void updateBrightnessControllerState() {
@@ -385,4 +396,11 @@
public void setChargingInfo(CharSequence chargingInfo) {
mChargingInfo.setText(chargingInfo);
}
+
+ public void setKeyguardUserSwitcherShowing(boolean showing) {
+ // STOPSHIP: NOT CALLED PROPERLY WHEN GOING TO FULL SHADE AND RETURNING!?!
+ mKeyguardUserSwitcherShowing = showing;
+ updateVisibilities();
+ updateSystemIconsLayoutParams();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09e4d94..a36f3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -190,19 +190,23 @@
*/
public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
- mPhoneStatusBar.hideKeyguard();
- mStatusBarWindowManager.setKeyguardFadingAway(true);
- mStatusBarWindowManager.setKeyguardShowing(false);
+
long uptimeMillis = SystemClock.uptimeMillis();
long delay = startTime - uptimeMillis;
if (delay < 0) {
delay = 0;
}
+
+ mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+ mPhoneStatusBar.hideKeyguard();
+ mStatusBarWindowManager.setKeyguardFadingAway(true);
+ mStatusBarWindowManager.setKeyguardShowing(false);
mBouncer.animateHide(delay, fadeoutDuration);
mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
@Override
public void run() {
mStatusBarWindowManager.setKeyguardFadingAway(false);
+ mPhoneStatusBar.finishKeyguardFadingAway();
}
});
mViewMediatorCallback.keyguardGone();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
new file mode 100644
index 0000000..6f2642a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view that displays a user image cropped to a circle with a frame.
+ */
+public class UserAvatarView extends View {
+
+ private int mActiveFrameColor;
+ private int mFrameColor;
+ private float mFrameWidth;
+ private Bitmap mBitmap;
+ private Drawable mDrawable;
+
+ private final Paint mFramePaint = new Paint();
+ private final Paint mBitmapPaint = new Paint();
+ private final Matrix mDrawMatrix = new Matrix();
+
+ private float mScale = 1;
+
+ public UserAvatarView(Context context, AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
+ final int N = a.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ int attr = a.getIndex(i);
+ switch (attr) {
+ case R.styleable.UserAvatarView_frameWidth:
+ setFrameWidth(a.getDimension(attr, 0));
+ break;
+ case R.styleable.UserAvatarView_activeFrameColor:
+ setActiveFrameColor(a.getColor(attr, 0));
+ break;
+ case R.styleable.UserAvatarView_frameColor:
+ setFrameColor(a.getColor(attr, 0));
+ break;
+ }
+ }
+ a.recycle();
+
+ mFramePaint.setAntiAlias(true);
+ mFramePaint.setStyle(Paint.Style.STROKE);
+ mBitmapPaint.setAntiAlias(true);
+ }
+
+ public UserAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserAvatarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserAvatarView(Context context) {
+ this(context, null);
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ setDrawable(null);
+ mBitmap = bitmap;
+ if (mBitmap != null) {
+ mBitmapPaint.setShader(new BitmapShader(
+ bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+ } else {
+ mBitmapPaint.setShader(null);
+ }
+ configureBounds();
+ invalidate();
+ }
+
+ public void setFrameColor(int frameColor) {
+ mFrameColor = frameColor;
+ invalidate();
+ }
+
+ public void setActiveFrameColor(int activeFrameColor) {
+ mActiveFrameColor = activeFrameColor;
+ invalidate();
+ }
+
+ public void setFrameWidth(float frameWidth) {
+ mFrameWidth = frameWidth;
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ configureBounds();
+ }
+
+ public void configureBounds() {
+ int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+ int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+
+ int dwidth;
+ int dheight;
+ if (mBitmap != null) {
+ dwidth = mBitmap.getWidth();
+ dheight = mBitmap.getHeight();
+ } else if (mDrawable != null) {
+ dwidth = mDrawable.getIntrinsicWidth();
+ dheight = mDrawable.getIntrinsicHeight();
+ mDrawable.setBounds(0, 0, dwidth, dheight);
+ vwidth -= 2 * (mFrameWidth - 1);
+ vheight -= 2 * (mFrameWidth - 1);
+ } else {
+ return;
+ }
+
+ float scale;
+ float dx;
+ float dy;
+
+ scale = Math.min((float) vwidth / (float) dwidth,
+ (float) vheight / (float) dheight);
+
+ dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
+ dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
+
+ mDrawMatrix.setScale(scale, scale);
+ mDrawMatrix.postTranslate(dx, dy);
+ mScale = scale;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int frameColor = isActivated() ? mActiveFrameColor : mFrameColor;
+ float halfW = getWidth() / 2f;
+ float halfH = getHeight() / 2f;
+ float halfSW = Math.min(halfH, halfW);
+ if (mBitmap != null && mScale > 0) {
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.translate(mPaddingLeft, mPaddingTop);
+ canvas.concat(mDrawMatrix);
+ float halfBW = mBitmap.getWidth() / 2f;
+ float halfBH = mBitmap.getHeight() / 2f;
+ float halfBSW = Math.min(halfBH, halfBW);
+ canvas.drawCircle(halfBW, halfBH, halfBSW - mFrameWidth / mScale + 1, mBitmapPaint);
+ canvas.restoreToCount(saveCount);
+ } else if (mDrawable != null && mScale > 0) {
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.translate(mPaddingLeft, mPaddingTop);
+ canvas.translate(mFrameWidth - 1, mFrameWidth - 1);
+ canvas.concat(mDrawMatrix);
+ mDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ if (frameColor != 0) {
+ mFramePaint.setColor(frameColor);
+ mFramePaint.setStrokeWidth(mFrameWidth);
+ canvas.drawCircle(halfW, halfH, halfSW - mFrameWidth / 2f, mFramePaint);
+ }
+ }
+
+ public void setDrawable(Drawable d) {
+ if (mDrawable != null) {
+ mDrawable.setCallback(null);
+ unscheduleDrawable(mDrawable);
+ }
+ mDrawable = d;
+ if (d != null) {
+ d.setCallback(this);
+ if (d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ d.setLayoutDirection(getLayoutDirection());
+ configureBounds();
+ }
+ if (d != null) {
+ mBitmap = null;
+ }
+ configureBounds();
+ invalidate();
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable dr) {
+ if (dr == mDrawable) {
+ invalidate();
+ } else {
+ super.invalidateDrawable(dr);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 330b599..0134fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -63,6 +63,7 @@
// Just an old-fashioned ImageView
performLongClick();
}
+ setPressed(false);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
new file mode 100644
index 0000000..c90750c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.WindowManagerGlobal;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+public class KeyguardUserSwitcher implements View.OnClickListener {
+
+ private static final String TAG = "KeyguardUserSwitcher";
+
+ private final Context mContext;
+ private final ViewGroup mUserSwitcher;
+ private final UserManager mUserManager;
+ private final StatusBarHeaderView mHeader;
+
+ public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
+ StatusBarHeaderView header) {
+ mContext = context;
+ if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher)) {
+ mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mHeader = header;
+ refresh();
+ } else {
+ mUserSwitcher = null;
+ mUserManager = null;
+ mHeader = null;
+ }
+ }
+
+ public void setKeyguard(boolean keyguard) {
+ if (mUserSwitcher != null) {
+ // TODO: Cache showUserSwitcherOnKeyguard().
+ if (keyguard && showUserSwitcherOnKeyguard()) {
+ show();
+ refresh();
+ } else {
+ hide();
+ }
+ }
+ }
+
+ /**
+ * @return true if the user switcher should be shown on the lock screen.
+ * @see android.os.UserManager#isUserSwitcherEnabled()
+ */
+ private boolean showUserSwitcherOnKeyguard() {
+ // TODO: Set isEdu. The edu provisioning process can add settings to Settings.Global.
+ boolean isEdu = false;
+ if (isEdu) {
+ return true;
+ }
+ List<UserInfo> users = mUserManager.getUsers(true /* excludeDying */);
+ int N = users.size();
+ int switchableUsers = 0;
+ for (int i = 0; i < N; i++) {
+ if (users.get(i).supportsSwitchTo()) {
+ switchableUsers++;
+ }
+ }
+ return switchableUsers > 1;
+ }
+
+ public void show() {
+ if (mUserSwitcher != null) {
+ // TODO: animate
+ mUserSwitcher.setVisibility(View.VISIBLE);
+ mHeader.setKeyguardUserSwitcherShowing(true);
+ }
+ }
+
+ private void hide() {
+ if (mUserSwitcher != null) {
+ // TODO: animate
+ mUserSwitcher.setVisibility(View.GONE);
+ mHeader.setKeyguardUserSwitcherShowing(false);
+ }
+ }
+
+ private void refresh() {
+ if (mUserSwitcher != null) {
+ new AsyncTask<Void, Void, ArrayList<UserData>>() {
+ @Override
+ protected ArrayList<UserData> doInBackground(Void... params) {
+ return loadUsers();
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<UserData> userInfos) {
+ bind(userInfos);
+ }
+ }.execute((Void[]) null);
+ }
+ }
+
+ private void bind(ArrayList<UserData> userList) {
+ mUserSwitcher.removeAllViews();
+ int N = userList.size();
+ for (int i = 0; i < N; i++) {
+ mUserSwitcher.addView(inflateUser(userList.get(i)));
+ }
+ // TODO: add Guest
+ // TODO: add (+) button
+ }
+
+ private View inflateUser(UserData user) {
+ View v = LayoutInflater.from(mUserSwitcher.getContext()).inflate(
+ R.layout.keyguard_user_switcher_item, mUserSwitcher, false);
+ TextView name = (TextView) v.findViewById(R.id.name);
+ UserAvatarView picture = (UserAvatarView) v.findViewById(R.id.picture);
+ name.setText(user.userInfo.name);
+ picture.setActivated(user.isCurrent);
+ if (user.userInfo.isGuest()) {
+ picture.setDrawable(mContext.getResources().getDrawable(R.drawable.ic_account_circle));
+ } else {
+ picture.setBitmap(user.userIcon);
+ }
+ v.setOnClickListener(this);
+ v.setTag(user.userInfo);
+ // TODO: mark which user is current for accessibility.
+ return v;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switchUser(((UserInfo)v.getTag()).id);
+ }
+
+ // TODO: Factor out logic below and share with QS implementation.
+
+ private ArrayList<UserData> loadUsers() {
+ ArrayList<UserInfo> users = (ArrayList<UserInfo>) mUserManager
+ .getUsers(true /* excludeDying */);
+ int N = users.size();
+ ArrayList<UserData> result = new ArrayList<>(N);
+ int currentUser = -1;
+ try {
+ currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couln't get current user.", e);
+ }
+ for (int i = 0; i < N; i++) {
+ UserInfo user = users.get(i);
+ if (user.supportsSwitchTo()) {
+ boolean isCurrent = user.id == currentUser;
+ result.add(new UserData(user, mUserManager.getUserIcon(user.id), isCurrent));
+ }
+ }
+ return result;
+ }
+
+ private void switchUser(int userId) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ ActivityManagerNative.getDefault().switchUser(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't switch user.", e);
+ }
+ }
+
+ private static class UserData {
+ final UserInfo userInfo;
+ final Bitmap userIcon;
+ final boolean isCurrent;
+
+ UserData(UserInfo userInfo, Bitmap userIcon, boolean isCurrent) {
+ this.userInfo = userInfo;
+ this.userIcon = userIcon;
+ this.isCurrent = isCurrent;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 6d92b05..fcc951e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -32,6 +32,7 @@
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
private int mSpeedBumpIndex = -1;
+ private float mScrimAmount;
public int getScrollY() {
return mScrollY;
@@ -85,6 +86,14 @@
}
}
+ public void setScrimAmount(float scrimAmount) {
+ mScrimAmount = scrimAmount;
+ }
+
+ public float getScrimAmount() {
+ return mScrimAmount;
+ }
+
public float getOverScrollAmount(boolean top) {
return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index ccbaed3..f6e9aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -541,8 +541,9 @@
if (slidingChild.getVisibility() == GONE) {
continue;
}
- float top = slidingChild.getTranslationY();
- float bottom = top + slidingChild.getActualHeight();
+ float childTop = slidingChild.getTranslationY();
+ float top = childTop + slidingChild.getClipTopAmount();
+ float bottom = childTop + slidingChild.getActualHeight();
int left = slidingChild.getLeft();
int right = slidingChild.getRight();
@@ -1845,6 +1846,11 @@
return true;
}
+ public void setScrimAlpha(float progress) {
+ mAmbientState.setScrimAmount(progress);
+ requestChildrenUpdate();
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 602c22b..9a4b798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -154,6 +154,17 @@
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivated(ambientState, resultState, algorithmState);
updateClipping(resultState, algorithmState);
+ updateScrimAmount(resultState, algorithmState, ambientState.getScrimAmount());
+ }
+
+ private void updateScrimAmount(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState, float scrimAmount) {
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ childViewState.scrimAmount = scrimAmount;
+ }
}
private void updateClipping(StackScrollState resultState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 1ad4acc..02f2cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -148,6 +148,9 @@
// apply dimming
child.setDimmed(state.dimmed, false /* animate */);
+ // apply scrimming
+ child.setScrimAmount(state.scrimAmount);
+
float oldClipTopAmount = child.getClipTopAmount();
if (oldClipTopAmount != state.clipTopAmount) {
child.setClipTopAmount(state.clipTopAmount);
@@ -223,6 +226,12 @@
boolean dimmed;
/**
+ * A value between 0 and 1 indicating how much the view should be scrimmed.
+ * 1 means that the notifications will be darkened as much as possible.
+ */
+ float scrimAmount;
+
+ /**
* The amount which the view should be clipped from the top. This is calculated to
* perceive consistent shadows.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 225398a..0006dad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -176,6 +176,9 @@
// start dimmed animation
child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
+ // apply scrimming
+ child.setScrimAmount(viewState.scrimAmount);
+
if (wasAdded) {
child.performAddAnimation(delay);
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index bc2671011..637061d04 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -624,6 +624,7 @@
public void onWakeUp() {
synchronized (mLock) {
if (shouldEnableWakeGestureLp()) {
+ performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
mPowerManager.wakeUp(SystemClock.uptimeMillis());
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 816e022..69262c8 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1328,7 +1328,7 @@
+ " app=" + app);
if (app != null && app.thread != null) {
try {
- app.addPackage(r.appInfo.packageName, mAm.mProcessStats);
+ app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
} catch (RemoteException e) {
@@ -1883,7 +1883,8 @@
mPendingServices.remove(i);
i--;
- proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats);
+ proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
+ mAm.mProcessStats);
realStartServiceLocked(sr, proc, sr.createdFromFg);
didSomething = true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 34c1ecd..57b56b4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -36,6 +36,9 @@
import android.app.IAppTask;
import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetManager;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.os.BatteryStats;
import android.os.PersistableBundle;
@@ -171,8 +174,12 @@
import android.os.UpdateLock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.format.DateUtils;
import android.text.format.Time;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Log;
@@ -186,6 +193,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -570,6 +578,12 @@
long mLastFullPssTime = SystemClock.uptimeMillis();
/**
+ * If set, the next time we collect PSS data we should do a full collection
+ * with data from native processes and the kernel.
+ */
+ boolean mFullPssPending = false;
+
+ /**
* This is the process holding what we currently consider to be
* the "home" activity.
*/
@@ -1158,6 +1172,8 @@
CompatModeDialog mCompatModeDialog;
long mLastMemUsageReportTime = 0;
+ private LockToAppRequestDialog mLockToAppRequest;
+
/**
* Flag whether the current user is a "monkey", i.e. whether
* the UI is driven by a UI automation tool.
@@ -1801,8 +1817,49 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case COLLECT_PSS_BG_MSG: {
- int i=0, num=0;
long start = SystemClock.uptimeMillis();
+ MemInfoReader memInfo = null;
+ synchronized (ActivityManagerService.this) {
+ if (mFullPssPending) {
+ mFullPssPending = false;
+ memInfo = new MemInfoReader();
+ }
+ }
+ if (memInfo != null) {
+ updateCpuStatsNow();
+ long nativeTotalPss = 0;
+ synchronized (mProcessCpuThread) {
+ final int N = mProcessCpuTracker.countStats();
+ for (int j=0; j<N; j++) {
+ ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j);
+ if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID
+ || st.uid == Process.SYSTEM_UID) {
+ // This is definitely an application process; skip it.
+ continue;
+ }
+ synchronized (mPidsSelfLocked) {
+ if (mPidsSelfLocked.indexOfKey(st.pid) >= 0) {
+ // This is one of our own processes; skip it.
+ continue;
+ }
+ }
+ nativeTotalPss += Debug.getPss(st.pid, null);
+ }
+ }
+ memInfo.readMemInfo();
+ synchronized (this) {
+ if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+ + (SystemClock.uptimeMillis()-start) + "ms");
+ mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+ memInfo.getFreeSizeKb(),
+ memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+ memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()
+ +memInfo.getSlabSizeKb(),
+ nativeTotalPss);
+ }
+ }
+
+ int i=0, num=0;
long[] tmp = new long[1];
do {
ProcessRecord proc;
@@ -2176,6 +2233,8 @@
}
};
+ mLockToAppRequest = new LockToAppRequestDialog(mContext, this);
+
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
}
@@ -2769,7 +2828,7 @@
// come up (we have a pid but not yet its thread), so keep it.
if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
// If this is a new package in the process, add the package to the list
- app.addPackage(info.packageName, mProcessStats);
+ app.addPackage(info.packageName, info.versionCode, mProcessStats);
return app;
}
@@ -2824,7 +2883,7 @@
}
} else {
// If this is a new package in the process, add the package to the list
- app.addPackage(info.packageName, mProcessStats);
+ app.addPackage(info.packageName, info.versionCode, mProcessStats);
}
// If the system is not ready yet, then hold off on starting this
@@ -3662,6 +3721,12 @@
// Keep track of the root activity of the task before we finish it
TaskRecord tr = r.task;
ActivityRecord rootR = tr.getRootActivity();
+ // Do not allow task to finish in Lock Task mode.
+ if (tr == mStackSupervisor.mLockTaskModeTask) {
+ if (rootR == r) {
+ return false;
+ }
+ }
if (mController != null) {
// Find the first activity that is not finishing.
ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
@@ -3806,13 +3871,25 @@
public boolean finishActivityAffinity(IBinder token) {
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
- ActivityRecord r = ActivityRecord.isInStackLocked(token);
- boolean res = false;
- if (r != null) {
- res = r.task.stack.finishActivityAffinityLocked(r);
+ try {
+ ActivityRecord r = ActivityRecord.isInStackLocked(token);
+
+ ActivityRecord rootR = r.task.getRootActivity();
+ // Do not allow task to finish in Lock Task mode.
+ if (r.task == mStackSupervisor.mLockTaskModeTask) {
+ if (rootR == r) {
+ Binder.restoreCallingIdentity(origId);
+ return false;
+ }
+ }
+ boolean res = false;
+ if (r != null) {
+ res = r.task.stack.finishActivityAffinityLocked(r);
+ }
+ return res;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- Binder.restoreCallingIdentity(origId);
- return res;
}
}
@@ -6044,7 +6121,11 @@
if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
return false;
}
+ return checkHoldingPermissionsInternalLocked(pm, pi, grantUri, uid, modeFlags, true);
+ }
+ private final boolean checkHoldingPermissionsInternalLocked(IPackageManager pm, ProviderInfo pi,
+ GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
if (pi.applicationInfo.uid == uid) {
return true;
} else if (!pi.exported) {
@@ -6055,11 +6136,11 @@
boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
try {
// check if target holds top-level <provider> permissions
- if (!readMet && pi.readPermission != null
+ if (!readMet && pi.readPermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
readMet = true;
}
- if (!writeMet && pi.writePermission != null
+ if (!writeMet && pi.writePermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
writeMet = true;
}
@@ -6085,7 +6166,8 @@
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(pprperm, uid));
if (pprperm != null) {
- if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) {
+ if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
+ == PERMISSION_GRANTED) {
readMet = true;
} else {
allowDefaultRead = false;
@@ -6099,7 +6181,8 @@
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(ppwperm, uid));
if (ppwperm != null) {
- if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) {
+ if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
+ == PERMISSION_GRANTED) {
writeMet = true;
} else {
allowDefaultWrite = false;
@@ -6296,28 +6379,40 @@
}
}
+ /* There is a special cross user grant if:
+ * - The target is on another user.
+ * - Apps on the current user can access the uri without any uid permissions.
+ * In this case, we grant a uri permission, even if the ContentProvider does not normally
+ * grant uri permissions.
+ */
+ boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+ && checkHoldingPermissionsInternalLocked(pm, pi, grantUri, callingUid,
+ modeFlags, false /*without considering the uid permissions*/);
+
// Second... is the provider allowing granting of URI permissions?
- if (!pi.grantUriPermissions) {
- throw new SecurityException("Provider " + pi.packageName
- + "/" + pi.name
- + " does not allow granting of Uri permissions (uri "
- + grantUri + ")");
- }
- if (pi.uriPermissionPatterns != null) {
- final int N = pi.uriPermissionPatterns.length;
- boolean allowed = false;
- for (int i=0; i<N; i++) {
- if (pi.uriPermissionPatterns[i] != null
- && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
- allowed = true;
- break;
- }
- }
- if (!allowed) {
+ if (!specialCrossUserGrant) {
+ if (!pi.grantUriPermissions) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
- + " does not allow granting of permission to path of Uri "
- + grantUri);
+ + " does not allow granting of Uri permissions (uri "
+ + grantUri + ")");
+ }
+ if (pi.uriPermissionPatterns != null) {
+ final int N = pi.uriPermissionPatterns.length;
+ boolean allowed = false;
+ for (int i=0; i<N; i++) {
+ if (pi.uriPermissionPatterns[i] != null
+ && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
+ allowed = true;
+ break;
+ }
+ }
+ if (!allowed) {
+ throw new SecurityException("Provider " + pi.packageName
+ + "/" + pi.name
+ + " does not allow granting of permission to path of Uri "
+ + grantUri);
+ }
}
}
@@ -7642,12 +7737,20 @@
}
}
- private void startLockTaskMode(TaskRecord task) {
+ void startLockTaskMode(TaskRecord task) {
final String pkg;
synchronized (this) {
pkg = task.intent.getComponent().getPackageName();
}
- if (!isLockTaskAuthorized(pkg)) {
+ boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;
+ if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {
+ final TaskRecord taskRecord = task;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mLockToAppRequest.showLockTaskPrompt(taskRecord);
+ }
+ });
return;
}
long ident = Binder.clearCallingIdentity();
@@ -7659,7 +7762,7 @@
if ((mFocusedActivity == null) || (task != mFocusedActivity.task)) {
throw new IllegalArgumentException("Invalid task, not in foreground");
}
- mStackSupervisor.setLockTaskModeLocked(task);
+ mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated);
}
}
} finally {
@@ -7704,24 +7807,55 @@
}
@Override
+ public void startLockTaskModeOnCurrent() throws RemoteException {
+ checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+ ActivityRecord r = null;
+ synchronized (this) {
+ r = mStackSupervisor.topRunningActivityLocked();
+ }
+ startLockTaskMode(r.task);
+ }
+
+ @Override
public void stopLockTaskMode() {
// Verify that the user matches the package of the intent for the TaskRecord
- // we are locked to. This will ensure the same caller for startLockTaskMode and
- // stopLockTaskMode.
- try {
- String pkg = mStackSupervisor.mLockTaskModeTask.intent.getPackage();
- int uid = mContext.getPackageManager().getPackageUid(pkg,
- Binder.getCallingUserHandle().getIdentifier());
- if (uid != Binder.getCallingUid()) {
- throw new SecurityException("Invalid uid, expected " + uid);
+ // we are locked to or systtem. This will ensure the same caller for startLockTaskMode
+ // and stopLockTaskMode.
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ try {
+ String pkg =
+ mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName();
+ int uid = mContext.getPackageManager().getPackageUid(pkg,
+ Binder.getCallingUserHandle().getIdentifier());
+ if (uid != callingUid) {
+ throw new SecurityException("Invalid uid, expected " + uid);
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "stopLockTaskMode " + e);
+ return;
}
- } catch (NameNotFoundException e) {
- Log.d(TAG, "stopLockTaskMode " + e);
- return;
}
- // Stop lock task
- synchronized (this) {
- mStackSupervisor.setLockTaskModeLocked(null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ Log.d(TAG, "stopLockTaskMode");
+ // Stop lock task
+ synchronized (this) {
+ mStackSupervisor.setLockTaskModeLocked(null, false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void stopLockTaskModeOnCurrent() throws RemoteException {
+ checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ stopLockTaskMode();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -7780,7 +7914,8 @@
// to run in multiple processes, because this is actually
// part of the framework so doesn't make sense to track as a
// separate apk in the process.
- app.addPackage(cpi.applicationInfo.packageName, mProcessStats);
+ app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
+ mProcessStats);
}
ensurePackageDexOpt(cpi.applicationInfo.packageName);
}
@@ -12588,6 +12723,8 @@
}
}
+ long nativeProcTotalPss = 0;
+
if (!isCheckinRequest && procs.size() > 1) {
// If we are showing aggregations, also look for native processes to
// include so that our aggregations are more accurate.
@@ -12609,6 +12746,7 @@
final long myTotalPss = mi.getTotalPss();
totalPss += myTotalPss;
+ nativeProcTotalPss += myTotalPss;
MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
st.name, myTotalPss, st.pid, false);
@@ -12676,6 +12814,15 @@
}
MemInfoReader memInfo = new MemInfoReader();
memInfo.readMemInfo();
+ if (nativeProcTotalPss > 0) {
+ synchronized (this) {
+ mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+ memInfo.getFreeSizeKb(),
+ memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+ memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(),
+ nativeProcTotalPss);
+ }
+ }
if (!brief) {
if (!isCompact) {
pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
@@ -15456,6 +15603,7 @@
}
if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs! memLowered=" + memLowered);
mLastFullPssTime = now;
+ mFullPssPending = true;
mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
mPendingPssProcesses.clear();
for (int i=mLruProcesses.size()-1; i>=0; i--) {
@@ -16651,7 +16799,7 @@
return true;
}
- mStackSupervisor.setLockTaskModeLocked(null);
+ mStackSupervisor.setLockTaskModeLocked(null, false);
final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
if (userInfo == null) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index dd9cae9..0825f2e 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -582,11 +582,6 @@
}
}
- boolean isRootActivity() {
- final ArrayList<ActivityRecord> activities = task.mActivities;
- return activities.size() == 0 || this == activities.get(0);
- }
-
UriPermissionOwner getUriPermissionsLocked() {
if (uriPermissions == null) {
uriPermissions = new UriPermissionOwner(service, this);
@@ -1035,11 +1030,11 @@
return -1;
}
final TaskRecord task = r.task;
- switch (task.mActivities.indexOf(r)) {
- case -1: return -1;
- case 0: return task.taskId;
- default: return onlyRoot ? -1 : task.taskId;
+ final int activityNdx = task.mActivities.indexOf(r);
+ if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
+ return -1;
}
+ return task.taskId;
}
static ActivityRecord isInStackLocked(IBinder token) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 03ce530..fe2a473 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1066,40 +1066,6 @@
}
}
- /**
- * Determine if home should be visible below the passed record.
- * @param record activity we are querying for.
- * @return true if home is visible below the passed activity, false otherwise.
- */
- boolean isActivityOverHome(ActivityRecord record) {
- // Start at record and go down, look for either home or a visible fullscreen activity.
- final TaskRecord recordTask = record.task;
- for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
- TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- final int startNdx =
- task == recordTask ? activities.indexOf(record) : activities.size() - 1;
- for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- if (r.isHomeActivity()) {
- return true;
- }
- if (!r.finishing && r.fullscreen) {
- // Passed activity is over a fullscreen activity.
- return false;
- }
- }
- if (task.mOnTopOfHome) {
- // Got to the bottom of a task on top of home without finding a visible fullscreen
- // activity. Home is visible.
- return true;
- }
- }
- // Got to the bottom of this stack and still don't know. If this is over the home stack
- // then record is over home. May not work if we ever get more than two layers.
- return mStackSupervisor.isFrontStack(this);
- }
-
private void setVisibile(ActivityRecord r, boolean visible) {
r.visible = visible;
mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1954,8 +1920,7 @@
// existing activities from other tasks in to it.
// If the caller has requested that the target task be
// reset, then do so.
- if ((r.intent.getFlags()
- & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+ if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
resetTaskIfNeededLocked(r, r);
doShow = topRunningNonDelayedActivityLocked(null) == r;
}
@@ -2048,7 +2013,8 @@
// the root, we may no longer have the task!).
final ArrayList<ActivityRecord> activities = task.mActivities;
final int numActivities = activities.size();
- for (int i = numActivities - 1; i > 0; --i ) {
+ final int rootActivityNdx = task.findEffectiveRootIndex();
+ for (int i = numActivities - 1; i > rootActivityNdx; --i ) {
ActivityRecord target = activities.get(i);
final int flags = target.info.flags;
@@ -2207,8 +2173,10 @@
final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
final int numActivities = activities.size();
- // Do not operate on the root Activity.
- for (int i = numActivities - 1; i > 0; --i) {
+ final int rootActivityNdx = affinityTask.findEffectiveRootIndex();
+
+ // Do not operate on or below the effective root Activity.
+ for (int i = numActivities - 1; i > rootActivityNdx; --i) {
ActivityRecord target = activities.get(i);
final int flags = target.info.flags;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9264186..4be208b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -79,6 +79,8 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
@@ -142,6 +144,8 @@
private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
+ private static final String LOCK_TASK_TAG = "Lock-to-App";
+
/** Status Bar Service **/
private IBinder mToken = new Binder();
private IStatusBarService mStatusBarService;
@@ -255,6 +259,10 @@
/** If non-null then the task specified remains in front and no other tasks may be started
* until the task exits or #stopLockTaskMode() is called. */
TaskRecord mLockTaskModeTask;
+ /**
+ * Notifies the user when entering/exiting lock-task.
+ */
+ private LockTaskNotify mLockTaskNotify;
public ActivityStackSupervisor(ActivityManagerService service) {
mService = service;
@@ -1178,7 +1186,8 @@
// to run in multiple processes, because this is actually
// part of the framework so doesn't make sense to track as a
// separate apk in the process.
- app.addPackage(r.info.packageName, mService.mProcessStats);
+ app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
+ mService.mProcessStats);
}
realStartActivityLocked(r, app, andResume, checkConfig);
return;
@@ -3004,7 +3013,7 @@
return list;
}
- void setLockTaskModeLocked(TaskRecord task) {
+ void setLockTaskModeLocked(TaskRecord task, boolean showHomeRecents) {
if (task == null) {
// Take out of lock task mode if necessary
if (mLockTaskModeTask != null) {
@@ -3028,6 +3037,7 @@
lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName();
lockTaskMsg.arg1 = mLockTaskModeTask.userId;
lockTaskMsg.what = LOCK_TASK_START_MSG;
+ lockTaskMsg.arg2 = showHomeRecents ? 1 : 0;
mHandler.sendMessage(lockTaskMsg);
}
@@ -3130,15 +3140,24 @@
case LOCK_TASK_START_MSG: {
// When lock task starts, we disable the status bars.
try {
- if (getStatusBarService() != null) {
- getStatusBarService().disable
- (StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK,
- mToken, mService.mContext.getPackageName());
+ if (mLockTaskNotify == null) {
+ mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
+ mLockTaskNotify.show(true);
+ if (getStatusBarService() != null) {
+ int flags =
+ StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK;
+ if (msg.arg2 != 0) {
+ flags ^= StatusBarManager.DISABLE_HOME
+ | StatusBarManager.DISABLE_RECENT;
+ }
+ getStatusBarService().disable(flags, mToken,
+ mService.mContext.getPackageName());
+ }
+ mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(true,
- (String)msg.obj,
- msg.arg1);
+ (String)msg.obj, msg.arg1);
}
} catch (RemoteException ex) {
throw new RuntimeException(ex);
@@ -3147,15 +3166,29 @@
case LOCK_TASK_END_MSG: {
// When lock task ends, we enable the status bars.
try {
- if (getStatusBarService() != null) {
- getStatusBarService().disable
- (StatusBarManager.DISABLE_NONE,
- mToken, mService.mContext.getPackageName());
- }
+ if (getStatusBarService() != null) {
+ getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken,
+ mService.mContext.getPackageName());
+ }
+ mWindowManager.reenableKeyguard(mToken);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null,
msg.arg1);
}
+ if (mLockTaskNotify == null) {
+ mLockTaskNotify = new LockTaskNotify(mService.mContext);
+ }
+ mLockTaskNotify.show(false);
+ try {
+ boolean shouldLockKeyguard = Settings.System.getInt(
+ mService.mContext.getContentResolver(),
+ Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0;
+ if (shouldLockKeyguard) {
+ mWindowManager.lockNow(null);
+ }
+ } catch (SettingNotFoundException e) {
+ // No setting, don't lock.
+ }
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7b2b04d..cdcc74b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -887,7 +887,8 @@
info.activityInfo.applicationInfo.uid, false);
if (app != null && app.thread != null) {
try {
- app.addPackage(info.activityInfo.packageName, mService.mProcessStats);
+ app.addPackage(info.activityInfo.packageName,
+ info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
processCurBroadcastLocked(r, app);
return;
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
new file mode 100644
index 0000000..1997f46
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ * Helper to manage showing/hiding a image to notify them that they are entering
+ * or exiting lock-to-app mode.
+ */
+public class LockTaskNotify {
+ private static final String TAG = "LockTaskNotify";
+
+ private static final int SHOW_LENGTH_MS = 1500;
+
+ private final Context mContext;
+ private final H mHandler;
+
+ private ClingWindowView mClingWindow;
+ private WindowManager mWindowManager;
+ private boolean mIsStarting;
+
+ public LockTaskNotify(Context context) {
+ mContext = context;
+ mHandler = new H();
+ mWindowManager = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ public void show(boolean starting) {
+ mIsStarting = starting;
+ mHandler.obtainMessage(H.SHOW).sendToTarget();
+ }
+
+ public WindowManager.LayoutParams getClingWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ 0
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ ,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("LockTaskNotify");
+ lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
+ lp.gravity = Gravity.FILL;
+ return lp;
+ }
+
+ public FrameLayout.LayoutParams getImageLayoutParams() {
+ return new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+ }
+
+ private void handleShow() {
+ mClingWindow = new ClingWindowView(mContext);
+
+ // we will be hiding the nav bar, so layout as if it's already hidden
+ mClingWindow.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+ // show the confirmation
+ WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+ mWindowManager.addView(mClingWindow, lp);
+ }
+
+ private void handleHide() {
+ if (mClingWindow != null) {
+ mWindowManager.removeView(mClingWindow);
+ mClingWindow = null;
+ }
+ }
+
+
+ private class ClingWindowView extends FrameLayout {
+ private View mView;
+
+ private Runnable mUpdateLayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mView != null && mView.getParent() != null) {
+ mView.setLayoutParams(getImageLayoutParams());
+ }
+ }
+ };
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ post(mUpdateLayoutRunnable);
+ }
+ }
+ };
+
+ public ClingWindowView(Context context) {
+ super(context);
+ setClickable(true);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mWindowManager.getDefaultDisplay().getMetrics(metrics);
+
+ int id = R.layout.lock_to_app_exit;
+ if (mIsStarting) {
+ id = R.layout.lock_to_app_enter;
+ }
+ mView = View.inflate(getContext(), id, null);
+
+ addView(mView, getImageLayoutParams());
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.HIDE), SHOW_LENGTH_MS);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent motion) {
+ Slog.v(TAG, "ClingWindowView.onTouchEvent");
+ return true;
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int HIDE = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case SHOW:
+ handleShow();
+ break;
+ case HIDE:
+ handleHide();
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
new file mode 100644
index 0000000..6e86dff
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
@@ -0,0 +1,87 @@
+
+package com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class LockToAppRequestDialog implements OnClickListener {
+ private static final String TAG = "ActivityManager";
+
+ final private Context mContext;
+ final private ActivityManagerService mService;
+
+ private AlertDialog mDialog;
+ private TaskRecord mRequestedTask;
+
+ public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) {
+ mContext = context;
+ mService = activityManagerService;
+ }
+
+ public void showLockTaskPrompt(TaskRecord task) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ mRequestedTask = task;
+
+ final Resources r = Resources.getSystem();
+ final String descriptionString = r.getString(R.string.lock_to_app_description);
+ final SpannableString description =
+ new SpannableString(descriptionString.replace('$', ' '));
+ final ImageSpan imageSpan = new ImageSpan(mContext,
+ BitmapFactory.decodeResource(r, R.drawable.ic_recent),
+ DynamicDrawableSpan.ALIGN_BOTTOM);
+ final int index = descriptionString.indexOf('$');
+ if (index >= 0) {
+ description.setSpan(imageSpan, index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ mDialog =
+ new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.lock_to_app_title))
+ .setMessage(description)
+ .setPositiveButton(r.getString(R.string.lock_to_app_positive), this)
+ .setNegativeButton(r.getString(R.string.lock_to_app_negative), this)
+ .create();
+
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mDialog.show();
+
+ // Make icon fit.
+ final TextView msgTxt = (TextView) mDialog.findViewById(R.id.message);
+ final float width = imageSpan.getDrawable().getIntrinsicWidth();
+ final float height = imageSpan.getDrawable().getIntrinsicHeight();
+ final int lineHeight = msgTxt.getLineHeight();
+ imageSpan.getDrawable().setBounds(0, 0, (int) (lineHeight * width / height), lineHeight);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (DialogInterface.BUTTON_POSITIVE == which) {
+ Slog.d(TAG, "accept lock-to-app request");
+ // Automatically enable if not currently on. (Could be triggered by an app)
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.LOCK_TO_APP_ENABLED, 1);
+
+ // Start lock-to-app.
+ mService.startLockTaskMode(mRequestedTask);
+ } else {
+ Slog.d(TAG, "ignore lock-to-app request");
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 8d7d300..a5b80bc 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -53,8 +53,8 @@
final int userId; // user of process.
final String processName; // name of the process
// List of packages running in the process
- final ArrayMap<String, ProcessStats.ProcessState> pkgList
- = new ArrayMap<String, ProcessStats.ProcessState>();
+ final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList
+ = new ArrayMap<String, ProcessStats.ProcessStateHolder>();
IApplicationThread thread; // the actual proc... may be null only if
// 'persistent' is true (in which case we
// are in the process of launching the app)
@@ -371,7 +371,7 @@
uid = _uid;
userId = UserHandle.getUserId(_uid);
processName = _processName;
- pkgList.put(_info.packageName, null);
+ pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));
maxAdj = ProcessList.UNKNOWN_ADJ;
curRawAdj = setRawAdj = -100;
curAdj = setAdj = -100;
@@ -398,16 +398,15 @@
info.versionCode, processName);
baseProcessTracker.makeActive();
for (int i=0; i<pkgList.size(); i++) {
- ProcessStats.ProcessState ps = pkgList.valueAt(i);
- if (ps != null && ps != origBase) {
- ps.makeInactive();
+ ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+ if (holder.state != null && holder.state != origBase) {
+ holder.state.makeInactive();
}
- ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+ holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
info.versionCode, processName);
- if (ps != baseProcessTracker) {
- ps.makeActive();
+ if (holder.state != baseProcessTracker) {
+ holder.state.makeActive();
}
- pkgList.setValueAt(i, ps);
}
}
thread = _thread;
@@ -424,11 +423,11 @@
}
baseProcessTracker = null;
for (int i=0; i<pkgList.size(); i++) {
- ProcessStats.ProcessState ps = pkgList.valueAt(i);
- if (ps != null && ps != origBase) {
- ps.makeInactive();
+ ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+ if (holder.state != null && holder.state != origBase) {
+ holder.state.makeInactive();
}
- pkgList.setValueAt(i, null);
+ holder.state = null;
}
}
}
@@ -572,14 +571,16 @@
/*
* Return true if package has been added false if not
*/
- public boolean addPackage(String pkg, ProcessStatsService tracker) {
+ public boolean addPackage(String pkg, int versionCode, ProcessStatsService tracker) {
if (!pkgList.containsKey(pkg)) {
if (baseProcessTracker != null) {
- ProcessStats.ProcessState state = tracker.getProcessStateLocked(
- pkg, info.uid, info.versionCode, processName);
- pkgList.put(pkg, state);
- if (state != baseProcessTracker) {
- state.makeActive();
+ ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+ versionCode);
+ holder.state = tracker.getProcessStateLocked(
+ pkg, info.uid, versionCode, processName);
+ pkgList.put(pkg, holder);
+ if (holder.state != baseProcessTracker) {
+ holder.state.makeActive();
}
} else {
pkgList.put(pkg, null);
@@ -615,23 +616,26 @@
tracker.getMemFactorLocked(), now, pkgList);
if (N != 1) {
for (int i=0; i<N; i++) {
- ProcessStats.ProcessState ps = pkgList.valueAt(i);
- if (ps != null && ps != baseProcessTracker) {
- ps.makeInactive();
+ ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+ if (holder.state != null && holder.state != baseProcessTracker) {
+ holder.state.makeInactive();
}
}
pkgList.clear();
ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
info.packageName, info.uid, info.versionCode, processName);
- pkgList.put(info.packageName, ps);
+ ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+ info.versionCode);
+ holder.state = ps;
+ pkgList.put(info.packageName, holder);
if (ps != baseProcessTracker) {
ps.makeActive();
}
}
} else if (N != 1) {
pkgList.clear();
- pkgList.put(info.packageName, null);
+ pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.versionCode));
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 14f3ef9..7ec14c29 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -171,10 +171,17 @@
return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
}
+ public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
+ long nativeMem) {
+ mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
+ }
+
public boolean shouldWriteNowLocked(long now) {
if (now > (mLastWriteTime+WRITE_PERIOD)) {
if (SystemClock.elapsedRealtime()
- > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) {
+ > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
+ SystemClock.uptimeMillis()
+ > (mProcessStats.mTimePeriodStartUptime+ProcessStats.COMMIT_UPTIME_PERIOD)) {
mCommitPending = true;
}
return true;
@@ -212,6 +219,7 @@
if (mPendingWrite == null || !mPendingWriteCommitted) {
mPendingWrite = Parcel.obtain();
mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+ mProcessStats.mTimePeriodEndUptime = now;
if (commit) {
mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
}
@@ -439,8 +447,10 @@
mWriteLock.lock();
try {
synchronized (mAm) {
+ long now = SystemClock.uptimeMillis();
mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
- mProcessStats.writeToParcel(current, 0);
+ mProcessStats.mTimePeriodEndUptime = now;
+ mProcessStats.writeToParcel(current, now, 0);
}
if (historic != null) {
ArrayList<String> files = getCommittedFiles(0, false, true);
@@ -470,8 +480,10 @@
Parcel current = Parcel.obtain();
long curTime;
synchronized (mAm) {
+ long now = SystemClock.uptimeMillis();
mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
- mProcessStats.writeToParcel(current, 0);
+ mProcessStats.mTimePeriodEndUptime = now;
+ mProcessStats.writeToParcel(current, now, 0);
curTime = mProcessStats.mTimePeriodEndRealtime
- mProcessStats.mTimePeriodStartRealtime;
}
@@ -568,8 +580,8 @@
static private void dumpHelp(PrintWriter pw) {
pw.println("Process stats (procstats) dump options:");
pw.println(" [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
- pw.println(" [--details] [--full-details] [--current] [--hours] [--active]");
- pw.println(" [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
+ pw.println(" [--details] [--full-details] [--current] [--hours N] [--last N]");
+ pw.println(" [--active] [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
pw.println(" --checkin: perform a checkin: print and delete old committed states.");
pw.println(" --c: print only state in checkin format.");
pw.println(" --csv: output data suitable for putting in a spreadsheet.");
@@ -581,6 +593,7 @@
pw.println(" --full-details: dump all timing and active state details.");
pw.println(" --current: only dump current state.");
pw.println(" --hours: aggregate over about N last hours.");
+ pw.println(" --last: only show the last committed stats at index N (starting at 1).");
pw.println(" --active: only show currently active processes/services.");
pw.println(" --commit: commit current stats to disk and reset to start new stats.");
pw.println(" --reset: reset current stats, without committing.");
@@ -621,6 +634,7 @@
boolean dumpFullDetails = false;
boolean dumpAll = false;
int aggregateHours = 0;
+ int lastIndex = 0;
boolean activeOnly = false;
String reqPackage = null;
boolean csvSepScreenStats = false;
@@ -705,6 +719,20 @@
dumpHelp(pw);
return;
}
+ } else if ("--last".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("Error: argument required for --last");
+ dumpHelp(pw);
+ return;
+ }
+ try {
+ lastIndex = Integer.parseInt(args[i]);
+ } catch (NumberFormatException e) {
+ pw.println("Error: --last argument not an int -- " + args[i]);
+ dumpHelp(pw);
+ return;
+ }
} else if ("--active".equals(arg)) {
activeOnly = true;
currentOnly = true;
@@ -818,6 +846,43 @@
dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
dumpDetails, dumpFullDetails, dumpAll, activeOnly);
return;
+ } else if (lastIndex > 0) {
+ pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
+ ArrayList<String> files = getCommittedFiles(0, false, true);
+ if (lastIndex >= files.size()) {
+ pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
+ return;
+ }
+ AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
+ ProcessStats processStats = new ProcessStats(false);
+ readLocked(processStats, file);
+ if (processStats.mReadError != null) {
+ if (isCheckin || isCompact) pw.print("err,");
+ pw.print("Failure reading "); pw.print(files.get(lastIndex));
+ pw.print("; "); pw.println(processStats.mReadError);
+ return;
+ }
+ String fileStr = file.getBaseFile().getPath();
+ boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
+ if (isCheckin || isCompact) {
+ // Don't really need to lock because we uniquely own this object.
+ processStats.dumpCheckinLocked(pw, reqPackage);
+ } else {
+ pw.print("COMMITTED STATS FROM ");
+ pw.print(processStats.mTimePeriodStartClockStr);
+ if (checkedIn) pw.print(" (checked in)");
+ pw.println(":");
+ if (dumpDetails || dumpFullDetails) {
+ processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
+ activeOnly);
+ if (dumpAll) {
+ pw.print(" mFile="); pw.println(mFile.getBaseFile());
+ }
+ } else {
+ processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+ }
+ }
+ return;
}
boolean sepNeeded = false;
@@ -859,7 +924,7 @@
// Always dump summary here, dumping all details is just too
// much crud.
if (dumpFullDetails) {
- mProcessStats.dumpLocked(pw, reqPackage, now, false, false,
+ processStats.dumpLocked(pw, reqPackage, now, false, false,
activeOnly);
} else {
processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1cde41f..986321d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,7 +27,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
@@ -57,11 +56,12 @@
private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+ private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
final int taskId; // Unique identifier for this task.
- final String affinity; // The affinity name for this task, or null.
+ String affinity; // The affinity name for this task, or null.
final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
Intent intent; // The original intent that started the task.
@@ -111,13 +111,15 @@
* Display.DEFAULT_DISPLAY. */
boolean mOnTopOfHome = false;
+ /** If original intent did not allow relinquishing task identity, save that information */
+ boolean mNeverRelinquishIdentity = true;
+
final ActivityManagerService mService;
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
mService = service;
taskId = _taskId;
- affinity = info.taskAffinity;
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
setIntent(_intent, info);
@@ -128,7 +130,7 @@
String _affinity, ComponentName _realActivity, ComponentName _origActivity,
boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
- long lastTimeMoved) {
+ long lastTimeMoved, boolean neverRelinquishIdentity) {
mService = service;
taskId = _taskId;
intent = _intent;
@@ -146,6 +148,7 @@
lastDescription = _lastDescription;
mActivities = activities;
mLastTimeMoved = lastTimeMoved;
+ mNeverRelinquishIdentity = neverRelinquishIdentity;
}
void touchActiveTime() {
@@ -157,6 +160,14 @@
}
void setIntent(Intent _intent, ActivityInfo info) {
+ if (intent == null) {
+ mNeverRelinquishIdentity =
+ (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ } else if (mNeverRelinquishIdentity) {
+ return;
+ }
+
+ affinity = info.taskAffinity;
stringName = null;
if (info.targetActivity == null) {
@@ -282,6 +293,7 @@
mActivities.remove(newTop);
mActivities.add(newTop);
+ updateEffectiveIntent();
setFrontOfTask();
}
@@ -311,6 +323,7 @@
r.mActivityType = taskType;
}
mActivities.add(index, r);
+ updateEffectiveIntent();
if (r.isPersistable()) {
mService.notifyTaskPersisterLocked(this, false);
}
@@ -325,7 +338,11 @@
if (r.isPersistable()) {
mService.notifyTaskPersisterLocked(this, false);
}
- return mActivities.size() == 0;
+ if (mActivities.isEmpty()) {
+ return true;
+ }
+ updateEffectiveIntent();
+ return false;
}
boolean autoRemoveFromRecents() {
@@ -579,12 +596,19 @@
// utility activities.
int activityNdx;
final int numActivities = mActivities.size();
+ final boolean relinquish = numActivities == 0 ? false :
+ (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
+ if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+ // This will be the top activity for determining taskDescription. Pre-inc to
+ // overcome initial decrement below.
+ ++activityNdx;
+ break;
+ }
if (r.intent != null &&
- (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
- != 0) {
+ (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
break;
}
}
@@ -615,6 +639,27 @@
}
}
+ int findEffectiveRootIndex() {
+ int activityNdx;
+ final int topActivityNdx = mActivities.size() - 1;
+ for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.finishing) {
+ continue;
+ }
+ if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+ break;
+ }
+ }
+ return activityNdx;
+ }
+
+ void updateEffectiveIntent() {
+ final int effectiveRootIndex = findEffectiveRootIndex();
+ final ActivityRecord r = mActivities.get(effectiveRootIndex);
+ setIntent(r.intent, r.info);
+ }
+
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
Slog.i(TAG, "Saving task=" + this);
@@ -634,6 +679,7 @@
out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+ out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
if (lastDescription != null) {
out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
}
@@ -684,6 +730,7 @@
int userId = 0;
String lastDescription = null;
long lastTimeOnTop = 0;
+ boolean neverRelinquishIdentity = true;
int taskId = -1;
final int outerDepth = in.getDepth();
@@ -714,6 +761,8 @@
lastDescription = attrValue;
} else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
lastTimeOnTop = Long.valueOf(attrValue);
+ } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
+ neverRelinquishIdentity = Boolean.valueOf(attrValue);
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
}
@@ -748,7 +797,7 @@
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, realActivity, origActivity, rootHasReset,
askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
- lastTimeOnTop);
+ lastTimeOnTop, neverRelinquishIdentity);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 353f603..52c092c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -78,7 +78,7 @@
*/
void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
assertRunOnServiceThread();
- HdmiCecDeviceInfo targetDevice = mService.getDeviceInfo(targetAddress);
+ HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
if (targetDevice == null) {
invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE);
return;
@@ -142,6 +142,9 @@
}
private static void invokeCallback(IHdmiControlCallback callback, int result) {
+ if (callback == null) {
+ return;
+ }
try {
callback.onComplete(result);
} catch (RemoteException e) {
@@ -455,8 +458,11 @@
final void addCecDevice(HdmiCecDeviceInfo info) {
assertRunOnServiceThread();
addDeviceInfo(info);
-
- // TODO: announce new device detection.
+ if (info.getLogicalAddress() == mAddress) {
+ // The addition of TV device itself should not be notified.
+ return;
+ }
+ mService.invokeDeviceEventListeners(info, true);
}
/**
@@ -466,10 +472,9 @@
*/
final void removeCecDevice(int address) {
assertRunOnServiceThread();
- removeDeviceInfo(address);
+ HdmiCecDeviceInfo info = removeDeviceInfo(address);
mCecMessageCache.flushMessagesFrom(address);
-
- // TODO: announce a device removal.
+ mService.invokeDeviceEventListeners(info, false);
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fddb833..d323f34 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -37,6 +37,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
@@ -94,21 +95,25 @@
// List of listeners registered by callers that want to get notified of
// hotplug events.
+ @GuardedBy("mLock")
private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
// List of records for hotplug event listener to handle the the caller killed in action.
+ @GuardedBy("mLock")
private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
new ArrayList<>();
// List of listeners registered by callers that want to get notified of
// device status events.
+ @GuardedBy("mLock")
private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
// List of records for device event listener to handle the the caller killed in action.
+ @GuardedBy("mLock")
private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
new ArrayList<>();
- // Handler running on service thread. It's used to run a task in service thread.
+ // Handler used to run a task in service thread.
private final Handler mHandler = new Handler();
@Nullable
@@ -153,6 +158,7 @@
// A container for [Logical Address, Local device info].
final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
final SparseIntArray finished = new SparseIntArray();
+ mCecController.clearLogicalAddress();
for (int type : deviceTypes) {
final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
localDevice.init();
@@ -171,8 +177,7 @@
}
finished.append(deviceType, logicalAddress);
- // Once finish address allocation for all devices, notify
- // it to each device.
+ // Address allocation completed for all devices. Notify each device.
if (deviceTypes.size() == finished.size()) {
notifyAddressAllocated(devices);
}
@@ -185,7 +190,7 @@
for (int i = 0; i < devices.size(); ++i) {
int address = devices.keyAt(i);
HdmiCecLocalDevice device = devices.valueAt(i);
- device.onAddressAllocated(address);
+ device.handleAddressAllocated(address);
}
}
@@ -663,15 +668,28 @@
}
private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
+ DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Listener already died");
+ return;
+ }
synchronized (mLock) {
- for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
- if (record.mListener.asBinder() == listener.asBinder()) {
- listener.asBinder().unlinkToDeath(record, 0);
- mDeviceEventListenerRecords.remove(record);
- break;
+ mDeviceEventListeners.add(listener);
+ mDeviceEventListenerRecords.add(record);
+ }
+ }
+
+ void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
+ synchronized (mLock) {
+ for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
+ try {
+ listener.onStatusChanged(device, activated);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report device event:" + e);
}
}
- mHotplugEventListeners.remove(listener);
}
}
@@ -683,16 +701,16 @@
}
}
- private void announceHotplugEvent(int portNo, boolean connected) {
- HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected);
+ private void announceHotplugEvent(int portId, boolean connected) {
+ HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
synchronized (mLock) {
for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
- invokeHotplugEventListener(listener, event);
+ invokeHotplugEventListenerLocked(listener, event);
}
}
}
- private void invokeHotplugEventListener(IHdmiHotplugEventListener listener,
+ private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
HdmiHotplugEvent event) {
try {
listener.onReceived(event);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index e48b0dc..92418ab 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -28,9 +28,6 @@
// State in which waits for <SetSystemAudioMode>.
private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
- // State in which waits for <ReportAudioStatus>.
- private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 2;
-
private static final int MAX_SEND_RETRY_COUNT = 2;
private static final int ON_TIMEOUT_MS = 5000;
@@ -92,39 +89,6 @@
tv().setSystemAudioMode(mode);
}
- protected void sendGiveAudioStatus() {
- HdmiCecMessage command = HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(),
- mAvrLogicalAddress);
- sendCommand(command, new HdmiControlService.SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
- mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
- addTimer(mState, TIMEOUT_MS);
- } else {
- handleSendGiveAudioStatusFailure();
- }
- }
- });
- }
-
- private void handleSendGiveAudioStatusFailure() {
- // TODO: Notify the failure status.
-
- int uiCommand = tv().getSystemAudioMode()
- ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION // SystemAudioMode: ON
- : HdmiConstants.UI_COMMAND_MUTE_FUNCTION; // SystemAudioMode: OFF
- sendUserControlPressedAndReleased(uiCommand);
- finish();
- }
-
- private void sendUserControlPressedAndReleased(int uiCommand) {
- sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
- getSourceAddress(), mAvrLogicalAddress, uiCommand));
- sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
- getSourceAddress(), mAvrLogicalAddress));
- }
-
@Override
final boolean processCommand(HdmiCecMessage cmd) {
switch (mState) {
@@ -137,7 +101,8 @@
boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
if (receivedStatus == mTargetAudioStatus) {
setSystemAudioMode(receivedStatus);
- sendGiveAudioStatus();
+ startAudioStatusAction();
+ return true;
} else {
// Unexpected response, consider the request is newly initiated by AVR.
// To return 'false' will initiate new SystemAudioActionFromAvr by the control
@@ -145,28 +110,14 @@
finish();
return false;
}
- return true;
-
- case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
- // TODO: Handle <FeatureAbort> of <GiveAudioStatus>
- if (cmd.getOpcode() != HdmiCec.MESSAGE_REPORT_AUDIO_STATUS
- || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
- return false;
- }
- byte[] params = cmd.getParams();
- if (params.length > 0) {
- boolean mute = (params[0] & 0x80) == 0x80;
- int volume = params[0] & 0x7F;
- tv().setAudioStatus(mute, volume);
- if (mTargetAudioStatus && mute || !mTargetAudioStatus && !mute) {
- // Toggle AVR's mute status to match with the system audio status.
- sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
- }
- }
- finish();
- return true;
+ default:
+ return false;
}
- return false;
+ }
+
+ protected void startAudioStatusAction() {
+ addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress));
+ finish();
}
protected void removeSystemAudioActionInProgress() {
@@ -183,9 +134,6 @@
case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
handleSendSystemAudioModeRequestTimeout();
return;
- case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
- handleSendGiveAudioStatusFailure();
- return;
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 3907b71..b743c64 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -59,7 +59,7 @@
// TODO: Stop the action for System Audio Mode initialization if it is running.
if (mTargetAudioStatus) {
setSystemAudioMode(true);
- sendGiveAudioStatus();
+ startAudioStatusAction();
} else {
setSystemAudioMode(false);
finish();
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index 75e4fef..5f4fc23 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -25,7 +25,6 @@
/**
* Action to update audio status (volume or mute) of audio amplifier
*/
-// TODO: refactor SystemAudioMode so that it uses this class instead of internal state.
final class SystemAudioStatusAction extends FeatureAction {
private static final String TAG = "SystemAudioStatusAction";
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 15a6b25..53337c4 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -203,7 +203,8 @@
@Override
public String toString() {
return String.valueOf(hashCode()).substring(0, 3) + ".."
- + ":[" + job.getService().getPackageName() + ",jId=" + job.getId()
+ + ":[" + job.getService()
+ + ",jId=" + job.getId()
+ ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
+ ",N=" + job.getNetworkCapabilities() + ",C=" + job.isRequireCharging()
+ ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9d61493..1264741 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,6 +17,8 @@
package com.android.server.media;
import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.routeprovider.RouteRequest;
@@ -25,7 +27,6 @@
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.MediaController;
-import android.media.session.RemoteVolumeProvider;
import android.media.session.RouteCommand;
import android.media.session.RouteInfo;
import android.media.session.RouteOptions;
@@ -34,9 +35,11 @@
import android.media.session.MediaSessionInfo;
import android.media.session.RouteInterface;
import android.media.session.PlaybackState;
+import android.media.session.ParcelableVolumeInfo;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
+import android.media.VolumeProvider;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
@@ -107,6 +110,7 @@
// TODO define a RouteState class with relevant info
private int mRouteState;
private long mFlags;
+ private ComponentName mMediaButtonReceiver;
// TransportPerformer fields
@@ -117,9 +121,10 @@
// End TransportPerformer fields
// Volume handling fields
- private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
+ private AudioManager mAudioManager;
+ private int mVolumeType = MediaSession.VOLUME_TYPE_LOCAL;
private int mAudioStream = AudioManager.STREAM_MUSIC;
- private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+ private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
// End volume handling fields
@@ -140,6 +145,7 @@
mSessionCb = new SessionCb(cb);
mService = service;
mHandler = new MessageHandler(handler.getLooper());
+ mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
}
/**
@@ -187,6 +193,10 @@
return mSessionInfo;
}
+ public ComponentName getMediaButtonReceiver() {
+ return mMediaButtonReceiver;
+ }
+
/**
* Get this session's flags.
*
@@ -265,20 +275,42 @@
*
* @param delta The amount to adjust the volume by.
*/
- public void adjustVolumeBy(int delta) {
- if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
- // Nothing to do, the volume cannot be changed
- return;
+ public void adjustVolumeBy(int delta, int flags) {
+ if (mVolumeType == MediaSession.VOLUME_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);
+ }
+ }
+ } else {
+ if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+ // Nothing to do, the volume cannot be changed
+ return;
+ }
+ mSessionCb.adjustVolumeBy(delta);
}
- mSessionCb.adjustVolumeBy(delta);
}
- public void setVolumeTo(int value) {
- if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
- // Nothing to do. The volume can't be set directly.
- return;
+ public void setVolumeTo(int value, int flags) {
+ if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) {
+ mAudioManager.setStreamVolume(mAudioStream, value, flags);
+ } else {
+ if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+ // Nothing to do. The volume can't be set directly.
+ return;
+ }
+ mSessionCb.setVolumeTo(value);
}
- mSessionCb.setVolumeTo(value);
}
/**
@@ -352,7 +384,7 @@
* @return The current type of playback.
*/
public int getPlaybackType() {
- return mPlaybackType;
+ return mVolumeType;
}
/**
@@ -683,6 +715,11 @@
}
@Override
+ public void setMediaButtonReceiver(ComponentName mbr) {
+ mMediaButtonReceiver = mbr;
+ }
+
+ @Override
public void setMetadata(MediaMetadata metadata) {
mMetadata = metadata;
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
@@ -754,7 +791,7 @@
public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
switch(type) {
case MediaSession.VOLUME_TYPE_LOCAL:
- mPlaybackType = type;
+ mVolumeType = type;
int audioStream = arg1;
if (isValidStream(audioStream)) {
mAudioStream = audioStream;
@@ -764,7 +801,7 @@
}
break;
case MediaSession.VOLUME_TYPE_REMOTE:
- mPlaybackType = type;
+ mVolumeType = type;
mVolumeControlType = arg1;
mMaxVolume = arg2;
break;
@@ -985,6 +1022,35 @@
}
@Override
+ public ParcelableVolumeInfo getVolumeAttributes() {
+ synchronized (mLock) {
+ int type;
+ int max;
+ int current;
+ if (mVolumeType == MediaSession.VOLUME_TYPE_REMOTE) {
+ type = mVolumeControlType;
+ max = mMaxVolume;
+ current = mCurrentVolume;
+ } else {
+ type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+ max = mAudioManager.getStreamMaxVolume(mAudioStream);
+ current = mAudioManager.getStreamVolume(mAudioStream);
+ }
+ return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current);
+ }
+ }
+
+ @Override
+ public void adjustVolumeBy(int delta, int flags) {
+ MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+ }
+
+ @Override
+ public void setVolumeTo(int value, int flags) {
+ MediaSessionRecord.this.setVolumeTo(value, flags);
+ }
+
+ @Override
public void play() throws RemoteException {
mSessionCb.play();
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 67065ba..685717f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -23,6 +23,7 @@
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -88,6 +89,7 @@
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
+ private ContentResolver mContentResolver;
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
@@ -115,6 +117,7 @@
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
+ mContentResolver = getContext().getContentResolver();
}
private IAudioService getAudioService() {
@@ -381,8 +384,7 @@
return false;
}
if (compName != null) {
- final String enabledNotifListeners = Settings.Secure.getStringForUser(
- getContext().getContentResolver(),
+ final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
userId);
if (enabledNotifListeners != null) {
@@ -485,6 +487,9 @@
synchronized (mLock) {
List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
int size = records.size();
+ if (size > 0) {
+ persistMediaButtonReceiverLocked(records.get(0));
+ }
ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
for (int i = 0; i < size; i++) {
tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
@@ -504,6 +509,16 @@
}
}
+ private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
+ ComponentName receiver = record.getMediaButtonReceiver();
+ if (receiver != null) {
+ Settings.System.putStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER,
+ receiver == null ? "" : receiver.flattenToString(),
+ UserHandle.USER_CURRENT);
+ }
+ }
+
private MediaRouteProviderProxy.RoutesListener mRoutesCallback
= new MediaRouteProviderProxy.RoutesListener() {
@Override
@@ -881,14 +896,6 @@
private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
MediaSessionRecord session) {
- int direction = 0;
- int steps = delta;
- if (delta > 0) {
- direction = 1;
- } else if (delta < 0) {
- direction = -1;
- steps = -delta;
- }
if (DEBUG) {
String sessionInfo = session == null ? null : session.getSessionInfo().toString();
Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
@@ -901,6 +908,14 @@
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());
@@ -910,26 +925,7 @@
Log.e(TAG, "Error adjusting default volume.", e);
}
} else {
- if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
- try {
- if (delta == 0) {
- mAudioService.adjustSuggestedStreamVolume(delta,
- session.getAudioStream(), flags,
- getContext().getOpPackageName());
- } else {
- for (int i = 0; i < steps; i++) {
- mAudioService.adjustSuggestedStreamVolume(direction,
- session.getAudioStream(), flags,
- getContext().getOpPackageName());
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error adjusting volume for stream "
- + session.getAudioStream(), e);
- }
- } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
- session.adjustVolumeBy(delta);
- }
+ session.adjustVolumeBy(delta, flags);
}
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 2a7b4f6..1d53016 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -34,9 +34,9 @@
public class BackgroundDexOptService extends JobService {
static final String TAG = "BackgroundDexOptService";
- static final int BACKGROUND_DEXOPT_JOB = 808;
+ static final int BACKGROUND_DEXOPT_JOB = 800;
private static ComponentName sDexoptServiceName = new ComponentName(
- BackgroundDexOptService.class.getPackage().getName(),
+ "android",
BackgroundDexOptService.class.getName());
final AtomicBoolean mIdleTime = new AtomicBoolean(false);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a7fc7eb..90e263a 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4229,6 +4229,7 @@
try {
pp.collectCertificates(pkg, parseFlags);
+ pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
mLastScanError = e.error;
return false;
@@ -10272,6 +10273,7 @@
try {
pp.collectCertificates(pkg, parseFlags);
+ pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
res.returnCode = e.error;
return;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index cbbcc58..6798f3f 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -106,11 +106,9 @@
mLogHandler = new LogHandler(IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context);
- registerBroadcastReceivers();
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
- buildTvInputListLocked(mCurrentUserId);
}
}
@@ -119,6 +117,16 @@
publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ registerBroadcastReceivers();
+ synchronized (mLock) {
+ buildTvInputListLocked(mCurrentUserId);
+ }
+ }
+ }
+
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
@Override
@@ -631,7 +639,9 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
- return false;
+ // STOPSHIP: Redesign the API around the availability change. For now, the service
+ // will be always available.
+ return true;
}
@Override
diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp
index 9a5079d..3696e24 100644
--- a/services/core/jni/com_android_server_AssetAtlasService.cpp
+++ b/services/core/jni/com_android_server_AssetAtlasService.cpp
@@ -61,8 +61,7 @@
jobject canvas, jint width, jint height) {
SkBitmap* bitmap = new SkBitmap;
- bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
- bitmap->allocPixels();
+ bitmap->allocN32Pixels(width, height);
bitmap->eraseColor(0);
INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap));
diff --git a/telecomm/java/android/telecomm/PhoneApplication.java b/telecomm/java/android/telecomm/PhoneApplication.java
deleted file mode 100644
index 1da54e0..0000000
--- a/telecomm/java/android/telecomm/PhoneApplication.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package android.telecomm;
-
-import android.annotation.SystemApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.telecomm.ITelecommService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class for managing the primary phone application that will receive incoming calls, and be allowed
- * to make emergency outgoing calls.
- *
- * @hide
- */
-public class PhoneApplication {
- private static final String TAG = PhoneApplication.class.getSimpleName();
- private static final String TELECOMM_SERVICE_NAME = "telecomm";
-
- /**
- * Sets the specified package name as the default phone application. The caller of this method
- * needs to have permission to write to secure settings.
- *
- * @hide
- * */
- @SystemApi
- public static void setDefaultPhoneApplication(String packageName, Context context) {
- // Get old package name
- String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.PHONE_DEFAULT_APPLICATION);
-
- if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
- // No change
- return;
- }
-
- // Only make the change if the new package belongs to a valid phone application
- List<ComponentName> componentNames = getInstalledPhoneApplications(context);
- ComponentName foundComponent = null;
- for (ComponentName componentName : componentNames) {
- if (TextUtils.equals(componentName.getPackageName(), packageName)) {
- foundComponent = componentName;
- break;
- }
- }
-
- if (foundComponent != null) {
- // Update the secure setting.
- Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.PHONE_DEFAULT_APPLICATION, foundComponent.getPackageName());
- }
- }
-
- /**
- * Returns the installed phone application that will be used to receive incoming calls, and is
- * allowed to make emergency calls.
- *
- * The application will be returned in order of preference:
- * 1) User selected phone application (if still installed)
- * 2) Pre-installed system dialer (if not disabled)
- * 3) Null
- *
- * @hide
- * */
- @SystemApi
- public static ComponentName getDefaultPhoneApplication(Context context) {
- String defaultPackageName = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.PHONE_DEFAULT_APPLICATION);
-
- final List<ComponentName> componentNames = getInstalledPhoneApplications(context);
- if (!TextUtils.isEmpty(defaultPackageName)) {
- for (ComponentName componentName : componentNames) {
- if (TextUtils.equals(componentName.getPackageName(), defaultPackageName)) {
- return componentName;
- }
- }
- }
-
- // No user-set dialer found, fallback to system dialer
- ComponentName systemDialer = null;
- try {
- systemDialer = getTelecommService().getSystemPhoneApplication();
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelecommService#getSystemPhoneApplication", e);
- return null;
- }
-
- if (systemDialer == null) {
- // No system dialer configured at build time
- return null;
- }
-
- // Verify that the system dialer has not been disabled.
- return getComponentName(componentNames, systemDialer.getPackageName());
- }
-
- /**
- * Returns a list of installed and available phone applications.
- *
- * In order to appear in the list, a phone application must implement an intent-filter with
- * the DIAL intent for the following schemes:
- *
- * 1) Empty scheme
- * 2) tel Uri scheme
- *
- * @hide
- **/
- @SystemApi
- public static List<ComponentName> getInstalledPhoneApplications(Context context) {
- PackageManager packageManager = context.getPackageManager();
-
- // Get the list of apps registered for the DIAL intent with empty scheme
- Intent intent = new Intent(Intent.ACTION_DIAL);
- List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
-
- List<ComponentName> componentNames = new ArrayList<ComponentName> ();
-
- for (ResolveInfo resolveInfo : resolveInfoList) {
- final ActivityInfo activityInfo = resolveInfo.activityInfo;
- if (activityInfo == null) {
- continue;
- }
- final ComponentName componentName =
- new ComponentName(activityInfo.packageName, activityInfo.name);
- componentNames.add(componentName);
- }
-
- // TODO: Filter for apps that don't handle DIAL intent with tel scheme
- return componentNames;
- }
-
- /**
- * Returns the {@link ComponentName} for the installed phone application for a given package
- * name.
- *
- * @param context A valid context.
- * @param packageName to retrieve the {@link ComponentName} for.
- *
- * @return The {@link ComponentName} for the installed phone application corresponding to the
- * package name, or null if none is found.
- *
- * @hide
- */
- @SystemApi
- public static ComponentName getPhoneApplicationForPackageName(Context context,
- String packageName) {
- return getComponentName(getInstalledPhoneApplications(context), packageName);
- }
-
- /**
- * Returns the component from a list of application components that corresponds to the package
- * name.
- *
- * @param componentNames A list of component names
- * @param packageName The package name to look for
- * @return The {@link ComponentName} that matches the provided packageName, or null if not
- * found.
- */
- private static ComponentName getComponentName(List<ComponentName> componentNames,
- String packageName) {
- for (ComponentName componentName : componentNames) {
- if (TextUtils.equals(packageName, componentName.getPackageName())) {
- return componentName;
- }
- }
- return null;
- }
-
- private static ITelecommService getTelecommService() {
- return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
- }
-}
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index 0952097..0a12c08 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -56,21 +56,6 @@
public static final String ACTION_CALL_SERVICE_SELECTOR = CallServiceSelector.class.getName();
/**
- * Activity action: Ask the user to change the default phone application. This will show a
- * dialog that asks the user whether they want to replace the current default phone application
- * with the one defined in {@link #EXTRA_PACKAGE_NAME}.
- */
- public static final String ACTION_CHANGE_DEFAULT_PHONE =
- "android.telecomm.ACTION_CHANGE_DEFAULT_PHONE";
-
- /**
- * The PackageName string passed in as an extra for {@link #ACTION_CHANGE_DEFAULT_PHONE}.
- *
- * @see #ACTION_CHANGE_DEFAULT_PHONE
- */
- public static final String EXTRA_PACKAGE_NAME = "package";
-
- /**
* Optional extra for {@link Intent#ACTION_CALL} containing a boolean that determines whether
* the speakerphone should be automatically turned on for an outgoing call.
*/
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index 989c2cd..a97e7e4 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -16,9 +16,7 @@
package android.telecomm;
-import android.content.ComponentName;
import android.content.Context;
-import android.os.RemoteException;
import com.android.internal.telecomm.ITelecommService;
@@ -47,14 +45,4 @@
public static TelecommManager from(Context context) {
return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
}
-
- /** {@hide} */
- public ComponentName getSystemPhoneApplication() {
- try {
- return mService.getSystemPhoneApplication();
- } catch (RemoteException e) {
- Log.e(TAG, e, "Error calling ITelecommService#getSystemPhoneApplication");
- return null;
- }
- }
}
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index dc2b869..c758c6d 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -17,7 +17,6 @@
package com.android.internal.telecomm;
import android.telecomm.Subscription;
-import android.content.ComponentName;
/**
* Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing
@@ -43,11 +42,6 @@
void showCallScreen(boolean showDialpad);
/**
- * Returns the component name of the phone application installed on the system partition.
- */
- ComponentName getSystemPhoneApplication();
-
- /**
* Gets a list of Subscriptions.
*/
List<Subscription> getSubscriptions();
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 777471d..59b48e4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -373,19 +373,19 @@
public static int GOOD_RSSI_24 = -65;
/** @hide **/
- public static int LOW_RSSI_24 = -75;
+ public static int LOW_RSSI_24 = -77;
/** @hide **/
public static int BAD_RSSI_24 = -87;
/** @hide **/
- public static int GOOD_RSSI_5 = -55;
+ public static int GOOD_RSSI_5 = -60;
/** @hide **/
- public static int LOW_RSSI_5 = -65;
+ public static int LOW_RSSI_5 = -72;
/** @hide **/
- public static int BAD_RSSI_5 = -75;
+ public static int BAD_RSSI_5 = -82;
/** @hide **/
public static int UNWANTED_BLACKLIST_SOFT_BUMP = 4;
@@ -419,6 +419,15 @@
* 5GHz band is penalized if the 5GHz RSSI is lower than this threshold **/
public static int G_BAND_PREFERENCE_RSSI_THRESHOLD = -75;
+ /** @hide
+ * Boost given to RSSI on a home network for the purpose of calculating the score
+ * This adds stickiness to home networks, as defined by:
+ * - less than 4 known BSSIDs
+ * - PSK only
+ * - TODO: add a test to verify that all BSSIDs are behind same gateway
+ ***/
+ public static int HOME_NETWORK_RSSI_BOOST = 5;
+
/**
* @hide
* A summary of the RSSI and Band status for that configuration