summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt36
-rw-r--r--api/system-current.txt37
-rw-r--r--core/java/android/app/ActivityManager.java30
-rw-r--r--core/java/android/app/AlarmManager.java63
-rw-r--r--core/java/android/app/IAlarmManager.aidl2
-rw-r--r--core/java/android/app/VoiceInteractor.java2
-rw-r--r--core/java/android/app/backup/BackupManager.java26
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl13
-rw-r--r--core/java/android/os/BatteryManager.java39
-rw-r--r--core/java/android/os/BatteryStats.java2
-rw-r--r--core/java/android/transition/TransitionManager.java1
-rw-r--r--core/java/android/view/InputDevice.java39
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java2
-rw-r--r--core/java/android/widget/Editor.java6
-rw-r--r--core/java/android/widget/FastScroller.java142
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl3
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java2
-rw-r--r--core/java/com/android/internal/logging/MetricsConstants.java18
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java18
-rw-r--r--core/java/com/android/internal/midi/EventScheduler.java11
-rw-r--r--core/java/com/android/internal/midi/MidiConstants.java14
-rw-r--r--core/java/com/android/internal/midi/MidiDispatcher.java7
-rw-r--r--core/java/com/android/internal/midi/MidiEventScheduler.java37
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java76
-rw-r--r--core/java/com/android/internal/os/InstallerConnection.java30
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java11
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java1045
-rw-r--r--core/jni/android_view_InputDevice.cpp16
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/drawable/fastscroll_label_left_material.xml26
-rw-r--r--core/res/res/drawable/fastscroll_label_right_material.xml26
-rw-r--r--core/res/res/layout/floating_popup_close_overflow_button.xml24
-rw-r--r--core/res/res/layout/floating_popup_open_overflow_button.xml2
-rw-r--r--core/res/res/layout/floating_popup_overflow_list_item33
-rw-r--r--core/res/res/values/attrs.xml14
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/styles_material.xml3
-rwxr-xr-xcore/res/res/values/symbols.xml4
-rw-r--r--docs/html/google/play-services/setup.jd50
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java6
-rw-r--r--include/androidfw/ResourceTypes.h2
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java21
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java1
-rw-r--r--keystore/java/android/security/KeyGeneratorSpec.java64
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java81
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java139
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java23
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java82
-rw-r--r--libs/hwui/AmbientShadow.cpp15
-rw-r--r--libs/hwui/ProgramCache.cpp5
-rw-r--r--libs/hwui/SpotShadow.cpp11
-rw-r--r--media/java/android/media/MediaCodec.java32
-rw-r--r--media/java/android/media/MediaDrm.java218
-rw-r--r--media/java/android/media/midi/IMidiDeviceServer.aidl3
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java9
-rw-r--r--media/java/android/media/midi/MidiInputPort.java13
-rw-r--r--media/java/android/media/midi/MidiManager.java92
-rw-r--r--media/java/android/media/midi/MidiOutputPort.java24
-rw-r--r--media/java/android/media/midi/MidiPortImpl.java70
-rw-r--r--media/java/android/media/midi/MidiReceiver.java7
-rw-r--r--media/jni/android_media_MediaCodec.cpp8
-rw-r--r--media/jni/android_media_MediaDrm.cpp33
-rw-r--r--media/packages/BluetoothMidiService/Android.mk11
-rw-r--r--media/packages/BluetoothMidiService/AndroidManifest.xml17
-rw-r--r--media/packages/BluetoothMidiService/res/values/strings.xml19
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java276
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java61
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java106
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java157
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java33
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java41
-rw-r--r--packages/StatementService/Android.mk2
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java5
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java6
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java312
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java30
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java63
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java16
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java27
-rw-r--r--services/core/java/com/android/server/job/controllers/BatteryController.java54
-rw-r--r--services/core/java/com/android/server/pm/Installer.java135
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java91
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java24
-rw-r--r--services/core/java/com/android/server/power/DeviceIdleController.java11
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java8
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java3
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java66
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java4
-rw-r--r--tests/Compatibility/Android.mk2
-rw-r--r--tests/Compatibility/AndroidManifest.xml2
96 files changed, 3627 insertions, 859 deletions
diff --git a/api/current.txt b/api/current.txt
index ccdac7e04d77..a5fac8365664 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1301,6 +1301,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -14992,6 +14993,7 @@ package android.media {
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -15007,6 +15009,7 @@ package android.media {
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -15327,6 +15330,8 @@ package android.media {
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
field public static final int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -15334,6 +15339,11 @@ package android.media {
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -15360,6 +15370,11 @@ package android.media {
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -15368,6 +15383,14 @@ package android.media {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -16815,11 +16838,17 @@ package android.media.midi {
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -16841,6 +16870,7 @@ package android.media.midi {
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -22246,6 +22276,9 @@ package android.os {
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -32506,6 +32539,7 @@ package android.transition {
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -33524,10 +33558,10 @@ package android.view {
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 7dad2bf12cbd..4f4762ec9c17 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1378,6 +1378,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -5914,6 +5915,7 @@ package android.app.backup {
method public android.app.backup.RestoreSession beginRestoreSession();
method public void dataChanged();
method public static void dataChanged(java.lang.String);
+ method public long getAvailableRestoreToken(java.lang.String);
method public java.lang.String getCurrentTransport();
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
@@ -16200,6 +16202,7 @@ package android.media {
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -16215,6 +16218,7 @@ package android.media {
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -16535,6 +16539,8 @@ package android.media {
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
method public void unprovisionDevice();
@@ -16543,6 +16549,11 @@ package android.media {
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -16569,6 +16580,11 @@ package android.media {
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -16577,6 +16593,14 @@ package android.media {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -18090,11 +18114,17 @@ package android.media.midi {
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -18116,6 +18146,7 @@ package android.media.midi {
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -24117,6 +24148,9 @@ package android.os {
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -35110,6 +35144,7 @@ package android.transition {
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -36128,10 +36163,10 @@ package android.view {
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8f125d7ba9fa..2b35cd44f874 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -269,45 +269,51 @@ public class ActivityManager {
* all activities that are visible to the user. */
public static final int PROCESS_STATE_TOP = 2;
+ /** @hide Process is hosting a foreground service. */
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 5;
+ public static final int PROCESS_STATE_BACKUP = 7;
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 6;
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 8;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 7;
+ public static final int PROCESS_STATE_SERVICE = 9;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 8;
+ public static final int PROCESS_STATE_RECEIVER = 10;
/** @hide Process is in the background but hosts the home activity. */
- public static final int PROCESS_STATE_HOME = 9;
+ public static final int PROCESS_STATE_HOME = 11;
/** @hide Process is in the background but hosts the last shown activity. */
- public static final int PROCESS_STATE_LAST_ACTIVITY = 10;
+ public static final int PROCESS_STATE_LAST_ACTIVITY = 12;
/** @hide Process is being cached for later use and contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY = 11;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY = 13;
/** @hide Process is being cached for later use and is a client of another cached
* process that contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 14;
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 15;
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
@@ -2064,7 +2070,7 @@ public class ActivityManager {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
} else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
- } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ } else if (procState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
} else {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 5dd02ae515d2..179957d7aac5 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -115,6 +115,40 @@ public class AlarmManager
/** @hide */
public static final long WINDOW_HEURISTIC = -1;
+ /**
+ * Flag for alarms: this is to be a stand-alone alarm, that should not be batched with
+ * other alarms.
+ * @hide
+ */
+ public static final int FLAG_STANDALONE = 1<<0;
+
+ /**
+ * Flag for alarms: this alarm would like to wake the device even if it is idle. This
+ * is, for example, an alarm for an alarm clock.
+ * @hide
+ */
+ public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
+
+ /**
+ * Flag for alarms: this alarm would like to still execute even if the device is
+ * idle. This won't bring the device out of idle, just allow this specific alarm to
+ * run. Note that this means the actual time this alarm goes off can be inconsistent
+ * with the time of non-allow-while-idle alarms (it could go earlier than the time
+ * requested by another alarm).
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
+
+ /**
+ * Flag for alarms: this alarm marks the point where we would like to come out of idle
+ * mode. It may be moved by the alarm manager to match the first wake-from-idle alarm.
+ * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
+ * avoids scheduling any further alarms until the marker alarm is executed.
+ * @hide
+ */
+ public static final int FLAG_IDLE_UNTIL = 1<<3;
+
private final IAlarmManager mService;
private final boolean mAlwaysExact;
@@ -204,7 +238,7 @@ public class AlarmManager
* @see #RTC_WAKEUP
*/
public void set(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
}
/**
@@ -265,7 +299,8 @@ public class AlarmManager
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
+ null);
}
/**
@@ -315,7 +350,7 @@ public class AlarmManager
*/
public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
- setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null, null);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
}
/**
@@ -353,7 +388,16 @@ public class AlarmManager
* @see #RTC_WAKEUP
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+ }
+
+ /**
+ * Schedule an idle-until alarm, which will keep the alarm manager idle until
+ * the given time.
+ * @hide
+ */
+ public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
}
/**
@@ -381,18 +425,19 @@ public class AlarmManager
* @see android.content.Intent#filterEquals
*/
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, operation, null, info);
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
}
/** @hide */
@SystemApi
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
+ null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
- PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+ int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -405,7 +450,7 @@ public class AlarmManager
}
try {
- mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
+ mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
workSource, alarmClock);
} catch (RemoteException ex) {
}
@@ -506,7 +551,7 @@ public class AlarmManager
*/
public void setInexactRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
}
/**
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 194082e2d9f4..d5719f54b909 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -28,7 +28,7 @@ import android.os.WorkSource;
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
void set(int type, long triggerAtTime, long windowLength,
- long interval, in PendingIntent operation, in WorkSource workSource,
+ long interval, int flags, in PendingIntent operation, in WorkSource workSource,
in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 7acf5f07edf6..022a62c58ca0 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -318,7 +318,7 @@ public class VoiceInteractor {
* @param label The label that will both be matched against what the user speaks
* and displayed visually.
* @param index The location of this option within the overall set of options.
- * Can be used to help identify which the option when it is returned from the
+ * Can be used to help identify the option when it is returned from the
* voice interactor.
*/
public Option(CharSequence label, int index) {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 9151a162aeed..8b7930590126 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -339,4 +339,30 @@ public class BackupManager {
}
}
}
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public long getAvailableRestoreToken(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ return sService.getAvailableRestoreToken(packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAvailableRestoreToken() couldn't connect");
+ }
+ }
+ return 0;
+ }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 8f36dc473ff1..87e4ef120730 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -313,4 +313,17 @@ interface IBackupManager {
* is being queried.
*/
boolean isBackupServiceActive(int whichUser);
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ */
+ long getAvailableRestoreToken(String packageName);
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index bd5a39229bd0..cccc4be5ae29 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,10 +16,12 @@
package android.os;
+import android.content.Context;
import android.os.BatteryProperty;
import android.os.IBatteryPropertiesRegistrar;
import android.os.RemoteException;
import android.os.ServiceManager;
+import com.android.internal.app.IBatteryStats;
/**
* The BatteryManager class contains strings and constants used for values
@@ -128,6 +130,26 @@ public class BatteryManager {
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+ /**
+ * Sent when the device's battery has started charging (or has reached full charge
+ * and the device is on power). This is a good time to do work that you would like to
+ * avoid doing while on battery (that is to avoid draining the user's battery due to
+ * things they don't care enough about).
+ *
+ * This is paired with {@link #ACTION_DISCHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_CHARGING = "android.os.action.CHARGING";
+
+ /**
+ * Sent when the device's battery may be discharging, so apps should avoid doing
+ * extraneous work that would cause it to discharge faster.
+ *
+ * This is paired with {@link #ACTION_CHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+
/*
* Battery property identifiers. These must match the values in
* frameworks/native/include/batteryservice/BatteryService.h
@@ -162,17 +184,34 @@ public class BatteryManager {
*/
public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5;
+ private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
/**
* @removed Was previously made visible by accident.
*/
public BatteryManager() {
+ mBatteryStats = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
ServiceManager.getService("batteryproperties"));
}
/**
+ * Return true if the battery is currently considered to be charging. This means that
+ * the device is plugged in and is supplying sufficient power that the battery level is
+ * going up (or the battery is fully charged). Changes in this state are matched by
+ * broadcasts of {@link #ACTION_CHARGING} and {@link #ACTION_DISCHARGING}.
+ */
+ public boolean isCharging() {
+ try {
+ return mBatteryStats.isCharging();
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /**
* Query a battery property from the batteryproperties service.
*
* Returns the requested value, or Long.MIN_VALUE if property not
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 30519267b266..156698528375 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1065,6 +1065,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATE_SCREEN_ON_FLAG = 1<<20;
public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+ public static final int STATE_CHARGING_FLAG = 1<<17;
public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
@@ -1751,6 +1752,7 @@ public abstract class BatteryStats implements Parcelable {
new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
+ new BitDescription(HistoryItem.STATE_CHARGING_FLAG, "charging", "ch"),
new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 0b70fdbe5a4f..5209f90cab68 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -430,7 +430,6 @@ public class TransitionManager {
* Ends all pending and ongoing transitions on the specified scene root.
*
* @param sceneRoot The root of the View hierarchy to end transitions on.
- * @hide
*/
public static void endTransitions(final ViewGroup sceneRoot) {
sPendingTransitions.remove(sceneRoot);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 2eac5495a9d8..1ee478023223 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -49,7 +49,6 @@ public final class InputDevice implements Parcelable {
private final String mName;
private final int mVendorId;
private final int mProductId;
- private final String mUniqueId;
private final String mDescriptor;
private final InputDeviceIdentifier mIdentifier;
private final boolean mIsExternal;
@@ -57,6 +56,7 @@ public final class InputDevice implements Parcelable {
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
private final boolean mHasVibrator;
+ private final boolean mHasMic;
private final boolean mHasButtonUnderPad;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@@ -357,8 +357,8 @@ public final class InputDevice implements Parcelable {
// Called by native code.
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
- int productId, String uniqueId, String descriptor, boolean isExternal, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator,
+ int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
+ KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMic,
boolean hasButtonUnderPad) {
mId = id;
mGeneration = generation;
@@ -366,13 +366,13 @@ public final class InputDevice implements Parcelable {
mName = name;
mVendorId = vendorId;
mProductId = productId;
- mUniqueId = uniqueId;
mDescriptor = descriptor;
mIsExternal = isExternal;
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
mHasVibrator = hasVibrator;
+ mHasMic = hasMic;
mHasButtonUnderPad = hasButtonUnderPad;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
}
@@ -384,13 +384,13 @@ public final class InputDevice implements Parcelable {
mName = in.readString();
mVendorId = in.readInt();
mProductId = in.readInt();
- mUniqueId = in.readString();
mDescriptor = in.readString();
mIsExternal = in.readInt() != 0;
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
mHasVibrator = in.readInt() != 0;
+ mHasMic = in.readInt() != 0;
mHasButtonUnderPad = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
@@ -509,23 +509,6 @@ public final class InputDevice implements Parcelable {
}
/**
- * Gets the vendor's unique id for the given device, if available.
- * <p>
- * A vendor may assign a unique id to a device (e.g., MAC address for
- * Bluetooth devices). A null value will be assigned where a unique id is
- * not available.
- * </p><p>
- * This method is dependent on the vendor, whereas {@link #getDescriptor}
- * attempts to create a unique id even when the vendor has not provided one.
- * </p>
- *
- * @return The unique id of a given device
- */
- public String getUniqueId() {
- return mUniqueId;
- }
-
- /**
* Gets the input device descriptor, which is a stable identifier for an input device.
* <p>
* An input device descriptor uniquely identifies an input device. Its value
@@ -737,6 +720,14 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Reports whether the device has a built-in microphone.
+ * @return Whether the device has a built-in microphone.
+ */
+ public boolean hasMic() {
+ return mHasMic;
+ }
+
+ /**
* Reports whether the device has a button under its touchpad
* @return Whether the device has a button under its touchpad
* @hide
@@ -864,13 +855,13 @@ public final class InputDevice implements Parcelable {
out.writeString(mName);
out.writeInt(mVendorId);
out.writeInt(mProductId);
- out.writeString(mUniqueId);
out.writeString(mDescriptor);
out.writeInt(mIsExternal ? 1 : 0);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
out.writeInt(mHasVibrator ? 1 : 0);
+ out.writeInt(mHasMic ? 1 : 0);
out.writeInt(mHasButtonUnderPad ? 1 : 0);
final int numRanges = mMotionRanges.size();
@@ -916,6 +907,8 @@ public final class InputDevice implements Parcelable {
description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+ description.append(" Has mic: ").append(mHasMic).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 36bce0bdbdee..f951dc2d0629 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -397,7 +397,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
- setupItemAnimations();
+// setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 32b99a809c41..8f4e8e11131b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1671,7 +1671,8 @@ public class Editor {
return false;
}
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
return mSelectionActionMode != null;
}
@@ -1704,7 +1705,8 @@ public class Editor {
// immediately hide the newly created action bar and would be visually distracting.
if (!willExtract) {
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
}
final boolean selectionStarted = mSelectionActionMode != null || willExtract;
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 21213acadd6d..4b5407a8a5dc 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -46,6 +46,8 @@ import android.view.ViewGroupOverlay;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView.ScaleType;
+import com.android.internal.R;
+
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
*/
@@ -82,6 +84,10 @@ class FastScroller {
private static final int OVERLAY_AT_THUMB = 1;
private static final int OVERLAY_ABOVE_THUMB = 2;
+ // Positions for thumb in relation to track.
+ private static final int THUMB_POSITION_MIDPOINT = 0;
+ private static final int THUMB_POSITION_INSIDE = 1;
+
// Indices for mPreviewResId.
private static final int PREVIEW_LEFT = 0;
private static final int PREVIEW_RIGHT = 1;
@@ -100,7 +106,6 @@ class FastScroller {
private final ImageView mThumbImage;
private final ImageView mTrackImage;
private final View mPreviewImage;
-
/**
* Preview image resource IDs for left- and right-aligned layouts. See
* {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
@@ -130,6 +135,11 @@ class FastScroller {
private Drawable mThumbDrawable;
private Drawable mTrackDrawable;
private int mTextAppearance;
+ private int mThumbPosition;
+
+ // Used to convert between y-coordinate and thumb position within track.
+ private float mThumbOffset;
+ private float mThumbRange;
/** Total width of decorations. */
private int mWidth;
@@ -278,7 +288,6 @@ class FastScroller {
}
private void updateAppearance() {
- final Context context = mList.getContext();
int width = 0;
// Add track to overlay if it has an image.
@@ -298,12 +307,9 @@ class FastScroller {
// Account for minimum thumb width.
mWidth = Math.max(width, mThumbMinWidth);
- mPreviewImage.setMinimumWidth(mPreviewMinWidth);
- mPreviewImage.setMinimumHeight(mPreviewMinHeight);
-
if (mTextAppearance != 0) {
- mPrimaryText.setTextAppearance(context, mTextAppearance);
- mSecondaryText.setTextAppearance(context, mTextAppearance);
+ mPrimaryText.setTextAppearance(mTextAppearance);
+ mSecondaryText.setTextAppearance(mTextAppearance);
}
if (mTextColor != null) {
@@ -316,13 +322,11 @@ class FastScroller {
mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
- final int textMinSize = Math.max(0, mPreviewMinHeight);
- mPrimaryText.setMinimumWidth(textMinSize);
- mPrimaryText.setMinimumHeight(textMinSize);
+ final int padding = mPreviewPadding;
mPrimaryText.setIncludeFontPadding(false);
- mSecondaryText.setMinimumWidth(textMinSize);
- mSecondaryText.setMinimumHeight(textMinSize);
+ mPrimaryText.setPadding(padding, padding, padding, padding);
mSecondaryText.setIncludeFontPadding(false);
+ mSecondaryText.setPadding(padding, padding, padding, padding);
refreshDrawablePressedState();
}
@@ -330,50 +334,53 @@ class FastScroller {
public void setStyle(@StyleRes int resId) {
final Context context = mList.getContext();
final TypedArray ta = context.obtainStyledAttributes(null,
- com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
+ R.styleable.FastScroll, R.attr.fastScrollStyle, resId);
final int N = ta.getIndexCount();
for (int i = 0; i < N; i++) {
final int index = ta.getIndex(i);
switch (index) {
- case com.android.internal.R.styleable.FastScroll_position:
+ case R.styleable.FastScroll_position:
mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundLeft:
+ case R.styleable.FastScroll_backgroundLeft:
mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundRight:
+ case R.styleable.FastScroll_backgroundRight:
mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbDrawable:
+ case R.styleable.FastScroll_thumbDrawable:
mThumbDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_trackDrawable:
+ case R.styleable.FastScroll_trackDrawable:
mTrackDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_textAppearance:
+ case R.styleable.FastScroll_textAppearance:
mTextAppearance = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_textColor:
+ case R.styleable.FastScroll_textColor:
mTextColor = ta.getColorStateList(index);
break;
- case com.android.internal.R.styleable.FastScroll_textSize:
+ case R.styleable.FastScroll_textSize:
mTextSize = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minWidth:
+ case R.styleable.FastScroll_minWidth:
mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minHeight:
+ case R.styleable.FastScroll_minHeight:
mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinWidth:
+ case R.styleable.FastScroll_thumbMinWidth:
mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinHeight:
+ case R.styleable.FastScroll_thumbMinHeight:
mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_padding:
+ case R.styleable.FastScroll_padding:
mPreviewPadding = ta.getDimensionPixelSize(index, 0);
break;
+ case R.styleable.FastScroll_thumbPosition:
+ mThumbPosition = ta.getInt(index, THUMB_POSITION_MIDPOINT);
+ break;
}
}
@@ -478,14 +485,16 @@ class FastScroller {
final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
mPreviewImage.setBackgroundResource(previewResId);
- // Add extra padding for text.
- final Drawable background = mPreviewImage.getBackground();
- if (background != null) {
- final Rect padding = mTempBounds;
- background.getPadding(padding);
- padding.offset(mPreviewPadding, mPreviewPadding);
- mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
+ // Propagate padding to text min width/height.
+ final int textMinWidth = Math.max(0, mPreviewMinWidth - mPreviewImage.getPaddingLeft()
+ - mPreviewImage.getPaddingRight());
+ mPrimaryText.setMinimumWidth(textMinWidth);
+ mSecondaryText.setMinimumWidth(textMinWidth);
+
+ final int textMinHeight = Math.max(0, mPreviewMinHeight - mPreviewImage.getPaddingTop()
+ - mPreviewImage.getPaddingBottom());
+ mPrimaryText.setMinimumHeight(textMinHeight);
+ mSecondaryText.setMinimumHeight(textMinHeight);
// Requires re-layout.
updateLayout();
@@ -560,6 +569,8 @@ class FastScroller {
layoutThumb();
layoutTrack();
+ updateOffsetAndRange();
+
final Rect bounds = mTempBounds;
measurePreview(mPrimaryText, bounds);
applyLayout(mPrimaryText, bounds);
@@ -758,15 +769,45 @@ class FastScroller {
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
track.measure(widthMeasureSpec, heightMeasureSpec);
+ final int top;
+ final int bottom;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ top = container.top;
+ bottom = container.bottom;
+ } else {
+ final int thumbHalfHeight = thumb.getHeight() / 2;
+ top = container.top + thumbHalfHeight;
+ bottom = container.bottom - thumbHalfHeight;
+ }
+
final int trackWidth = track.getMeasuredWidth();
- final int thumbHalfHeight = thumb.getHeight() / 2;
final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
final int right = left + trackWidth;
- final int top = container.top + thumbHalfHeight;
- final int bottom = container.bottom - thumbHalfHeight;
track.layout(left, top, right, bottom);
}
+ /**
+ * Updates the offset and range used to convert from absolute y-position to
+ * thumb position within the track.
+ */
+ private void updateOffsetAndRange() {
+ final View trackImage = mTrackImage;
+ final View thumbImage = mThumbImage;
+ final float min;
+ final float max;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ final float halfThumbHeight = thumbImage.getHeight() / 2f;
+ min = trackImage.getTop() + halfThumbHeight;
+ max = trackImage.getBottom() - halfThumbHeight;
+ } else{
+ min = trackImage.getTop();
+ max = trackImage.getBottom();
+ }
+
+ mThumbOffset = min;
+ mThumbRange = max - min;
+ }
+
private void setState(int state) {
mList.removeCallbacks(mDeferHide);
@@ -1145,18 +1186,8 @@ class FastScroller {
* to place the thumb.
*/
private void setThumbPos(float position) {
- final Rect container = mContainerRect;
- final int top = container.top;
- final int bottom = container.bottom;
-
- final View trackImage = mTrackImage;
- final View thumbImage = mThumbImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
- final float thumbMiddle = position * range + offset;
- thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
+ final float thumbMiddle = position * mThumbRange + mThumbOffset;
+ mThumbImage.setTranslationY(thumbMiddle - mThumbImage.getHeight() / 2f);
final View previewImage = mPreviewImage;
final float previewHalfHeight = previewImage.getHeight() / 2f;
@@ -1175,6 +1206,9 @@ class FastScroller {
}
// Center the preview on the thumb, constrained to the list bounds.
+ final Rect container = mContainerRect;
+ final int top = container.top;
+ final int bottom = container.bottom;
final float minP = top + previewHalfHeight;
final float maxP = bottom - previewHalfHeight;
final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
@@ -1186,19 +1220,13 @@ class FastScroller {
}
private float getPosFromMotionEvent(float y) {
- final View trackImage = mTrackImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
-
// If the list is the same height as the thumbnail or shorter,
// effectively disable scrolling.
- if (range <= 0) {
+ if (mThumbRange <= 0) {
return 0f;
}
- return MathUtils.constrain((y - offset) / range, 0f, 1f);
+ return MathUtils.constrain((y - mThumbOffset) / mThumbRange, 0f, 1f);
}
/**
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1746bedae61f..4f0e29ebed39 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -40,6 +40,9 @@ interface IBatteryStats {
ParcelFileDescriptor getStatisticsStream();
+ // Return true if we see the battery as currently charging.
+ boolean isCharging();
+
// Return the computed amount of time remaining on battery, in milliseconds.
// Returns -1 if nothing could be computed.
long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 75beee9572f8..fe79effc6ab7 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -140,6 +140,8 @@ public final class ProcessStats implements Parcelable {
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
+ STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index e5cba8415110..ee225a192a24 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -32,9 +32,16 @@ public interface MetricsConstants {
public static final int ACCOUNTS_ACCOUNT_SYNC = 9;
public static final int ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10;
public static final int ACCOUNTS_MANAGE_ACCOUNTS = 11;
+ public static final int ACTION_WIFI_ADD_NETWORK = 134;
+ public static final int ACTION_WIFI_CONNECT = 135;
+ public static final int ACTION_WIFI_FORCE_SCAN = 136;
+ public static final int ACTION_WIFI_FORGET = 137;
+ public static final int ACTION_WIFI_OFF = 138;
+ public static final int ACTION_WIFI_ON = 139;
public static final int APN = 12;
public static final int APN_EDITOR = 13;
public static final int APPLICATION = 16;
+ public static final int APPLICATIONS_ADVANCED = 130;
public static final int APPLICATIONS_APP_LAUNCH = 17;
public static final int APPLICATIONS_APP_PERMISSION = 18;
public static final int APPLICATIONS_APP_STORAGE = 19;
@@ -85,8 +92,13 @@ public interface MetricsConstants {
public static final int INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62;
public static final int LOCATION = 63;
public static final int LOCATION_MODE = 64;
+ public static final int LOCATION_SCANNING = 131;
public static final int MAIN_SETTINGS = 1;
public static final int MANAGE_APPLICATIONS = 65;
+ public static final int MANAGE_APPLICATIONS_ALL = 132;
+ public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 133;
+ public static final int MANAGE_DOMAIN_URLS = 143;
+ public static final int MANAGE_PERMISSIONS = 140;
public static final int MASTER_CLEAR = 66;
public static final int MASTER_CLEAR_CONFIRM = 67;
public static final int NET_DATA_USAGE_METERED = 68;
@@ -94,10 +106,15 @@ public interface MetricsConstants {
public static final int NFC_PAYMENT = 70;
public static final int NOTIFICATION = 71;
public static final int NOTIFICATION_APP_NOTIFICATION = 72;
+ public static final int NOTIFICATION_ITEM = 128;
+ public static final int NOTIFICATION_ITEM_ACTION = 129;
public static final int NOTIFICATION_OTHER_SOUND = 73;
+ public static final int NOTIFICATION_PANEL = 127;
public static final int NOTIFICATION_REDACTION = 74;
public static final int NOTIFICATION_STATION = 75;
public static final int NOTIFICATION_ZEN_MODE = 76;
+ public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 142;
+ public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 141;
public static final int OWNER_INFO = 77;
public static final int PRINT_JOB_SETTINGS = 78;
public static final int PRINT_SERVICE_SETTINGS = 79;
@@ -132,7 +149,6 @@ public interface MetricsConstants {
public static final int TRUST_AGENT = 91;
public static final int TTS_ENGINE_SETTINGS = 93;
public static final int TTS_TEXT_TO_SPEECH = 94;
- public static final int TYPE_UNKNOWN = 0;
public static final int USAGE_ACCESS = 95;
public static final int USER = 96;
public static final int USERS_APP_RESTRICTIONS = 97;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 103854320997..0d5db775cac3 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -26,23 +26,7 @@ import android.os.Build;
*/
public class MetricsLogger implements MetricsConstants {
// These constants are temporary, they should migrate to MetricsConstants.
- public static final int APPLICATIONS_ADVANCED = 132;
- public static final int LOCATION_SCANNING = 133;
- public static final int MANAGE_APPLICATIONS_ALL = 134;
- public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135;
-
- public static final int ACTION_WIFI_ADD_NETWORK = 136;
- public static final int ACTION_WIFI_CONNECT = 137;
- public static final int ACTION_WIFI_FORCE_SCAN = 138;
- public static final int ACTION_WIFI_FORGET = 139;
- public static final int ACTION_WIFI_OFF = 140;
- public static final int ACTION_WIFI_ON = 141;
-
- public static final int MANAGE_PERMISSIONS = 142;
- public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 143;
- public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 144;
-
- public static final int MANAGE_DOMAIN_URLS = 143;
+ // next value is 144;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7b9a48c9e592..506902f61fd6 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,6 +16,7 @@
package com.android.internal.midi;
+import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -28,7 +29,7 @@ public class EventScheduler {
private static final long NANOS_PER_MILLI = 1000000;
private final Object mLock = new Object();
- private SortedMap<Long, FastEventQueue> mEventBuffer;
+ volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
private FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
private boolean mClosed;
@@ -68,6 +69,7 @@ public class EventScheduler {
mEventsRemoved++;
SchedulableEvent event = mFirst;
mFirst = event.mNext;
+ event.mNext = null;
return event;
}
@@ -87,7 +89,7 @@ public class EventScheduler {
*/
public static class SchedulableEvent {
private long mTimestamp;
- private SchedulableEvent mNext = null;
+ volatile private SchedulableEvent mNext = null;
/**
* @param timestamp
@@ -235,6 +237,11 @@ public class EventScheduler {
return event;
}
+ protected void flush() {
+ // Replace our event buffer with a fresh empty one
+ mEventBuffer = new TreeMap<Long, FastEventQueue>();
+ }
+
public void close() {
synchronized (mLock) {
mClosed = true;
diff --git a/core/java/com/android/internal/midi/MidiConstants.java b/core/java/com/android/internal/midi/MidiConstants.java
index 87552e499ea0..f78f75a22bdf 100644
--- a/core/java/com/android/internal/midi/MidiConstants.java
+++ b/core/java/com/android/internal/midi/MidiConstants.java
@@ -19,7 +19,7 @@ package com.android.internal.midi;
/**
* MIDI related constants and static methods.
*/
-public class MidiConstants {
+public final class MidiConstants {
public static final byte STATUS_COMMAND_MASK = (byte) 0xF0;
public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F;
@@ -85,4 +85,16 @@ public class MidiConstants {
}
return (goodBytes == 0);
}
+
+ // Returns true if this command can be used for running status
+ public static boolean allowRunningStatus(int command) {
+ // only Channel Voice and Channel Mode commands can use running status
+ return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
+ }
+
+ // Returns true if this command cancels running status
+ public static boolean cancelsRunningStatus(int command) {
+ // System Common messages cancel running status
+ return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 377bc688f629..70e699a70402 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -83,4 +83,11 @@ public final class MidiDispatcher extends MidiReceiver {
}
}
}
+
+ @Override
+ public void flush() throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ receiver.flush();
+ }
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 42d70f6a0d24..4dc5838c5d41 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,16 +28,9 @@ public class MidiEventScheduler extends EventScheduler {
// Maintain a pool of scheduled events to reduce memory allocation.
// This pool increases performance by about 14%.
private final static int POOL_EVENT_SIZE = 16;
-
- private final MidiReceiver[] mReceivers;
+ private MidiReceiver mReceiver = new SchedulingReceiver();
private class SchedulingReceiver extends MidiReceiver {
- private final int mPortNumber;
-
- public SchedulingReceiver(int portNumber) {
- mPortNumber = portNumber;
- }
-
/**
* Store these bytes in the EventScheduler to be delivered at the specified
* time.
@@ -47,14 +40,17 @@ public class MidiEventScheduler extends EventScheduler {
throws IOException {
MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
if (event != null) {
- event.portNumber = mPortNumber;
add(event);
}
}
+
+ @Override
+ public void flush() {
+ MidiEventScheduler.this.flush();
+ }
}
public static class MidiEvent extends SchedulableEvent {
- public int portNumber;
public int count = 0;
public byte[] data;
@@ -80,17 +76,6 @@ public class MidiEventScheduler extends EventScheduler {
}
}
- public MidiEventScheduler() {
- this(0);
- }
-
- public MidiEventScheduler(int portCount) {
- mReceivers = new MidiReceiver[portCount];
- for (int i = 0; i < portCount; i++) {
- mReceivers[i] = new SchedulingReceiver(i);
- }
- }
-
/**
* Create an event that contains the message.
*/
@@ -132,15 +117,7 @@ public class MidiEventScheduler extends EventScheduler {
* @return the MidiReceiver
*/
public MidiReceiver getReceiver() {
- return mReceivers[0];
- }
-
- /**
- * This MidiReceiver will write date to the scheduling buffer.
- * @return the MidiReceiver
- */
- public MidiReceiver getReceiver(int portNumber) {
- return mReceivers[portNumber];
+ return mReceiver;
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 793d0d3655cf..c5c0ba6a3542 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -22,6 +22,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -127,6 +128,7 @@ public final class BatteryStatsImpl extends BatteryStats {
static final int MSG_UPDATE_WAKELOCKS = 1;
static final int MSG_REPORT_POWER_CHANGE = 2;
+ static final int MSG_REPORT_CHARGING = 3;
static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
@@ -135,6 +137,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public interface BatteryCallback {
public void batteryNeedsCpuUpdate();
public void batteryPowerChanged(boolean onBattery);
+ public void batterySendBroadcast(Intent intent);
}
final class MyHandler extends Handler {
@@ -156,6 +159,18 @@ public final class BatteryStatsImpl extends BatteryStats {
cb.batteryPowerChanged(msg.arg1 != 0);
}
break;
+ case MSG_REPORT_CHARGING:
+ if (cb != null) {
+ final String action;
+ synchronized (BatteryStatsImpl.this) {
+ action = mCharging ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING;
+ }
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ cb.batterySendBroadcast(intent);
+ }
+ break;
}
}
}
@@ -393,6 +408,12 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mOnBattery;
boolean mOnBatteryInternal;
+ /**
+ * External reporting of whether the device is actually charging.
+ */
+ boolean mCharging = true;
+ int mLastChargingStateLevel;
+
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
@@ -7243,6 +7264,10 @@ public final class BatteryStatsImpl extends BatteryStats {
return mOnBattery;
}
+ public boolean isCharging() {
+ return mCharging;
+ }
+
public boolean isScreenOn() {
return mScreenState == Display.STATE_ON;
}
@@ -7802,6 +7827,20 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ boolean setChargingLocked(boolean charging) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ if (charging) {
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_CHARGING_FLAG;
+ }
+ mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
+ return true;
+ }
+ return false;
+ }
+
void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
final int oldStatus, final int level) {
boolean doWrite = false;
@@ -7861,6 +7900,10 @@ public final class BatteryStatsImpl extends BatteryStats {
reset = true;
mDischargeStepTracker.init();
}
+ if (mCharging) {
+ setChargingLocked(false);
+ }
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = true;
mLastDischargeStepLevel = level;
mMinDischargeStepLevel = level;
@@ -7890,6 +7933,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOff = 0;
updateTimeBasesLocked(true, !screenOn, uptime, realtime);
} else {
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -7982,10 +8026,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
}
}
+ // Always start out assuming charging, that will be updated later.
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
mHistoryCur.batteryStatus = (byte)status;
mHistoryCur.batteryLevel = (byte)level;
mMaxChargeStepLevel = mMinDischargeStepLevel =
mLastChargeStepLevel = mLastDischargeStepLevel = level;
+ mLastChargingStateLevel = level;
} else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
}
@@ -8046,13 +8093,11 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.batteryVoltage = (char)volt;
changed = true;
}
- if (changed) {
- addHistoryRecordLocked(elapsedRealtime, uptime);
- }
long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
if (onBattery) {
+ changed |= setChargingLocked(false);
if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
modeBits, elapsedRealtime);
@@ -8064,6 +8109,28 @@ public final class BatteryStatsImpl extends BatteryStats {
mModStepMode = 0;
}
} else {
+ if (level >= 90) {
+ // If the battery level is at least 90%, always consider the device to be
+ // charging even if it happens to go down a level.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ } if (!mCharging) {
+ if (mLastChargeStepLevel < level) {
+ // We have not reporting that we are charging, but the level has now
+ // gone up, so consider the state to be charging.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ }
+ } else {
+ if (mLastChargeStepLevel > level) {
+ // We had reported that the device was charging, but here we are with
+ // power connected and the level going down. Looks like the current
+ // power supplied isn't enough, so consider the device to now be
+ // discharging.
+ changed |= setChargingLocked(false);
+ mLastChargeStepLevel = level;
+ }
+ }
if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
modeBits, elapsedRealtime);
@@ -8075,6 +8142,9 @@ public final class BatteryStatsImpl extends BatteryStats {
mModStepMode = 0;
}
}
+ if (changed) {
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index a4cdf194189f..671bf2444acc 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -90,12 +90,15 @@ public class InstallerConnection {
}
}
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null);
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
+ return dexopt(apkPath, uid, isPublic, "*", instructionSet, dexoptNeeded,
+ false, false, null);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, String outputPath) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
@@ -106,6 +109,8 @@ public class InstallerConnection {
builder.append(pkgName);
builder.append(' ');
builder.append(instructionSet);
+ builder.append(' ');
+ builder.append(dexoptNeeded);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
builder.append(' ');
@@ -113,25 +118,6 @@ public class InstallerConnection {
return execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return patchoat(apkPath, uid, isPublic, "*", instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- StringBuilder builder = new StringBuilder("patchoat");
- builder.append(' ');
- builder.append(apkPath);
- builder.append(' ');
- builder.append(uid);
- builder.append(isPublic ? " 1" : " 0");
- builder.append(' ');
- builder.append(pkgName);
- builder.append(' ');
- builder.append(instructionSet);
- return execute(builder.toString());
- }
-
private boolean connect() {
if (mSocket != null) {
return true;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 70f7b72a4f57..3ad4f1ca74ad 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -467,12 +467,11 @@ public class ZygoteInit {
try {
for (String classPathElement : classPathElements) {
- final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet,
- false /* defer */);
- if (dexopt == DexFile.DEXOPT_NEEDED) {
- installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet);
- } else if (dexopt == DexFile.PATCHOAT_NEEDED) {
- installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet);
+ final int dexoptNeeded = DexFile.getDexOptNeeded(
+ classPathElement, "*", instructionSet, false /* defer */);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ installer.dexopt(classPathElement, Process.SYSTEM_UID, false,
+ instructionSet, dexoptNeeded);
}
}
} catch (IOException ioe) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index be9945d35fbc..2219ad10c720 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -24,7 +24,9 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -32,19 +34,28 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Transformation;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.PopupWindow;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
+import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
/**
* A floating toolbar for showing contextual menu items.
* This view shows as many menu item buttons as can fit in the horizontal toolbar and the
@@ -53,6 +64,9 @@ import java.util.List;
*/
public final class FloatingToolbar {
+ // This class is responsible for the public API of the floating toolbar.
+ // It delegates rendering operations to the FloatingToolbarPopup.
+
private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
new MenuItem.OnMenuItemClickListener() {
@Override
@@ -63,17 +77,6 @@ public final class FloatingToolbar {
private final Context mContext;
private final FloatingToolbarPopup mPopup;
- private final ViewGroup mMenuItemButtonsContainer;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getTag() instanceof MenuItem) {
- mMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
- mPopup.dismiss();
- }
- }
- };
private final Rect mContentRect = new Rect();
private final Point mCoordinates = new Point();
@@ -81,17 +84,17 @@ public final class FloatingToolbar {
private Menu mMenu;
private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
- private View mOpenOverflowButton;
private int mSuggestedWidth;
+ private boolean mWidthChanged = true;
+ private int mOverflowDirection;
/**
* Initializes a floating toolbar.
*/
public FloatingToolbar(Context context, Window window) {
mContext = Preconditions.checkNotNull(context);
- mPopup = new FloatingToolbarPopup(Preconditions.checkNotNull(window.getDecorView()));
- mMenuItemButtonsContainer = createMenuButtonsContainer(context);
+ mPopup = new FloatingToolbarPopup(window.getDecorView());
}
/**
@@ -137,6 +140,10 @@ public final class FloatingToolbar {
* toolbar.
*/
public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
+ // Check if there's been a substantial width spec change.
+ int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+ mWidthChanged = difference > (mSuggestedWidth * 0.2);
+
mSuggestedWidth = suggestedWidth;
return this;
}
@@ -146,16 +153,18 @@ public final class FloatingToolbar {
*/
public FloatingToolbar show() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
- if (hasContentChanged(menuItems) || hasWidthChanged()) {
+ if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
- layoutMenuItemButtons(menuItems);
+ mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
mShowingTitles = getMenuItemTitles(menuItems);
}
refreshCoordinates();
+ mPopup.setOverflowDirection(mOverflowDirection);
mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
if (!mPopup.isShowing()) {
mPopup.show(mCoordinates.x, mCoordinates.y);
}
+ mWidthChanged = false;
return this;
}
@@ -189,45 +198,26 @@ public final class FloatingToolbar {
* Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
*/
private void refreshCoordinates() {
- int popupWidth = mPopup.getWidth();
- int popupHeight = mPopup.getHeight();
- if (!mPopup.isShowing()) {
- // Popup isn't yet shown, get estimated size from the menu item buttons container.
- mMenuItemButtonsContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- popupWidth = mMenuItemButtonsContainer.getMeasuredWidth();
- popupHeight = mMenuItemButtonsContainer.getMeasuredHeight();
- }
- int x = mContentRect.centerX() - popupWidth / 2;
+ int x = mContentRect.centerX() - mPopup.getWidth() / 2;
int y;
- if (shouldDisplayAtTopOfContent()) {
- y = mContentRect.top - popupHeight;
+ if (mContentRect.top > mPopup.getHeight()) {
+ y = mContentRect.top - mPopup.getHeight();
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
+ } else if (mContentRect.top > getEstimatedToolbarHeight(mContext)) {
+ y = mContentRect.top - getEstimatedToolbarHeight(mContext);
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
} else {
y = mContentRect.bottom;
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
}
mCoordinates.set(x, y);
}
/**
- * Returns true if this floating toolbar's menu items have been reordered or changed.
- */
- private boolean hasContentChanged(List<MenuItem> menuItems) {
- return !mShowingTitles.equals(getMenuItemTitles(menuItems));
- }
-
- /**
- * Returns true if there is a significant change in width of the toolbar.
- */
- private boolean hasWidthChanged() {
- int actualWidth = mPopup.getWidth();
- int difference = Math.abs(actualWidth - mSuggestedWidth);
- return difference > (actualWidth * 0.2);
- }
-
- /**
- * Returns true if the preferred positioning of the toolbar is above the content rect.
+ * Returns true if this floating toolbar is currently showing the specified menu items.
*/
- private boolean shouldDisplayAtTopOfContent() {
- return mContentRect.top - getMinimumOverflowHeight(mContext) > 0;
+ private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
+ return mShowingTitles.equals(getMenuItemTitles(menuItems));
}
/**
@@ -258,178 +248,162 @@ public final class FloatingToolbar {
return titles;
}
- private void layoutMenuItemButtons(List<MenuItem> menuItems) {
- final int toolbarWidth = getAdjustedToolbarWidth(mContext, mSuggestedWidth)
- // Reserve space for the "open overflow" button.
- - getEstimatedOpenOverflowButtonWidth(mContext);
-
- int availableWidth = toolbarWidth;
- LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
-
- mMenuItemButtonsContainer.removeAllViews();
-
- boolean isFirstItem = true;
- while (!remainingMenuItems.isEmpty()) {
- final MenuItem menuItem = remainingMenuItems.peek();
- Button menuItemButton = createMenuItemButton(mContext, menuItem);
-
- // Adding additional left padding for the first button to even out button spacing.
- if (isFirstItem) {
- menuItemButton.setPadding(
- 2 * menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
- isFirstItem = false;
- }
-
- // Adding additional right padding for the last button to even out button spacing.
- if (remainingMenuItems.size() == 1) {
- menuItemButton.setPadding(
- menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- 2 * menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
- }
-
- menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
- if (menuItemButtonWidth <= availableWidth) {
- menuItemButton.setTag(menuItem);
- menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
- mMenuItemButtonsContainer.addView(menuItemButton);
- menuItemButton.getLayoutParams().width = menuItemButtonWidth;
- availableWidth -= menuItemButtonWidth;
- remainingMenuItems.pop();
- } else {
- // The "open overflow" button launches the vertical overflow from the
- // floating toolbar.
- createOpenOverflowButtonIfNotExists();
- mMenuItemButtonsContainer.addView(mOpenOverflowButton);
- break;
- }
- }
- mPopup.setContentView(mMenuItemButtonsContainer);
- }
-
- /**
- * Creates and returns the button that opens the vertical overflow.
- */
- private void createOpenOverflowButtonIfNotExists() {
- mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
- .inflate(R.layout.floating_popup_open_overflow_button, null);
- mOpenOverflowButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Open the overflow.
- }
- });
- }
-
- /**
- * Creates and returns a floating toolbar menu buttons container.
- */
- private static ViewGroup createMenuButtonsContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
- }
/**
- * Creates and returns a menu button for the specified menu item.
+ * A popup window used by the floating toolbar.
+ *
+ * This class is responsible for the rendering/animation of the floating toolbar.
+ * It can hold one of 2 panels (i.e. main panel and overflow panel) at a time.
+ * It delegates specific panel functionality to the appropriate panel.
*/
- private static Button createMenuItemButton(Context context, MenuItem menuItem) {
- Button menuItemButton = (Button) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_menu_button, null);
- menuItemButton.setText(menuItem.getTitle());
- menuItemButton.setContentDescription(menuItem.getTitle());
- return menuItemButton;
- }
-
- private static int getMinimumOverflowHeight(Context context) {
- return context.getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
- }
+ private static final class FloatingToolbarPopup {
- private static int getEstimatedOpenOverflowButtonWidth(Context context) {
- return context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
- }
+ public static final int OVERFLOW_DIRECTION_UP = 0;
+ public static final int OVERFLOW_DIRECTION_DOWN = 1;
- private static int getAdjustedToolbarWidth(Context context, int width) {
- if (width <= 0 || width > getScreenWidth(context)) {
- width = context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
- }
- return width;
- }
+ private final View mParent;
+ private final PopupWindow mPopupWindow;
+ private final ViewGroup mContentContainer;
+ private final int mPadding;
- /**
- * Returns the device's screen width.
- */
- public static int getScreenWidth(Context context) {
- return context.getResources().getDisplayMetrics().widthPixels;
- }
+ private final Animation.AnimationListener mOnOverflowOpened =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
- /**
- * Returns the device's screen height.
- */
- public static int getScreenHeight(Context context) {
- return context.getResources().getDisplayMetrics().heightPixels;
- }
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the overflow panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mOverflowPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mOverflowPanel.getView());
+ mOverflowPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ };
+ private final Animation.AnimationListener mOnOverflowClosed =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
- /**
- * A popup window used by the floating toolbar.
- */
- private static final class FloatingToolbarPopup {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the main panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mMainPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mMainPanel.getView());
+ mMainPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
- private final View mParent;
- private final PopupWindow mPopupWindow;
- private final ViewGroup mContentContainer;
- private final Animator.AnimatorListener mOnDismissEnd =
- new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
- mPopupWindow.dismiss();
- mDismissAnimating = false;
+ public void onAnimationRepeat(Animation animation) {
}
};
private final AnimatorSet mGrowFadeInFromBottomAnimation;
private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
+ private final Runnable mOpenOverflow = new Runnable() {
+ @Override
+ public void run() {
+ openOverflow();
+ }
+ };
+ private final Runnable mCloseOverflow = new Runnable() {
+ @Override
+ public void run() {
+ closeOverflow();
+ }
+ };
+
+ private final Region mTouchableRegion = new Region();
+
private boolean mDismissAnimating;
+ private FloatingToolbarOverflowPanel mOverflowPanel;
+ private FloatingToolbarMainPanel mMainPanel;
+ private int mOverflowDirection;
+
/**
- * Initializes a new floating bar popup.
+ * Initializes a new floating toolbar popup.
*
- * @param parent A parent view to get the {@link View#getWindowToken()} token from.
+ * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
+ * from.
*/
public FloatingToolbarPopup(View parent) {
mParent = Preconditions.checkNotNull(parent);
mContentContainer = createContentContainer(parent.getContext());
mPopupWindow = createPopupWindow(mContentContainer);
mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
- mShrinkFadeOutFromBottomAnimation =
- createShrinkFadeOutFromBottomAnimation(mContentContainer, mOnDismissEnd);
+ mShrinkFadeOutFromBottomAnimation = createShrinkFadeOutFromBottomAnimation(
+ mContentContainer,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ mDismissAnimating = false;
+ setMainPanelAsContent();
+ }
+ });
+ // Make the touchable area of this popup be the area specified by mTouchableRegion.
+ mPopupWindow.getContentView()
+ .getRootView()
+ .getViewTreeObserver()
+ .addOnComputeInternalInsetsListener(
+ new ViewTreeObserver.OnComputeInternalInsetsListener() {
+ public void onComputeInternalInsets(
+ ViewTreeObserver.InternalInsetsInfo info) {
+ info.contentInsets.setEmpty();
+ info.visibleInsets.setEmpty();
+ info.touchableRegion.set(mTouchableRegion);
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo
+ .TOUCHABLE_INSETS_REGION);
+ }
+ });
+ mPadding = parent.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_margin);
+ }
+
+ /**
+ * Lays out buttons for the specified menu items.
+ */
+ public void layoutMenuItems(List<MenuItem> menuItems,
+ MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) {
+ mContentContainer.removeAllViews();
+ if (mMainPanel == null) {
+ mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow);
+ }
+ List<MenuItem> overflowMenuItems =
+ mMainPanel.layoutMenuItems(menuItems, suggestedWidth);
+ mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
+ if (!overflowMenuItems.isEmpty()) {
+ if (mOverflowPanel == null) {
+ mOverflowPanel =
+ new FloatingToolbarOverflowPanel(mParent.getContext(), mCloseOverflow);
+ }
+ mOverflowPanel.setMenuItems(overflowMenuItems);
+ mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
+ }
+ updatePopupSize();
}
/**
* Shows this popup at the specified coordinates.
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
- * If this popup is already showing, this will be a no-op.
*/
public void show(int x, int y) {
if (isShowing()) {
- updateCoordinates(x, y);
return;
}
- mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, 0, 0);
- positionOnScreen(x, y);
+ stopDismissAnimation();
+ preparePopupContent();
+ mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y);
growFadeInFromBottom();
-
- mDismissAnimating = false;
}
/**
@@ -440,12 +414,9 @@ public final class FloatingToolbar {
return;
}
- if (mDismissAnimating) {
- // This window is already dismissing. Don't restart the animation.
- return;
- }
mDismissAnimating = true;
shrinkFadeOutFromBottom();
+ setZeroTouchableSurface();
}
/**
@@ -460,32 +431,40 @@ public final class FloatingToolbar {
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
public void updateCoordinates(int x, int y) {
- if (isShowing()) {
- positionOnScreen(x, y);
+ if (mDismissAnimating) {
+ // Already being dismissed. Ignore.
+ return;
}
+
+ preparePopupContent();
+ mPopupWindow.update(x, y, getWidth(), getHeight());
}
/**
- * Sets the content of this popup.
+ * Sets the direction in which the overflow will open. i.e. up or down.
+ *
+ * @param overflowDirection Either {@link #OVERFLOW_DIRECTION_UP}
+ * or {@link #OVERFLOW_DIRECTION_DOWN}.
*/
- public void setContentView(View view) {
- Preconditions.checkNotNull(view);
- mContentContainer.removeAllViews();
- mContentContainer.addView(view);
+ public void setOverflowDirection(int overflowDirection) {
+ mOverflowDirection = overflowDirection;
+ if (mOverflowPanel != null) {
+ mOverflowPanel.setOverflowDirection(mOverflowDirection);
+ }
}
/**
* Returns the width of this popup.
*/
public int getWidth() {
- return mContentContainer.getWidth();
+ return mPopupWindow.getWidth();
}
/**
* Returns the height of this popup.
*/
public int getHeight() {
- return mContentContainer.getHeight();
+ return mPopupWindow.getHeight();
}
/**
@@ -495,24 +474,10 @@ public final class FloatingToolbar {
return mContentContainer.getContext();
}
- private void positionOnScreen(int x, int y) {
- if (getWidth() == 0) {
- // content size is yet to be measured.
- mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- }
- x = clamp(x, 0, getScreenWidth(getContext()) - getWidth());
- y = clamp(y, 0, getScreenHeight(getContext()) - getHeight());
-
- // Position the view w.r.t. the window.
- mContentContainer.setX(x);
- mContentContainer.setY(y);
- }
-
/**
* Performs the "grow and fade in from the bottom" animation on the floating popup.
*/
private void growFadeInFromBottom() {
- setPivot();
mGrowFadeInFromBottomAnimation.start();
}
@@ -520,77 +485,643 @@ public final class FloatingToolbar {
* Performs the "shrink and fade out from bottom" animation on the floating popup.
*/
private void shrinkFadeOutFromBottom() {
- setPivot();
mShrinkFadeOutFromBottomAnimation.start();
}
+ private void stopDismissAnimation() {
+ mDismissAnimating = false;
+ mShrinkFadeOutFromBottomAnimation.cancel();
+ }
+
+ /**
+ * Opens the floating toolbar overflow.
+ * This method should not be called if menu items have not been laid out with
+ * {@link #layoutMenuItems(List, MenuItem.OnMenuItemClickListener, int)}.
+ *
+ * @throws IllegalStateException if called when menu items have not been laid out.
+ */
+ private void openOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mMainPanel.fadeOut(true);
+ Size overflowPanelSize = mOverflowPanel.measure();
+ final int targetWidth = getOverflowWidth(mParent.getContext());
+ final int targetHeight = overflowPanelSize.getHeight();
+ final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float startY = mContentContainer.getY();
+ final float right = mContentContainer.getX() + mContentContainer.getWidth();
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ params.width = startWidth + deltaWidth;
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ params.height = startHeight + deltaHeight;
+ mContentContainer.setLayoutParams(params);
+ if (morphUpwards) {
+ float y = startY - (mContentContainer.getHeight() - startHeight);
+ mContentContainer.setY(y);
+ }
+ }
+ };
+ widthAnimation.setDuration(240);
+ heightAnimation.setDuration(180);
+ heightAnimation.setStartOffset(60);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowOpened);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * Opens the floating toolbar overflow.
+ * This method should not be called if menu items have not been laid out with
+ * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
+ *
+ * @throws IllegalStateException
+ */
+ private void closeOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mOverflowPanel.fadeOut(true);
+ Size mainPanelSize = mMainPanel.measure();
+ final int targetWidth = mainPanelSize.getWidth();
+ final int targetHeight = mainPanelSize.getHeight();
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float right = mContentContainer.getX() + mContentContainer.getWidth();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+ final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ params.width = startWidth + deltaWidth;
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ params.height = startHeight + deltaHeight;
+ mContentContainer.setLayoutParams(params);
+ if (morphedUpwards) {
+ mContentContainer.setY(bottom - mContentContainer.getHeight());
+ }
+ }
+ };
+ widthAnimation.setDuration(150);
+ widthAnimation.setStartOffset(150);
+ heightAnimation.setDuration(210);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowClosed);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * Prepares the content container for show and update calls.
+ */
+ private void preparePopupContent() {
+ // Do not call this method if main view panel has not been initialized.
+ Preconditions.checkNotNull(mMainPanel);
+
+ // If we're yet to show the popup, set the container visibility to zero.
+ // The "show" animation will make this visible.
+ if (!mPopupWindow.isShowing()) {
+ mContentContainer.setAlpha(0);
+ }
+
+ // Make sure panels are visible.
+ mMainPanel.fadeIn(false);
+ if (mOverflowPanel != null) {
+ mOverflowPanel.fadeIn(false);
+ }
+
+ // Make sure a panel is set as the content.
+ if (mContentContainer.getChildCount() == 0) {
+ mContentContainer.addView(mMainPanel.getView());
+ }
+
+ // Make sure the main panel is at the correct position.
+ if (mContentContainer.getChildAt(0) == mMainPanel.getView()) {
+ mContentContainer.setX(mPadding);
+ float y = mPadding;
+ if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
+ y = getHeight() - getEstimatedToolbarHeight(mParent.getContext()) - mPadding;
+ }
+ mContentContainer.setY(y);
+ }
+
+ setContentAreaAsTouchableSurface();
+ }
+
+ /**
+ * Sets the current content to be the main view panel.
+ */
+ private void setMainPanelAsContent() {
+ mContentContainer.removeAllViews();
+ Size mainPanelSize = mMainPanel.measure();
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ params.width = mainPanelSize.getWidth();
+ params.height = mainPanelSize.getHeight();
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.addView(mMainPanel.getView());
+ }
+
+ private void updatePopupSize() {
+ int width = 0;
+ int height = 0;
+ if (mMainPanel != null) {
+ Size mainPanelSize = mMainPanel.measure();
+ width = mainPanelSize.getWidth();
+ height = mainPanelSize.getHeight();
+ }
+ if (mOverflowPanel != null) {
+ Size overflowPanelSize = mOverflowPanel.measure();
+ width = Math.max(width, overflowPanelSize.getWidth());
+ height = Math.max(height, overflowPanelSize.getHeight());
+ }
+ mPopupWindow.setWidth(width + mPadding * 2);
+ mPopupWindow.setHeight(height + mPadding * 2);
+ }
+
+ /**
+ * Sets the touchable region of this popup to be zero. This means that all touch events on
+ * this popup will go through to the surface behind it.
+ */
+ private void setZeroTouchableSurface() {
+ mTouchableRegion.setEmpty();
+ }
+
+ /**
+ * Sets the touchable region of this popup to be the area occupied by its content.
+ */
+ private void setContentAreaAsTouchableSurface() {
+ if (!mPopupWindow.isShowing()) {
+ mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+ int width = mContentContainer.getMeasuredWidth();
+ int height = mContentContainer.getMeasuredHeight();
+ mTouchableRegion.set(
+ (int) mContentContainer.getX(),
+ (int) mContentContainer.getY(),
+ (int) mContentContainer.getX() + width,
+ (int) mContentContainer.getY() + height);
+ }
+ }
+
+ /**
+ * A widget that holds the primary menu items in the floating toolbar.
+ */
+ private static final class FloatingToolbarMainPanel {
+
+ private final Context mContext;
+ private final ViewGroup mContentView;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() instanceof MenuItem) {
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
+ }
+ }
+ }
+ };
+ private final ViewFader viewFader;
+ private final Runnable mOpenOverflow;
+
+ private View mOpenOverflowButton;
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+
+ /**
+ * Initializes a floating toolbar popup main view panel.
+ *
+ * @param context
+ * @param openOverflow The code that opens the toolbar popup overflow.
+ */
+ public FloatingToolbarMainPanel(Context context, Runnable openOverflow) {
+ mContext = Preconditions.checkNotNull(context);
+ mContentView = new LinearLayout(context);
+ viewFader = new ViewFader(mContentView);
+ mOpenOverflow = Preconditions.checkNotNull(openOverflow);
+ }
+
/**
- * Sets the popup content container's pivot.
+ * Fits as many menu items in the main panel and returns a list of the menu items that
+ * were not fit in.
+ *
+ * @return The menu items that are not included in this main panel.
*/
- private void setPivot() {
- mContentContainer.setPivotX(mContentContainer.getMeasuredWidth() / 2);
- mContentContainer.setPivotY(mContentContainer.getMeasuredHeight());
+ public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) {
+ final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth)
+ // Reserve space for the "open overflow" button.
+ - getEstimatedOpenOverflowButtonWidth(mContext);
+
+ int availableWidth = toolbarWidth;
+ final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
+
+ mContentView.removeAllViews();
+
+ boolean isFirstItem = true;
+ while (!remainingMenuItems.isEmpty()) {
+ final MenuItem menuItem = remainingMenuItems.peek();
+ Button menuItemButton = createMenuItemButton(mContext, menuItem);
+
+ // Adding additional left padding for the first button to even out button spacing.
+ if (isFirstItem) {
+ menuItemButton.setPadding(
+ 2 * menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ isFirstItem = false;
+ }
+
+ // Adding additional right padding for the last button to even out button spacing.
+ if (remainingMenuItems.size() == 1) {
+ menuItemButton.setPadding(
+ menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ 2 * menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ }
+
+ menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+ if (menuItemButtonWidth <= availableWidth) {
+ menuItemButton.setTag(menuItem);
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+ mContentView.addView(menuItemButton);
+ ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+ params.width = menuItemButtonWidth;
+ menuItemButton.setLayoutParams(params);
+ availableWidth -= menuItemButtonWidth;
+ remainingMenuItems.pop();
+ } else {
+ if (mOpenOverflowButton == null) {
+ mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_open_overflow_button, null);
+ mOpenOverflowButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOpenOverflowButton != null) {
+ mOpenOverflow.run();
+ }
+ }
+ });
+ }
+ mContentView.addView(mOpenOverflowButton);
+ break;
+ }
+ }
+ return remainingMenuItems;
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
}
- private static ViewGroup createContentContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
+ public View getView() {
+ return mContentView;
}
- private static PopupWindow createPopupWindow(View content) {
- ViewGroup popupContentHolder = new LinearLayout(content.getContext());
- PopupWindow popupWindow = new PopupWindow(popupContentHolder);
- popupWindow.setAnimationStyle(0);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setWidth(getScreenWidth(content.getContext()));
- popupWindow.setHeight(getScreenHeight(content.getContext()));
- content.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- popupContentHolder.addView(content);
- return popupWindow;
+ public void fadeIn(boolean animate) {
+ viewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ viewFader.fadeOut(animate);
}
/**
- * Creates a "grow and fade in from the bottom" animation for the specified view.
+ * Returns how big this panel's view should be.
+ * This method should only be called when the view has not been attached to a parent
+ * otherwise it will throw an illegal state.
+ */
+ public Size measure() throws IllegalStateException {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
+ }
+
+
+ /**
+ * A widget that holds the overflow items in the floating toolbar.
+ */
+ private static final class FloatingToolbarOverflowPanel {
+
+ private final LinearLayout mContentView;
+ private final ViewGroup mBackButtonContainer;
+ private final View mBackButton;
+ private final ListView mListView;
+ private final ViewFader mViewFader;
+ private final Runnable mCloseOverflow;
+
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+
+ /**
+ * Initializes a floating toolbar popup overflow view panel.
*
- * @param view The view to animate
+ * @param context
+ * @param closeOverflow The code that closes the toolbar popup's overflow.
+ */
+ public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
+ mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+
+ mContentView = new LinearLayout(context);
+ mContentView.setOrientation(LinearLayout.VERTICAL);
+ mViewFader = new ViewFader(mContentView);
+
+ mBackButton = LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_close_overflow_button, null);
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCloseOverflow.run();
+ }
+ });
+ mBackButtonContainer = new LinearLayout(context);
+ mBackButtonContainer.addView(mBackButton);
+
+ mListView = createOverflowListView(context);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick(menuItem);
+ }
+ }
+ });
+
+ mContentView.addView(mListView);
+ mContentView.addView(mBackButtonContainer);
+ }
+
+ /**
+ * Sets the menu items to be displayed in the overflow.
*/
- private static AnimatorSet createGrowFadeInFromBottom(View view) {
- AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
- growFadeInFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
- return growFadeInFromBottomAnimation;
+ public void setMenuItems(List<MenuItem> menuItems) {
+ ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
+ overflowListViewAdapter.clear();
+ overflowListViewAdapter.addAll(menuItems);
+ setListViewHeight();
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
}
/**
- * Creates a "shrink and fade out from bottom" animation for the specified view.
+ * Notifies the overflow of the current direction in which the overflow will be opened.
*
- * @param view The view to animate
- * @param listener The animation listener
+ * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
+ * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
*/
- private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
- View view, Animator.AnimatorListener listener) {
- AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
- shrinkFadeOutFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
- shrinkFadeOutFromBottomAnimation.setStartDelay(150);
- shrinkFadeOutFromBottomAnimation.addListener(listener);
- return shrinkFadeOutFromBottomAnimation;
+ public void setOverflowDirection(int overflowDirection) {
+ mContentView.removeView(mBackButtonContainer);
+ int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
+ mContentView.addView(mBackButtonContainer, index);
}
/**
- * Returns value, restricted to the range min->max (inclusive).
- * If maximum is less than minimum, the result is undefined.
+ * Returns the content view of the overflow.
+ */
+ public View getView() {
+ return mContentView;
+ }
+
+ public void fadeIn(boolean animate) {
+ mViewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ mViewFader.fadeOut(animate);
+ }
+
+ /**
+ * Returns how big this panel's view should be.
+ * This method should only be called when the view has not been attached to a parent.
*
- * @param value The value to clamp.
- * @param minimum The minimum value in the range.
- * @param maximum The maximum value in the range. Must not be less than minimum.
+ * @throws IllegalStateException
*/
- private static int clamp(int value, int minimum, int maximum) {
- return Math.max(minimum, Math.min(value, maximum));
+ public Size measure() {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
+
+ private void setListViewHeight() {
+ int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
+ int height = mListView.getAdapter().getCount() * itemHeight;
+ int maxHeight = mContentView.getContext().getResources().
+ getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+ ViewGroup.LayoutParams params = mListView.getLayoutParams();
+ params.height = Math.min(height, maxHeight);
+ mListView.setLayoutParams(params);
}
+
+ private static ListView createOverflowListView(final Context context) {
+ final ListView overflowListView = new ListView(context);
+ overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ overflowListView.setDivider(null);
+ overflowListView.setDividerHeight(0);
+ final ArrayAdapter overflowListViewAdapter =
+ new ArrayAdapter<MenuItem>(context, 0) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView menuButton;
+ if (convertView != null) {
+ menuButton = (TextView) convertView;
+ } else {
+ menuButton = createOverflowMenuItemButton(context);
+ }
+ MenuItem menuItem = getItem(position);
+ menuButton.setText(menuItem.getTitle());
+ menuButton.setContentDescription(menuItem.getTitle());
+ return menuButton;
+ }
+ };
+ overflowListView.setAdapter(overflowListViewAdapter);
+ return overflowListView;
+ }
+ }
+
+
+ /**
+ * A helper for fading in or out a view.
+ */
+ private static final class ViewFader {
+
+ private static final int FADE_OUT_DURATION = 250;
+ private static final int FADE_IN_DURATION = 150;
+
+ private final View mView;
+ private final ObjectAnimator mFadeOutAnimation;
+ private final ObjectAnimator mFadeInAnimation;
+
+ private ViewFader(View view) {
+ mView = Preconditions.checkNotNull(view);
+ mFadeOutAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0)
+ .setDuration(FADE_OUT_DURATION);
+ mFadeInAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1)
+ .setDuration(FADE_IN_DURATION);
+ }
+
+ public void fadeIn(boolean animate) {
+ if (animate) {
+ mFadeInAnimation.start();
+ } else {
+ mView.setAlpha(1);
+ }
+ }
+
+ public void fadeOut(boolean animate) {
+ if (animate) {
+ mFadeOutAnimation.start();
+ } else {
+ mView.setAlpha(0);
+ }
+ }
+ }
+
+
+ /**
+ * Creates and returns a menu button for the specified menu item.
+ */
+ private static Button createMenuItemButton(Context context, MenuItem menuItem) {
+ Button menuItemButton = (Button) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_menu_button, null);
+ menuItemButton.setText(menuItem.getTitle());
+ menuItemButton.setContentDescription(menuItem.getTitle());
+ return menuItemButton;
+ }
+
+ /**
+ * Creates and returns a styled floating toolbar overflow list view item.
+ */
+ private static TextView createOverflowMenuItemButton(Context context) {
+ return (TextView) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_overflow_list_item, null);
+ }
+
+ private static ViewGroup createContentContainer(Context context) {
+ return (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ }
+
+ private static PopupWindow createPopupWindow(View content) {
+ ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ content.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ popupContentHolder.addView(content);
+ return popupWindow;
+ }
+
+ /**
+ * Creates a "grow and fade in from the bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ */
+ private static AnimatorSet createGrowFadeInFromBottom(View view) {
+ AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
+ growFadeInFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
+ growFadeInFromBottomAnimation.setStartDelay(50);
+ return growFadeInFromBottomAnimation;
+ }
+
+ /**
+ * Creates a "shrink and fade out from bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ * @param listener The animation listener
+ */
+ private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
+ View view, Animator.AnimatorListener listener) {
+ AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
+ shrinkFadeOutFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
+ shrinkFadeOutFromBottomAnimation.setStartDelay(150);
+ shrinkFadeOutFromBottomAnimation.addListener(listener);
+ return shrinkFadeOutFromBottomAnimation;
+ }
+
+ private static int getOverflowWidth(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_width);
+ }
+
+ private static int getEstimatedToolbarHeight(Context context) {
+ return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
+ }
+
+ private static int getEstimatedOpenOverflowButtonWidth(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
+ }
+
+ private static int getAdjustedToolbarWidth(Context context, int width) {
+ if (width <= 0 || width > getScreenWidth(context)) {
+ width = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
+ }
+ return width;
+ }
+
+ /**
+ * Returns the device's screen width.
+ */
+ private static int getScreenWidth(Context context) {
+ return context.getResources().getDisplayMetrics().widthPixels;
+ }
+
+ /**
+ * Returns the device's screen height.
+ */
+ private static int getScreenHeight(Context context) {
+ return context.getResources().getDisplayMetrics().heightPixels;
+ }
+
+ /**
+ * Returns value, restricted to the range min->max (inclusive).
+ * If maximum is less than minimum, the result is undefined.
+ *
+ * @param value The value to clamp.
+ * @param minimum The minimum value in the range.
+ * @param maximum The maximum value in the range. Must not be less than minimum.
+ */
+ private static int clamp(int value, int minimum, int maximum) {
+ return Math.max(minimum, Math.min(value, maximum));
}
}
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index fb91c8f67a67..9cf6a9d08890 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -48,11 +48,6 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
return NULL;
}
- ScopedLocalRef<jstring> uniqueIdObj(env, env->NewStringUTF(deviceInfo.getIdentifier().uniqueId));
- if (!uniqueIdObj.get()) {
- return NULL;
- }
-
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
deviceInfo.getKeyCharacterMap()));
@@ -62,13 +57,16 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+ // Not sure why, but JNI is complaining when I pass this through directly.
+ jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE;
+
ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
deviceInfo.getControllerNumber(), nameObj.get(),
static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product),
- uniqueIdObj.get(), descriptorObj.get(), deviceInfo.isExternal(),
- deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(),
- deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad()));
+ descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(),
+ deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(),
+ hasMic, deviceInfo.hasButtonUnderPad()));
const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (size_t i = 0; i < ranges.size(); i++) {
@@ -90,7 +88,7 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
- "(IIILjava/lang/String;IILjava/lang/String;Ljava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZ)V");
+ "(IIILjava/lang/String;IILjava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f427f2bfe392..f2f7be289fde 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -56,6 +56,8 @@
<protected-broadcast android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
+ <protected-broadcast android:name="android.intent.action.CHARGING" />
+ <protected-broadcast android:name="android.intent.action.DISCHARGING" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
diff --git a/core/res/res/drawable/fastscroll_label_left_material.xml b/core/res/res/drawable/fastscroll_label_left_material.xml
index 430d1b0aa652..c825f734eb9f 100644
--- a/core/res/res/drawable/fastscroll_label_left_material.xml
+++ b/core/res/res/drawable/fastscroll_label_left_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomRightRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomRightRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/drawable/fastscroll_label_right_material.xml b/core/res/res/drawable/fastscroll_label_right_material.xml
index 6e61397118cf..94f5fde95126 100644
--- a/core/res/res/drawable/fastscroll_label_right_material.xml
+++ b/core/res/res/drawable/fastscroll_label_right_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomLeftRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetRight="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomLeftRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/layout/floating_popup_close_overflow_button.xml b/core/res/res/layout/floating_popup_close_overflow_button.xml
new file mode 100644
index 000000000000..a1d28113dad1
--- /dev/null
+++ b/core/res/res/layout/floating_popup_close_overflow_button.xml
@@ -0,0 +1,24 @@
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:src="?android:attr/actionModeCloseDrawable"
+ android:contentDescription="@string/floating_toolbar_close_overflow_description"
+ android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_open_overflow_button.xml b/core/res/res/layout/floating_popup_open_overflow_button.xml
index 4c1176c2a4c9..dca538462cc3 100644
--- a/core/res/res/layout/floating_popup_open_overflow_button.xml
+++ b/core/res/res/layout/floating_popup_open_overflow_button.xml
@@ -21,5 +21,5 @@
android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
android:minHeight="@dimen/floating_toolbar_height"
android:src="@drawable/ic_menu_moreoverflow_material"
- android:contentDescription="@string/action_menu_overflow_description"
+ android:contentDescription="@string/floating_toolbar_open_overflow_description"
android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_overflow_list_item b/core/res/res/layout/floating_popup_overflow_list_item
new file mode 100644
index 000000000000..9294f3b1ab4b
--- /dev/null
+++ b/core/res/res/layout/floating_popup_overflow_list_item
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:minWidth="@dimen/floating_toolbar_menu_button_side_padding"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:textSize="@dimen/floating_toolbar_text_size"
+ android:textAllCaps="true" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index eb37619a5d66..aefe79d90f82 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3414,9 +3414,9 @@
<attr name="position">
<!-- Floating at the top of the content. -->
<enum name="floating" value="0" />
- <!-- Pinned alongside the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the middle of the thumb. -->
<enum name="atThumb" value="1" />
- <!-- Pinned above the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the top edge of the thumb. -->
<enum name="aboveThumb" value="2" />
</attr>
<attr name="textAppearance" />
@@ -3428,6 +3428,16 @@
<attr name="minHeight" />
<!-- Padding for the section header preview. -->
<attr name="padding" />
+ <!-- Position of thumb in relation to the track. -->
+ <attr name="thumbPosition">
+ <!-- The thumb's midpoint is anchored to the track. At its
+ extremes, the thumb will extend half-way outside the
+ track. -->
+ <enum name="midpoint" value="0" />
+ <!-- The thumb is entirely inside the track. At its extremes,
+ the thumb will be contained entirely within the track. -->
+ <enum name="inside" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="FrameLayout">
<!-- Determines whether to measure all children or just those in
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 7d08e7f62756..2654a259927b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -379,11 +379,11 @@
<dimen name="text_handle_min_size">40dp</dimen>
<!-- Lighting and shadow properties -->
- <dimen name="light_y">-200dp</dimen>
- <dimen name="light_z">800dp</dimen>
- <dimen name="light_radius">600dp</dimen>
- <item type="dimen" format="float" name="ambient_shadow_alpha">0.075</item>
- <item type="dimen" format="float" name="spot_shadow_alpha">0.15</item>
+ <dimen name="light_y">0dp</dimen>
+ <dimen name="light_z">600dp</dimen>
+ <dimen name="light_radius">800dp</dimen>
+ <item type="dimen" format="float" name="ambient_shadow_alpha">0.039</item>
+ <item type="dimen" format="float" name="spot_shadow_alpha">0.19</item>
<!-- Floating toolbar dimensions -->
<dimen name="floating_toolbar_height">48dp</dimen>
@@ -392,4 +392,6 @@
<dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
<dimen name="floating_toolbar_default_width">250dp</dimen>
<dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen>
+ <dimen name="floating_toolbar_overflow_width">130dp</dimen>
+ <dimen name="floating_toolbar_margin">2dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 24d17a4b9c98..7349d232a243 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2659,4 +2659,5 @@
<public type="attr" name="breakStrategy" />
<public type="attr" name="supportsAssistGesture" />
+ <public type="attr" name="thumbPosition" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7dc3ff79d273..a48e9648c7e1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5237,4 +5237,10 @@
<!-- Model name for USB MIDI Peripheral port -->
<string name="usb_midi_peripheral_model_name">USB Peripheral Port</string>
+ <!-- Floating toolbar strings -->
+ <!-- Content description for the button that opens the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_open_overflow_description">More options</string>
+ <!-- Content description for the button that closes the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_close_overflow_description">Close overflow</string>
+
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 9cf7884b737b..7cb8144fb673 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -943,9 +943,10 @@ please see styles_device_defaults.xml.
<item name="thumbMinWidth">0dp</item>
<item name="thumbMinHeight">0dp</item>
<item name="textSize">45sp</item>
- <item name="minWidth">88dp</item>
+ <item name="minWidth">104dp</item>
<item name="minHeight">88dp</item>
<item name="padding">0dp</item>
+ <item name="thumbPosition">inside</item>
</style>
<style name="Widget.Material.PreferenceFrameLayout">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b4ba3162a785..c2c00b5bdcc3 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2214,12 +2214,16 @@
<java-symbol type="layout" name="floating_popup_container" />
<java-symbol type="layout" name="floating_popup_menu_button" />
<java-symbol type="layout" name="floating_popup_open_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_close_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_overflow_list_item" />
<java-symbol type="dimen" name="floating_toolbar_height" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" />
<java-symbol type="dimen" name="floating_toolbar_text_size" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
<java-symbol type="dimen" name="floating_toolbar_default_width" />
<java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
+ <java-symbol type="dimen" name="floating_toolbar_overflow_width" />
+ <java-symbol type="dimen" name="floating_toolbar_margin" />
<java-symbol type="drawable" name="ic_chevron_left" />
<java-symbol type="drawable" name="ic_chevron_right" />
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index e75235e78aec..70e71078714e 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -9,7 +9,7 @@ page.title=Setting Up Google Play Services
<h2>In this document</h2>
<ol>
<li><a href="#Setup">Add Google Play Services to Your Project</a></li>
- <li><a href="#Proguard">Create a Proguard Exception</a></li>
+ <li><a href="#Proguard">Create a ProGuard Exception</a></li>
<li><a href="#ensure">Ensure Devices Have the Google Play services APK</a></li>
</ol>
@@ -195,6 +195,17 @@ you include an API that does have a separate library.)</p>
</tr>
</table>
+<p class="note"><strong>Note:</strong> ProGuard directives are included in the Play services
+client libraries to preserve the required classes. The
+<a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plugin for Gradle</a>
+automatically appends ProGuard configuration files in an AAR (Android ARchive) package and appends
+that package to your ProGuard configuration. During project creation, Android Studio automatically
+creates the ProGuard configuration files and <code>build.gradle</code> properties for ProGuard use.
+To use ProGuard with Android Studio, you must enable the ProGuard setting in your
+<code>build.gradle</code> <code>buildTypes</code>. For more information, see the
+<a href="{@docRoot}tools/help/proguard.html">ProGuard</a> topic. </p>
+
+
</div><!-- end studio -->
<div class="select-ide eclipse">
@@ -230,6 +241,33 @@ element:
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
+
+<h2 id="Proguard">Create a ProGuard Exception</h2>
+
+<p>To prevent <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> from stripping away
+required classes, add the following lines in the
+<code>&lt;project_directory&gt;/proguard-project.txt</code> file:
+<pre>
+-keep class * extends java.util.ListResourceBundle {
+ protected Object[][] getContents();
+}
+
+-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
+ public static final *** NULL;
+}
+
+-keepnames &#64;com.google.android.gms.common.annotation.KeepName class *
+-keepclassmembernames class * {
+ &#64;com.google.android.gms.common.annotation.KeepName *;
+}
+
+-keepnames class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+</pre>
+
+
+
</div><!-- end eclipse -->
<div class="select-ide other">
@@ -263,8 +301,6 @@ workspace&mdash;you should not reference the library directly from the Android S
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
-</div><!-- end other -->
-
<h2 id="Proguard">Create a Proguard Exception</h2>
@@ -290,11 +326,9 @@ required classes, add the following lines in the
}
</pre>
-<p class="note"><strong>Note:</strong> When using Android Studio, you must add Proguard
-to your <code>build.gradle</code> file's build types. For more information, see the
-<a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Running-ProGuard"
->Gradle Plugin User Guide</a>.
-</ol>
+
+</div><!-- end other -->
+
<h2 id="ensure">Ensure Devices Have the Google Play services APK</h2>
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index a2f71e5e30f2..78eee37528c1 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -211,7 +211,11 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
updateLayerFromTypedArray(layer, a);
a.recycle();
- if (layer.mDrawable == null) {
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+ layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index a5776a4b029c..da70e9b608eb 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -112,7 +112,7 @@ struct __assertChar16Size {
*
* The PNG chunk type is "npTc".
*/
-struct Res_png_9patch
+struct alignas(uintptr_t) Res_png_9patch
{
Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
yDivsOffset(0), colorsOffset(0) { }
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index ed690de12ec4..c5b6a68d41dc 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -512,12 +512,23 @@ public class AndroidKeyStore extends KeyStoreSpi {
}
}
- int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = params.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (params.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new KeyStoreException("Randomized encryption (IND-CPA) required but may be"
+ + " violated by block mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyStoreParameter documentation.");
+ }
+ }
for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -553,8 +564,8 @@ public class AndroidKeyStore extends KeyStoreSpi {
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed for the Cipher abstraction.
+ && (!params.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 635b2fa94b09..43f3b30fa4fb 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -64,6 +64,7 @@ public class AndroidKeyStoreProvider extends Provider {
putSecretKeyFactoryImpl("HmacSHA512");
// javax.crypto.Mac
+ putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1");
putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
index 0e490cd7cadb..4eedb2488030 100644
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Date;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -51,6 +52,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -66,6 +68,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
@KeyStoreKeyConstraints.PurposeEnum int purposes,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -89,6 +92,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
mPurposes = purposes;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -172,6 +176,19 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -223,6 +240,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -281,7 +299,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*/
@@ -293,7 +311,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -308,7 +326,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*/
@@ -321,7 +339,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*/
@@ -363,6 +381,43 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited;
+ * </li>
+ * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
+ * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
+ * used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * </ul>
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -427,6 +482,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
mPurposes,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index 52b7097b77c7..4ca220de9312 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -86,6 +86,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
@@ -134,6 +136,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
@KeyStoreKeyConstraints.DigestEnum int digests,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -174,6 +177,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mDigests = digests;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -186,8 +190,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
Date startDate, Date endDate, int flags) {
- this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate,
- endDate, flags, startDate, endDate, endDate, 0, 0, 0, 0, 0, -1, false);
+
+ this(context,
+ keyStoreAlias,
+ keyType,
+ keySize,
+ spec,
+ subjectDN,
+ serialNumber,
+ startDate,
+ endDate,
+ flags,
+ startDate,
+ endDate,
+ endDate,
+ 0,
+ 0,
+ 0,
+ 0,
+ true,
+ 0,
+ -1,
+ false);
}
/**
@@ -347,6 +371,21 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to the private key. The key can only
* be used iff the user has authenticated to at least one of these user authenticators.
*
@@ -446,6 +485,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
@@ -580,7 +621,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -594,7 +635,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -611,7 +652,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -626,7 +667,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -689,6 +730,33 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not
+ * offer {@code IND-CPA}, such as RSA without padding, are prohibited.
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -771,6 +839,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mDigests,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 097c20faca5f..e61092f1bef1 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -123,7 +123,7 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Algorithm.AES, Algorithm.HMAC})
+ @IntDef({Algorithm.AES, Algorithm.HMAC, Algorithm.RSA, Algorithm.EC})
public @interface AlgorithmEnum {}
/**
@@ -135,12 +135,22 @@ public abstract class KeyStoreKeyConstraints {
/**
* Key algorithm: AES.
*/
- public static final int AES = 0;
+ public static final int AES = 1 << 0;
/**
* Key algorithm: HMAC.
*/
- public static final int HMAC = 1;
+ public static final int HMAC = 1 << 1;
+
+ /**
+ * Key algorithm: RSA.
+ */
+ public static final int RSA = 1 << 2;
+
+ /**
+ * Key algorithm: EC.
+ */
+ public static final int EC = 1 << 3;
/**
* @hide
@@ -151,6 +161,10 @@ public abstract class KeyStoreKeyConstraints {
return KeymasterDefs.KM_ALGORITHM_AES;
case HMAC:
return KeymasterDefs.KM_ALGORITHM_HMAC;
+ case RSA:
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ case EC:
+ return KeymasterDefs.KM_ALGORITHM_ECDSA;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -165,6 +179,10 @@ public abstract class KeyStoreKeyConstraints {
return AES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
return HMAC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA;
+ case KeymasterDefs.KM_ALGORITHM_ECDSA:
+ return EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -179,6 +197,10 @@ public abstract class KeyStoreKeyConstraints {
return "AES";
case HMAC:
return "HMAC";
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -213,8 +235,18 @@ public abstract class KeyStoreKeyConstraints {
throw new IllegalArgumentException("HMAC digest not specified");
}
switch (digest) {
+ case Digest.MD5:
+ return "HmacMD5";
+ case Digest.SHA1:
+ return "HmacSHA1";
+ case Digest.SHA224:
+ return "HmacSHA224";
case Digest.SHA256:
return "HmacSHA256";
+ case Digest.SHA384:
+ return "HmacSHA384";
+ case Digest.SHA512:
+ return "HmacSHA512";
default:
throw new IllegalArgumentException(
"Unsupported HMAC digest: " + digest);
@@ -223,11 +255,32 @@ public abstract class KeyStoreKeyConstraints {
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
}
}
+
+ /**
+ * @hide
+ */
+ public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
+ switch (algorithm) {
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
+ default:
+ throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
+ }
+ }
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
- value = {Padding.NONE, Padding.PKCS7})
+ value = {
+ Padding.NONE,
+ Padding.PKCS7,
+ Padding.RSA_PKCS1_ENCRYPTION,
+ Padding.RSA_PKCS1_SIGNATURE,
+ Padding.RSA_OAEP,
+ Padding.RSA_PSS,
+ })
public @interface PaddingEnum {}
/**
@@ -247,6 +300,26 @@ public abstract class KeyStoreKeyConstraints {
public static final int PKCS7 = 1 << 1;
/**
+ * RSA PKCS#1 v1.5 padding for encryption/decryption.
+ */
+ public static final int RSA_PKCS1_ENCRYPTION = 1 << 2;
+
+ /**
+ * RSA PKCS#1 v1.5 padding for signatures.
+ */
+ public static final int RSA_PKCS1_SIGNATURE = 1 << 3;
+
+ /**
+ * RSA Optimal Asymmetric Encryption Padding (OAEP).
+ */
+ public static final int RSA_OAEP = 1 << 4;
+
+ /**
+ * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
+ */
+ public static final int RSA_PSS = 1 << 5;
+
+ /**
* @hide
*/
public static int toKeymaster(int padding) {
@@ -255,6 +328,14 @@ public abstract class KeyStoreKeyConstraints {
return KeymasterDefs.KM_PAD_NONE;
case PKCS7:
return KeymasterDefs.KM_PAD_PKCS7;
+ case RSA_PKCS1_ENCRYPTION:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ case RSA_PKCS1_SIGNATURE:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
+ case RSA_OAEP:
+ return KeymasterDefs.KM_PAD_RSA_OAEP;
+ case RSA_PSS:
+ return KeymasterDefs.KM_PAD_RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -269,6 +350,14 @@ public abstract class KeyStoreKeyConstraints {
return NONE;
case KeymasterDefs.KM_PAD_PKCS7:
return PKCS7;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return RSA_PKCS1_ENCRYPTION;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
+ return RSA_PKCS1_SIGNATURE;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ return RSA_OAEP;
+ case KeymasterDefs.KM_PAD_RSA_PSS:
+ return RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -283,6 +372,14 @@ public abstract class KeyStoreKeyConstraints {
return "NONE";
case PKCS7:
return "PKCS#7";
+ case RSA_PKCS1_ENCRYPTION:
+ return "RSA PKCS#1 (encryption)";
+ case RSA_PKCS1_SIGNATURE:
+ return "RSA PKCS#1 (signature)";
+ case RSA_OAEP:
+ return "RSA OAEP";
+ case RSA_PSS:
+ return "RSA PSS";
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -291,12 +388,18 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
- public static @PaddingEnum int fromJCAPadding(String padding) {
+ public static @PaddingEnum int fromJCACipherPadding(String padding) {
String paddingLower = padding.toLowerCase(Locale.US);
if ("nopadding".equals(paddingLower)) {
return NONE;
} else if ("pkcs7padding".equals(paddingLower)) {
return PKCS7;
+ } else if ("pkcs1padding".equals(paddingLower)) {
+ return RSA_PKCS1_ENCRYPTION;
+ } else if (("oaeppadding".equals(paddingLower))
+ || ((paddingLower.startsWith("oaepwith"))
+ && (paddingLower.endsWith("padding")))) {
+ return RSA_OAEP;
} else {
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -592,6 +695,14 @@ public abstract class KeyStoreKeyConstraints {
public static final int GCM = 1 << 3;
/**
+ * Set of block modes compatible with IND-CPA if used correctly.
+ *
+ * @hide
+ */
+ public static final @BlockModeEnum int IND_CPA_COMPATIBLE_MODES =
+ CBC | CTR | GCM;
+
+ /**
* @hide
*/
public static int toKeymaster(@BlockModeEnum int mode) {
@@ -670,6 +781,24 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
+ public static String allToString(@BlockModeEnum int modes) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@BlockModeEnum int mode : getSetFlags(modes)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(mode));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
public static @BlockModeEnum int fromJCAMode(String mode) {
String modeLower = mode.toLowerCase(Locale.US);
if ("ecb".equals(modeLower)) {
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index 279acd66d294..b39d16d1aa81 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -138,13 +138,26 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
}
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
- int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = spec.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new IllegalStateException(
+ "Randomized encryption (IND-CPA) required but may be violated by block"
+ + " mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyGeneratorSpec documentation.");
+ }
+ }
+
for (int keymasterPurpose :
KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -177,8 +190,8 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed due to the Cipher abstraction.
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 0b2f9b69b2f1..751eef5b42e8 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -19,13 +19,14 @@ package android.security;
import android.content.Context;
import java.security.Key;
-import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
import java.util.Date;
+import javax.crypto.Cipher;
+
/**
- * This provides the optional parameters that can be specified for
- * {@code KeyStore} entries that work with
+ * Parameters specifying how to secure and restrict the use of a key being
+ * imported into the
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
* facility</a>. The Android KeyStore facility is accessed through a
* {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
@@ -36,12 +37,6 @@ import java.util.Date;
* there is only one logical instance of the {@code KeyStore} per application
* UID so apps using the {@code sharedUid} facility will also share a
* {@code KeyStore}.
- * <p>
- * Keys may be generated using the {@link KeyPairGenerator} facility with a
- * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A
- * self-signed X.509 certificate will be attached to generated entries, but that
- * may be replaced at a later time by a certificate signed by a real Certificate
- * Authority.
*/
public final class KeyStoreParameter implements ProtectionParameter {
private int mFlags;
@@ -52,6 +47,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -64,6 +60,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.DigestEnum Integer digests,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -81,6 +78,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
mPaddings = paddings;
mDigests = digests;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -188,6 +186,21 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -251,6 +264,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -287,7 +301,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -301,7 +315,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -318,7 +332,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -333,7 +347,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -398,6 +412,47 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
+ * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
+ * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
+ * encrypting, to ensure that only random IVs are used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -465,6 +520,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
mPaddings,
mDigests,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 0a210d6927dc..a4100a2d44fb 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -45,8 +45,9 @@
/**
* Other constants:
*/
-// For the edge of the penumbra, the opacity is 0.
-#define OUTER_OPACITY (0.0f)
+// For the edge of the penumbra, the opacity is 0. After transform (1 - alpha),
+// it is 1.
+#define TRANSFORMED_OUTER_OPACITY (1.0f)
// Once the alpha difference is greater than this threshold, we will allocate extra
// edge vertices.
@@ -83,11 +84,13 @@ inline float getAlphaFromFactoredZ(float factoredZ) {
return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
}
+// The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform
+// the alpha value to (1 - alpha)
inline float getTransformedAlphaFromAlpha(float alpha) {
- return acosf(1.0f - 2.0f * alpha);
+ return 1.0f - alpha;
}
-// The output is ranged from 0 to M_PI.
+// The output is ranged from 0 to 1.
inline float getTransformedAlphaFromFactoredZ(float factoredZ) {
return getTransformedAlphaFromAlpha(getAlphaFromFactoredZ(factoredZ));
}
@@ -249,7 +252,7 @@ void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
indexBuffer[indexBufferIndex++] = currentInnerVertexIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x,
- outerVertex.y, OUTER_OPACITY);
+ outerVertex.y, TRANSFORMED_OUTER_OPACITY);
if (j == 0) {
outerStart = outerVertex;
@@ -285,7 +288,7 @@ void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
(outerLast * startWeight + outerNext * k) / extraVerticesNumber;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x,
- currentOuter.y, OUTER_OPACITY);
+ currentOuter.y, TRANSFORMED_OUTER_OPACITY);
if (!isCasterOpaque) {
umbraVertices[umbraIndex++] = vertexBufferIndex;
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index e9b22e20bc18..41adda15f367 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -240,8 +240,9 @@ const char* gFS_Main_ModulateColor =
const char* gFS_Main_ApplyVertexAlphaLinearInterp =
" fragColor *= alpha;\n";
const char* gFS_Main_ApplyVertexAlphaShadowInterp =
- " fragColor *= (1.0 - cos(alpha)) / 2.0;\n";
-
+ // Use a gaussian function for the shadow fall off. Note that alpha here
+ // is actually (1.0 - alpha) for saving computation.
+ " fragColor *= exp(- alpha * alpha * 4.0) - 0.018;\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
" fragColor = texture2D(baseSampler, outTexCoords);\n",
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index b3b06d672dc7..db3c2d9a5060 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -44,6 +44,9 @@
// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals.
#define SPOT_CORNER_RADIANS_DIVISOR (M_PI / SPOT_EXTRA_CORNER_VERTEX_PER_PI)
+// For performance, we use (1 - alpha) value for the shader input.
+#define TRANSFORMED_PENUMBRA_ALPHA 1.0f
+#define TRANSFORMED_UMBRA_ALPHA 0.0f
#include <math.h>
#include <stdlib.h>
@@ -964,11 +967,11 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength
// Fill the IB and VB for the penumbra area.
for (int i = 0; i < newPenumbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x,
- newPenumbra[i].y, 0.0f);
+ newPenumbra[i].y, TRANSFORMED_PENUMBRA_ALPHA);
}
for (int i = 0; i < umbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y,
- M_PI);
+ TRANSFORMED_UMBRA_ALPHA);
}
for (int i = 0; i < verticesPairIndex; i++) {
@@ -1008,14 +1011,14 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++],
- closerVertex.x, closerVertex.y, M_PI);
+ closerVertex.x, closerVertex.y, TRANSFORMED_UMBRA_ALPHA);
}
} else {
// If there is no occluded umbra at all, then draw the triangle fan
// starting from the centroid to all umbra vertices.
int lastCentroidIndex = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x,
- centroid.y, M_PI);
+ centroid.y, TRANSFORMED_UMBRA_ALPHA);
for (int i = 0; i < umbraLength; i++) {
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = lastCentroidIndex;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index fd7fca6ee6da..e028e3f349b3 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -325,6 +325,13 @@ final public class MediaCodec {
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ /**
+ * This indicates that the codec is released because the media resources used by the codec
+ * have been reclaimed, for example by the resource manager.
+ * This is used by the {@link Callback#onCodecReleased} callback.
+ */
+ public static final int REASON_RECLAIMED = 1;
+
private EventHandler mEventHandler;
private Callback mCallback;
@@ -335,6 +342,7 @@ final public class MediaCodec {
private static final int CB_OUTPUT_AVAILABLE = 2;
private static final int CB_ERROR = 3;
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
+ private static final int CB_CODEC_RELEASED = 5;
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -405,6 +413,13 @@ final public class MediaCodec {
break;
}
+ case CB_CODEC_RELEASED:
+ {
+ int reason = msg.arg2;
+ mCallback.onCodecReleased(mCodec, reason);
+ break;
+ }
+
default:
{
break;
@@ -720,6 +735,7 @@ final public class MediaCodec {
}
/* Must be in sync with android_media_MediaCodec.cpp */
+ private final static int ACTION_FATAL = 0;
private final static int ACTION_TRANSIENT = 1;
private final static int ACTION_RECOVERABLE = 2;
@@ -1654,6 +1670,22 @@ final public class MediaCodec {
* @param format The new output format.
*/
public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
+
+ /**
+ * Called when the underlying codec component has been released.
+ * <p>
+ * At this point the MediaCodec must be released, as it has moved to terminal
+ * Uninitialized state.
+ *
+ * @param codec The MediaCodec object.
+ * @param reason The reason of the release.
+ */
+ public void onCodecReleased(MediaCodec codec, int reason) {
+ int errorCode = -1;
+ String detailMessage = "resources reclaimed";
+ onError(codec,
+ new CodecException(errorCode, CodecException.ACTION_FATAL, detailMessage));
+ }
}
private void postEventFromNative(
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 069f7ff0d3a0..fc5fc4373270 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -17,9 +17,10 @@
package android.media;
import java.lang.ref.WeakReference;
-import java.util.UUID;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
import android.annotation.SystemApi;
import android.os.Handler;
import android.os.Looper;
@@ -98,12 +99,14 @@ import android.util.Log;
*/
public final class MediaDrm {
- private final static String TAG = "MediaDrm";
+ private static final String TAG = "MediaDrm";
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private EventHandler mEventHandler;
private OnEventListener mOnEventListener;
+ private OnKeysChangeListener mOnKeysChangeListener;
+ private OnExpirationUpdateListener mOnExpirationUpdateListener;
private long mNativeContext;
@@ -227,6 +230,148 @@ public final class MediaDrm {
}
/**
+ * Register a callback to be invoked when a session expiration update
+ * occurs. The app's OnExpirationUpdateListener will be notified
+ * when the expiration time of the keys in the session have changed.
+ * @param listener the callback that will be run
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnExpirationUpdateListener(OnExpirationUpdateListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnExpirationUpdateListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a drm session
+ * expiration update occurs
+ */
+ public interface OnExpirationUpdateListener
+ {
+ /**
+ * Called when a session expiration update occurs, to inform the app
+ * about the change in expiration time
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param expirationTime the new expiration time for the keys in the session.
+ * The time is in milliseconds, relative to the Unix epoch.
+ */
+ void onExpirationUpdate(MediaDrm md, byte[] sessionId, long expirationTime);
+ }
+
+ /**
+ * Register a callback to be invoked when the state of keys in a session
+ * change, e.g. when a license update occurs or when a license expires.
+ *
+ * @param listener the callback that will be run when key status changes
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnKeysChangeListener(OnKeysChangeListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnKeysChangeListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the keys in a drm
+ * session change states.
+ */
+ public interface OnKeysChangeListener
+ {
+ /**
+ * Called when the keys in a session change status, such as when the license
+ * is renewed or expires.
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param keyInformation a list of {@link MediaDrm.KeyStatus}
+ * instances indicating the status for each key in the session
+ * @param hasNewUsableKey indicates if a key has been added that is usable,
+ * which may trigger an attempt to resume playback on the media stream
+ * if it is currently blocked waiting for a key.
+ */
+ void onKeysChange(MediaDrm md, byte[] sessionId, List<KeyStatus> keyInformation,
+ boolean hasNewUsableKey);
+ }
+
+ /**
+ * The key is currently usable to decrypt media data
+ */
+ public static final int KEY_STATUS_USABLE = 0;
+
+ /**
+ * The key is no longer usable to decrypt media data because its
+ * expiration time has passed.
+ */
+ public static final int KEY_STATUS_EXPIRED = 1;
+
+ /**
+ * The key is not currently usable to decrypt media data because its
+ * output requirements cannot currently be met.
+ */
+ public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
+
+ /**
+ * The status of the key is not yet known and is being determined.
+ * The status will be updated with the actual status when it has
+ * been determined.
+ */
+ public static final int KEY_STATUS_PENDING = 3;
+
+ /**
+ * The key is not currently usable to decrypt media data because of an
+ * internal error in processing unrelated to input parameters. This error
+ * is not actionable by an app.
+ */
+ public static final int KEY_STATUS_INTERNAL_ERROR = 4;
+
+
+ /**
+ * Defines the status of a key.
+ * A KeyStatus for each key in a session is provided to the
+ * {@link OnKeysChangeListener#onKeysChange}
+ * listener.
+ */
+ public static final class KeyStatus {
+ private final byte[] mKeyId;
+ private final int mStatusCode;
+
+ KeyStatus(byte[] keyId, int statusCode) {
+ mKeyId = keyId;
+ mStatusCode = statusCode;
+ }
+
+ /**
+ * Returns the status code for the key
+ */
+ public int getStatusCode() { return mStatusCode; }
+
+ /**
+ * Returns the id for the key
+ */
+ public byte[] getKeyId() { return mKeyId; }
+ }
+
+ /**
* Register a callback to be invoked when an event occurs
*
* @param listener the callback that will be run
@@ -289,6 +434,8 @@ public final class MediaDrm {
public static final int EVENT_SESSION_RECLAIMED = 5;
private static final int DRM_EVENT = 200;
+ private static final int EXPIRATION_UPDATE = 201;
+ private static final int KEYS_CHANGE = 202;
private class EventHandler extends Handler
{
@@ -308,8 +455,6 @@ public final class MediaDrm {
switch(msg.what) {
case DRM_EVENT:
- Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
-
if (mOnEventListener != null) {
if (msg.obj != null && msg.obj instanceof Parcel) {
Parcel parcel = (Parcel)msg.obj;
@@ -321,11 +466,46 @@ public final class MediaDrm {
if (data.length == 0) {
data = null;
}
+
+ Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
}
}
return;
+ case KEYS_CHANGE:
+ if (mOnKeysChangeListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel);
+ boolean hasNewUsableKey = (parcel.readInt() != 0);
+
+ Log.i(TAG, "Drm keys change");
+ mOnKeysChangeListener.onKeysChange(mMediaDrm, sessionId, keyStatusList,
+ hasNewUsableKey);
+ }
+ }
+ }
+ return;
+
+ case EXPIRATION_UPDATE:
+ if (mOnExpirationUpdateListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ long expirationTime = parcel.readLong();
+
+ Log.i(TAG, "Drm key expiration update: " + expirationTime);
+ mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId,
+ expirationTime);
+ }
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -333,7 +513,21 @@ public final class MediaDrm {
}
}
- /*
+ /**
+ * Parse a list of KeyStatus objects from an event parcel
+ */
+ private List<KeyStatus> keyStatusListFromParcel(Parcel parcel) {
+ int nelems = parcel.readInt();
+ List<KeyStatus> keyStatusList = new ArrayList(nelems);
+ while (nelems-- > 0) {
+ byte[] keyId = parcel.createByteArray();
+ int keyStatusCode = parcel.readInt();
+ keyStatusList.add(new KeyStatus(keyId, keyStatusCode));
+ }
+ return keyStatusList;
+ }
+
+ /**
* This method is called from native code when an event occurs. This method
* just uses the EventHandler system to post the event back to the main app thread.
* We use a weak reference to the original MediaPlayer object so that the native
@@ -341,14 +535,14 @@ public final class MediaDrm {
* the cookie passed to native_setup().)
*/
private static void postEventFromNative(Object mediadrm_ref,
- int eventType, int extra, Object obj)
+ int what, int eventType, int extra, Object obj)
{
- MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
+ MediaDrm md = (MediaDrm)((WeakReference<MediaDrm>)mediadrm_ref).get();
if (md == null) {
return;
}
if (md.mEventHandler != null) {
- Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj);
+ Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
md.mEventHandler.sendMessage(m);
}
}
@@ -404,7 +598,7 @@ public final class MediaDrm {
/**
* Contains the opaque data an app uses to request keys from a license server
*/
- public final static class KeyRequest {
+ public static final class KeyRequest {
private byte[] mData;
private String mDefaultUrl;
private int mRequestType;
@@ -521,7 +715,7 @@ public final class MediaDrm {
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*/
- public final static class ProvisionRequest {
+ public static final class ProvisionRequest {
ProvisionRequest() {}
/**
@@ -812,7 +1006,7 @@ public final class MediaDrm {
*
* @hide - not part of the public API at this time
*/
- public final static class CertificateRequest {
+ public static final class CertificateRequest {
private byte[] mData;
private String mDefaultUrl;
@@ -860,7 +1054,7 @@ public final class MediaDrm {
*
* @hide - not part of the public API at this time
*/
- public final static class Certificate {
+ public static final class Certificate {
Certificate() {}
/**
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 642078aa669a..96d12fdedf05 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.media.midi.MidiDeviceInfo;
import android.os.ParcelFileDescriptor;
/** @hide */
@@ -27,4 +28,6 @@ interface IMidiDeviceServer
// connects the input port pfd to the specified output port
void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
+
+ MidiDeviceInfo getDeviceInfo();
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index bc85f92c9e36..a316a44d4f74 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -252,6 +252,11 @@ public final class MidiDeviceServer implements Closeable {
mPortClients.put(token, client);
}
}
+
+ @Override
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
};
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
@@ -279,6 +284,10 @@ public final class MidiDeviceServer implements Closeable {
return mServer;
}
+ public IBinder asBinder() {
+ return mServer.asBinder();
+ }
+
/* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
if (mDeviceInfo != null) {
throw new IllegalStateException("setDeviceInfo should only be called once");
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 1d3b37a3781b..ff16a5704546 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -83,7 +83,18 @@ public final class MidiInputPort extends MidiReceiver implements Closeable {
if (mOutputStream == null) {
throw new IOException("MidiInputPort is closed");
}
- int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+ int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
+ int length = MidiPortImpl.packFlush(mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d62b2dc85187..0ba17447d252 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +43,24 @@ import java.util.HashMap;
public final class MidiManager {
private static final String TAG = "MidiManager";
+ /**
+ * Intent for starting BluetoothMidiService
+ * @hide
+ */
+ public static final String BLUETOOTH_MIDI_SERVICE_INTENT =
+ "android.media.midi.BluetoothMidiService";
+
+ /**
+ * BluetoothMidiService package name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice";
+
+ /**
+ * BluetoothMidiService class name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_CLASS =
+ "com.android.bluetoothmidiservice.BluetoothMidiService";
+
private final Context mContext;
private final IMidiManager mService;
private final IBinder mToken = new Binder();
@@ -145,6 +164,19 @@ public final class MidiManager {
}
/**
+ * Callback class used for receiving the results of {@link #openBluetoothDevice}
+ */
+ abstract public static class BluetoothOpenCallback {
+ /**
+ * Called to respond to a {@link #openBluetoothDevice} request
+ *
+ * @param bluetoothDevice the {@link android.bluetooth.BluetoothDevice} to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(BluetoothDevice bluetoothDevice, MidiDevice device);
+ }
+
+ /**
* @hide
*/
public MidiManager(Context context, IMidiManager service) {
@@ -214,6 +246,19 @@ public final class MidiManager {
}
}
+ private void sendBluetoothDeviceResponse(final BluetoothDevice bluetoothDevice,
+ final MidiDevice device, final BluetoothOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
@@ -260,7 +305,7 @@ public final class MidiManager {
// return immediately to avoid calling sendOpenDeviceResponse below
return;
} else {
- Log.e(TAG, "Unable to bind service: " + intent);
+ Log.e(TAG, "Unable to bind service: " + intent);
}
}
} else {
@@ -272,6 +317,51 @@ public final class MidiManager {
sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
+ /**
+ * Opens a Bluetooth MIDI device for reading and writing.
+ *
+ * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
+ * @param callback a {@link MidiManager.BluetoothOpenCallback} to be called to receive the
+ * result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
+ */
+ public void openBluetoothDevice(final BluetoothDevice bluetoothDevice,
+ final BluetoothOpenCallback callback, final Handler handler) {
+ Intent intent = new Intent(BLUETOOTH_MIDI_SERVICE_INTENT);
+ intent.setComponent(new ComponentName(BLUETOOTH_MIDI_SERVICE_PACKAGE,
+ BLUETOOTH_MIDI_SERVICE_CLASS));
+ intent.putExtra("device", bluetoothDevice);
+ if (!mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ try {
+ // fetch MidiDeviceInfo from the server
+ MidiDeviceInfo deviceInfo = server.getDeviceInfo();
+ MidiDevice device = new MidiDevice(deviceInfo, server, mContext, this);
+ sendBluetoothDeviceResponse(bluetoothDevice, device, callback, handler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception in onServiceConnected");
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0290a7634883..7491f3cf2feb 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -62,12 +62,24 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
// FIXME - inform receivers here?
}
- int offset = MidiPortImpl.getMessageOffset(buffer, count);
- int size = MidiPortImpl.getMessageSize(buffer, count);
- long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
-
- // dispatch to all our receivers
- mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ int packetType = MidiPortImpl.getPacketType(buffer, count);
+ switch (packetType) {
+ case MidiPortImpl.PACKET_TYPE_DATA: {
+ int offset = MidiPortImpl.getDataOffset(buffer, count);
+ int size = MidiPortImpl.getDataSize(buffer, count);
+ long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
+
+ // dispatch to all our receivers
+ mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ break;
+ }
+ case MidiPortImpl.PACKET_TYPE_FLUSH:
+ mDispatcher.flush();
+ break;
+ default:
+ Log.e(TAG, "Unknown packet type " + packetType);
+ break;
+ }
}
} catch (IOException e) {
// FIXME report I/O failure?
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 5795045edc7d..16fc2145002a 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -24,6 +24,16 @@ package android.media.midi;
private static final String TAG = "MidiPort";
/**
+ * Packet type for data packet
+ */
+ public static final int PACKET_TYPE_DATA = 1;
+
+ /**
+ * Packet type for flush packet
+ */
+ public static final int PACKET_TYPE_FLUSH = 2;
+
+ /**
* Maximum size of a packet that can pass through our ParcelFileDescriptor.
*/
public static final int MAX_PACKET_SIZE = 1024;
@@ -34,12 +44,17 @@ package android.media.midi;
private static final int TIMESTAMP_SIZE = 8;
/**
+ * Data packet overhead is timestamp size plus packet type byte
+ */
+ private static final int DATA_PACKET_OVERHEAD = TIMESTAMP_SIZE + 1;
+
+ /**
* Maximum amount of MIDI data that can be included in a packet
*/
- public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
/**
- * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
@@ -47,46 +62,65 @@ package android.media.midi;
* dest is buffer to pack into
* returns size of packed message
*/
- public static int packMessage(byte[] message, int offset, int size, long timestamp,
+ public static int packData(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
- if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
- size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ if (size > MAX_PACKET_DATA_SIZE) {
+ size = MAX_PACKET_DATA_SIZE;
}
- // message data goes first
- System.arraycopy(message, offset, dest, 0, size);
+ int length = 0;
+ // packet type goes first
+ dest[length++] = PACKET_TYPE_DATA;
+ // data goes next
+ System.arraycopy(message, offset, dest, length, size);
+ length += size;
// followed by timestamp
for (int i = 0; i < TIMESTAMP_SIZE; i++) {
- dest[size++] = (byte)timestamp;
+ dest[length++] = (byte)timestamp;
timestamp >>= 8;
}
- return size;
+ return length;
+ }
+
+ /**
+ * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+ */
+ public static int packFlush(byte[] dest) {
+ dest[0] = PACKET_TYPE_FLUSH;
+ return 1;
+ }
+
+ /**
+ * Returns the packet type (PACKET_TYPE_DATA or PACKET_TYPE_FLUSH)
+ */
+ public static int getPacketType(byte[] buffer, int bufferLength) {
+ return buffer[0];
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns the offset of the MIDI message in packed buffer
*/
- public static int getMessageOffset(byte[] buffer, int bufferLength) {
- // message is at the beginning
- return 0;
+ public static int getDataOffset(byte[] buffer, int bufferLength) {
+ // data follows packet type byte
+ return 1;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns size of MIDI data in packed buffer
*/
- public static int getMessageSize(byte[] buffer, int bufferLength) {
+ public static int getDataSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp
- return bufferLength - TIMESTAMP_SIZE;
+ return bufferLength - DATA_PACKET_OVERHEAD;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*/
- public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
// timestamp is at end of the packet
int offset = bufferLength;
long timestamp = 0;
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 6f4c266a7e31..d06907551a27 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -42,6 +42,13 @@ abstract public class MidiReceiver {
throws IOException;
/**
+ * Instructs the receiver to discard all pending events.
+ * @throws IOException
+ */
+ public void flush() throws IOException {
+ }
+
+ /**
* Returns the maximum size of a message this receiver can receive.
* Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
* @return maximum message size
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 16758d05327a..71457b75472b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -669,6 +669,14 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
break;
}
+ case MediaCodec::CB_CODEC_RELEASED:
+ {
+ if (!msg->findInt32("reason", &arg2)) {
+ arg2 = MediaCodec::REASON_UNKNOWN;
+ }
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 96d71339cee9..f8146a79b60d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -96,6 +96,12 @@ struct EventTypes {
jint kEventSessionReclaimed;
} gEventTypes;
+struct EventWhat {
+ jint kWhatDrmEvent;
+ jint kWhatExpirationUpdate;
+ jint kWhatKeysChange;
+} gEventWhat;
+
struct KeyTypes {
jint kKeyTypeStreaming;
jint kKeyTypeOffline;
@@ -186,25 +192,37 @@ JNIDrmListener::~JNIDrmListener()
void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
const Parcel *obj)
{
- jint jeventType;
+ jint jwhat;
+ jint jeventType = 0;
// translate DrmPlugin event types into their java equivalents
switch (eventType) {
case DrmPlugin::kDrmPluginEventProvisionRequired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventProvisionRequired;
break;
case DrmPlugin::kDrmPluginEventKeyNeeded:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyRequired;
break;
case DrmPlugin::kDrmPluginEventKeyExpired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyExpired;
break;
case DrmPlugin::kDrmPluginEventVendorDefined:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventVendorDefined;
break;
case DrmPlugin::kDrmPluginEventSessionReclaimed:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventSessionReclaimed;
break;
+ case DrmPlugin::kDrmPluginEventExpirationUpdate:
+ jwhat = gEventWhat.kWhatExpirationUpdate;
+ break;
+ case DrmPlugin::kDrmPluginEventKeysChange:
+ jwhat = gEventWhat.kWhatKeysChange;
+ break;
default:
ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
return;
@@ -217,7 +235,7 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
nativeParcel->setData(obj->data(), obj->dataSize());
env->CallStaticVoidMethod(mClass, gFields.post_event, mObject,
- jeventType, extra, jParcel);
+ jwhat, jeventType, extra, jParcel);
env->DeleteLocalRef(jParcel);
}
}
@@ -573,7 +591,7 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
FIND_CLASS(clazz, "android/media/MediaDrm");
GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "J");
GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative",
- "(Ljava/lang/Object;IILjava/lang/Object;)V");
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
jfieldID field;
GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I");
@@ -587,6 +605,13 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I");
gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "DRM_EVENT", "I");
+ gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I");
+ gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEYS_CHANGE", "I");
+ gEventWhat.kWhatKeysChange = env->GetStaticIntField(clazz, field);
+
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
@@ -837,7 +862,7 @@ static jobject android_media_MediaDrm_getKeyRequest(
env->SetIntField(keyObj, gFields.keyRequest.requestType,
gKeyRequestTypes.kKeyRequestTypeRelease);
break;
- case DrmPlugin::kKeyRequestType_Unknown:
+ default:
throwStateException(env, "DRM plugin failure: unknown key request type",
ERROR_DRM_UNKNOWN);
break;
diff --git a/media/packages/BluetoothMidiService/Android.mk b/media/packages/BluetoothMidiService/Android.mk
new file mode 100644
index 000000000000..2c9c3c5615e2
--- /dev/null
+++ b/media/packages/BluetoothMidiService/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BluetoothMidiService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
new file mode 100644
index 000000000000..15aa581074ab
--- /dev/null
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.bluetoothmidiservice"
+ >
+
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
+
+ <application
+ android:label="@string/app_name">
+ <service android:name="BluetoothMidiService">
+ <intent-filter>
+ <action android:name="android.media.midi.BluetoothMidiService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/media/packages/BluetoothMidiService/res/values/strings.xml b/media/packages/BluetoothMidiService/res/values/strings.xml
new file mode 100644
index 000000000000..c98e56ca1526
--- /dev/null
+++ b/media/packages/BluetoothMidiService/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_name">Bluetooth MIDI Service</string>
+</resources>
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
new file mode 100644
index 000000000000..8d194e54169c
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDeviceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Class used to implement a Bluetooth MIDI device.
+ */
+public final class BluetoothMidiDevice {
+
+ private static final String TAG = "BluetoothMidiDevice";
+
+ private static final int MAX_PACKET_SIZE = 20;
+
+ // Bluetooth MIDI Gatt service UUID
+ private static final UUID MIDI_SERVICE = UUID.fromString(
+ "03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
+ // Bluetooth MIDI Gatt characteristic UUID
+ private static final UUID MIDI_CHARACTERISTIC = UUID.fromString(
+ "7772E5DB-3868-4112-A1A9-F2669D106BF3");
+ // Descriptor UUID for enabling characteristic changed notifications
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(
+ "00002902-0000-1000-8000-00805f9b34fb");
+
+ private final BluetoothDevice mBluetoothDevice;
+ private final BluetoothMidiService mService;
+ private final MidiManager mMidiManager;
+ private MidiReceiver mOutputReceiver;
+ private final MidiEventScheduler mEventScheduler = new MidiEventScheduler();
+
+ private MidiDeviceServer mDeviceServer;
+ private BluetoothGatt mBluetoothGatt;
+
+ private BluetoothGattCharacteristic mCharacteristic;
+
+ // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder
+ private final PacketReceiver mPacketReceiver = new PacketReceiver();
+
+ private final BluetoothPacketEncoder mPacketEncoder
+ = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE);
+
+ private final BluetoothPacketDecoder mPacketDecoder
+ = new BluetoothPacketDecoder(MAX_PACKET_SIZE);
+
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status,
+ int newState) {
+ String intentAction;
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ Log.i(TAG, "Connected to GATT server.");
+ Log.i(TAG, "Attempting to start service discovery:" +
+ mBluetoothGatt.discoverServices());
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnected from GATT server.");
+ // FIXME synchronize?
+ close();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ List<BluetoothGattService> services = mBluetoothGatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (MIDI_SERVICE.equals(service.getUuid())) {
+ Log.d(TAG, "found MIDI_SERVICE");
+ List<BluetoothGattCharacteristic> characteristics
+ = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ if (MIDI_CHARACTERISTIC.equals(characteristic.getUuid())) {
+ Log.d(TAG, "found MIDI_CHARACTERISTIC");
+ mCharacteristic = characteristic;
+
+ // Specification says to read the characteristic first and then
+ // switch to receiving notifications
+ mBluetoothGatt.readCharacteristic(characteristic);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ // FIXME - report error back to client?
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicRead " + status);
+
+ // switch to receiving notifications after initial characteristic read
+ mBluetoothGatt.setCharacteristicNotification(characteristic, true);
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ CLIENT_CHARACTERISTIC_CONFIG);
+ // FIXME null check
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicWrite " + status);
+ mPacketEncoder.writeComplete();
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+// logByteArray("Received ", characteristic.getValue(), 0,
+// characteristic.getValue().length);
+ mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
+ }
+ };
+
+ // This receives MIDI data that has already been passed through our MidiEventScheduler
+ // and has been normalized by our MidiFramer.
+
+ private class PacketReceiver implements PacketEncoder.PacketReceiver {
+ // buffers of every possible packet size
+ private final byte[][] mWriteBuffers;
+
+ public PacketReceiver() {
+ // Create buffers of every possible packet size
+ mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][];
+ for (int i = 0; i <= MAX_PACKET_SIZE; i++) {
+ mWriteBuffers[i] = new byte[i];
+ }
+ }
+
+ @Override
+ public void writePacket(byte[] buffer, int count) {
+ if (mCharacteristic == null) {
+ Log.w(TAG, "not ready to send packet yet");
+ return;
+ }
+ byte[] writeBuffer = mWriteBuffers[count];
+ System.arraycopy(buffer, 0, writeBuffer, 0, count);
+ mCharacteristic.setValue(writeBuffer);
+// logByteArray("Sent ", mCharacteristic.getValue(), 0,
+// mCharacteristic.getValue().length);
+ mBluetoothGatt.writeCharacteristic(mCharacteristic);
+ }
+ }
+
+ public BluetoothMidiDevice(Context context, BluetoothDevice device,
+ BluetoothMidiService service) {
+ mBluetoothDevice = device;
+ mService = service;
+
+ mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
+
+ mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+
+ Bundle properties = new Bundle();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName());
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE,
+ mBluetoothGatt.getDevice());
+
+ MidiReceiver[] inputPortReceivers = new MidiReceiver[1];
+ inputPortReceivers[0] = mEventScheduler.getReceiver();
+
+ mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
+ null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null);
+
+ mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
+
+ // This thread waits for outgoing messages from our MidiEventScheduler
+ // And forwards them to our MidiFramer to be prepared to send via Bluetooth.
+ new Thread("BluetoothMidiDevice " + mBluetoothDevice) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)mEventScheduler.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ mPacketEncoder.sendWithTimestamp(event.data, 0, event.count,
+ event.getTimestamp());
+ } catch (IOException e) {
+ Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e);
+ }
+ mEventScheduler.addEventToPool(event);
+ }
+ Log.d(TAG, "BluetoothMidiDevice thread exit");
+ }
+ }.start();
+ }
+
+ void close() {
+ mEventScheduler.close();
+ if (mDeviceServer != null) {
+ IoUtils.closeQuietly(mDeviceServer);
+ mDeviceServer = null;
+ mService.deviceClosed(mBluetoothDevice);
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+
+ public IBinder getBinder() {
+ return mDeviceServer.asBinder();
+ }
+
+ private static void logByteArray(String prefix, byte[] value, int offset, int count) {
+ StringBuilder builder = new StringBuilder(prefix);
+ for (int i = offset; i < count; i++) {
+ String hex = Integer.toHexString(value[i]);
+ int length = hex.length();
+ if (length == 1) {
+ hex = "0x" + hex;
+ } else {
+ hex = hex.substring(length - 2, length);
+ }
+ builder.append(hex);
+ if (i != value.length - 1) {
+ builder.append(", ");
+ }
+ }
+ Log.d(TAG, builder.toString());
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
new file mode 100644
index 000000000000..fbde2b4ccfb7
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.app.Service;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.media.midi.MidiManager;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public class BluetoothMidiService extends Service {
+ private static final String TAG = "BluetoothMidiService";
+
+ // BluetoothMidiDevices keyed by BluetoothDevice
+ private final HashMap<BluetoothDevice,BluetoothMidiDevice> mDeviceServerMap
+ = new HashMap<BluetoothDevice,BluetoothMidiDevice>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT.equals(intent.getAction())) {
+ BluetoothDevice bluetoothDevice = (BluetoothDevice)intent.getParcelableExtra("device");
+ if (bluetoothDevice == null) {
+ Log.e(TAG, "no BluetoothDevice in onBind intent");
+ return null;
+ }
+
+ BluetoothMidiDevice device;
+ synchronized (mDeviceServerMap) {
+ device = mDeviceServerMap.get(bluetoothDevice);
+ if (device == null) {
+ device = new BluetoothMidiDevice(this, bluetoothDevice, this);
+ }
+ }
+ return device.getBinder();
+ }
+ return null;
+ }
+
+ void deviceClosed(BluetoothDevice device) {
+ synchronized (mDeviceServerMap) {
+ mDeviceServerMap.remove(device);
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
new file mode 100644
index 000000000000..c5bfb5f697f8
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public class BluetoothPacketDecoder extends PacketDecoder {
+
+ private static final String TAG = "BluetoothPacketDecoder";
+
+ private final byte[] mBuffer;
+
+ private final int TIMESTAMP_MASK_HIGH = 0x1F80;
+ private final int TIMESTAMP_MASK_LOW = 0x7F;
+ private final int HEADER_TIMESTAMP_MASK = 0x3F;
+
+ public BluetoothPacketDecoder(int maxPacketSize) {
+ mBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void decodePacket(byte[] buffer, MidiReceiver receiver) {
+ int length = buffer.length;
+
+ // NOTE his code allows running status across packets,
+ // although the specification does not allow that.
+
+ if (length < 1) {
+ Log.e(TAG, "empty packet");
+ return;
+ }
+ byte header = buffer[0];
+ if ((header & 0xC0) != 0x80) {
+ Log.e(TAG, "packet does not start with header");
+ return;
+ }
+
+ // shift bits 0 - 5 to bits 7 - 12
+ int timestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
+ boolean lastWasTimestamp = false;
+ int dataCount = 0;
+ int previousLowTimestamp = 0;
+
+ // iterate through the rest of the packet, separating MIDI data from timestamps
+ for (int i = 1; i < buffer.length; i++) {
+ byte b = buffer[i];
+
+ if ((b & 0x80) != 0 && !lastWasTimestamp) {
+ lastWasTimestamp = true;
+ int lowTimestamp = b & TIMESTAMP_MASK_LOW;
+ int newTimestamp = (timestamp & TIMESTAMP_MASK_HIGH) | lowTimestamp;
+ if (lowTimestamp < previousLowTimestamp) {
+ newTimestamp = (newTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
+ }
+ previousLowTimestamp = lowTimestamp;
+
+ if (newTimestamp != timestamp) {
+ if (dataCount > 0) {
+ // send previous message separately since it has a different timestamp
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ dataCount = 0;
+ }
+ }
+ timestamp = newTimestamp;
+ } else {
+ lastWasTimestamp = false;
+ mBuffer[dataCount++] = b;
+ }
+ }
+
+ if (dataCount > 0) {
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
new file mode 100644
index 000000000000..463edcff596c
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+import com.android.internal.midi.MidiConstants;
+import com.android.internal.midi.MidiFramer;
+
+import java.io.IOException;
+
+/**
+ * This class accumulates MIDI messages to form a MIDI packet.
+ */
+public class BluetoothPacketEncoder extends PacketEncoder {
+
+ private static final String TAG = "BluetoothPacketEncoder";
+
+ private static final long MILLISECOND_NANOS = 1000000L;
+
+ // mask for generating 13 bit timestamps
+ private static final int MILLISECOND_MASK = 0x1FFF;
+
+ private final PacketReceiver mPacketReceiver;
+
+ // buffer for accumulating messages to write
+ private final byte[] mAccumulationBuffer;
+ // number of bytes currently in mAccumulationBuffer
+ private int mAccumulatedBytes;
+ // timestamp for first message in current packet
+ private int mPacketTimestamp;
+ // current running status, or zero if none
+ private int mRunningStatus;
+
+ private boolean mWritePending;
+
+ private final Object mLock = new Object();
+
+ // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
+ private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+
+ int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
+ int status = msg[0] & 0xFF;
+
+ synchronized (mLock) {
+ boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
+ int bytesNeeded = count;
+ if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
+ if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
+
+ if (mAccumulatedBytes + bytesNeeded > mAccumulationBuffer.length) {
+ // write out our data if there is no more room
+ // if necessary, block until previous packet is sent
+ flushLocked(true);
+ }
+
+ // write header if we are starting a new packet
+ if (mAccumulatedBytes == 0) {
+ // header byte with timestamp bits 7 - 12
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7));
+ mPacketTimestamp = milliTimestamp;
+ needsTimestamp = true;
+ }
+
+ // write new timestamp byte and status byte if necessary
+ if (needsTimestamp) {
+ // timestamp byte with bits 0 - 6 of timestamp
+ mAccumulationBuffer[mAccumulatedBytes++] =
+ (byte)(0x80 | (milliTimestamp & 0x7F));
+ mPacketTimestamp = milliTimestamp;
+ }
+
+ if (status != mRunningStatus) {
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)status;
+ if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = status;
+ } else if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = 0;
+ }
+ }
+
+ // now copy data bytes
+ int dataLength = count - 1;
+ System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
+ // FIXME - handle long SysEx properly
+ mAccumulatedBytes += dataLength;
+
+ // write the packet if possible, but do not block
+ flushLocked(false);
+ }
+ }
+ };
+
+ // MidiFramer for normalizing incoming data
+ private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
+
+ public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) {
+ mPacketReceiver = packetReceiver;
+ mAccumulationBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ // normalize the data by passing it through a MidiFramer first
+ mMidiFramer.sendWithTimestamp(msg, offset, count, timestamp);
+ }
+
+ @Override
+ public void writeComplete() {
+ synchronized (mLock) {
+ mWritePending = false;
+ flushLocked(false);
+ mLock.notify();
+ }
+ }
+
+ private void flushLocked(boolean canBlock) {
+ if (mWritePending && !canBlock) {
+ return;
+ }
+
+ while (mWritePending && mAccumulatedBytes > 0) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ }
+
+ if (mAccumulatedBytes > 0) {
+ mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes);
+ mAccumulatedBytes = 0;
+ mPacketTimestamp = 0;
+ mRunningStatus = 0;
+ mWritePending = true;
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
new file mode 100644
index 000000000000..da4b63abad23
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public abstract class PacketDecoder {
+
+ /**
+ * Decodes MIDI data in a packet and passes it to a {@link android.media.midi.MidiReceiver}
+ * @param buffer the packet to decode
+ * @param receiver the {@link android.media.midi.MidiReceiver} to receive the decoded MIDI data
+ */
+ abstract public void decodePacket(byte[] buffer, MidiReceiver receiver);
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
new file mode 100644
index 000000000000..12c8b9b237da
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that encodes MIDI data into a packet buffer.
+ * PacketEncoder receives data via its {@link android.media.midi.MidiReceiver#onReceive} method
+ * and notifies its client of packets to write via the {@link PacketEncoder.PacketReceiver}
+ * interface.
+ */
+public abstract class PacketEncoder extends MidiReceiver {
+
+ public interface PacketReceiver {
+ /** Called to write an accumulated packet.
+ * @param buffer the packet buffer to write
+ * @param count the number of bytes in the packet buffer to write
+ */
+ public void writePacket(byte[] buffer, int count);
+ }
+
+ /**
+ * Called to inform PacketEncoder when the previous write is complete.
+ */
+ abstract public void writeComplete();
+}
diff --git a/packages/StatementService/Android.mk b/packages/StatementService/Android.mk
index f0adb1cd1ca8..470d824a1868 100644
--- a/packages/StatementService/Android.mk
+++ b/packages/StatementService/Android.mk
@@ -24,6 +24,8 @@ LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PACKAGE_NAME := StatementService
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
+
LOCAL_STATIC_JAVA_LIBRARIES := \
libprotobuf-java-nano \
volley
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 96840a2703eb..1bed4f37d1f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2209,7 +2209,10 @@ public class BackupManagerService {
// Get the restore-set token for the best-available restore set for this package:
// the active set if possible, else the ancestral one. Returns zero if none available.
- long getAvailableRestoreToken(String packageName) {
+ public long getAvailableRestoreToken(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getAvailableRestoreToken");
+
long token = mAncestralToken;
synchronized (mQueueLock) {
if (mEverStoredApps.contains(packageName)) {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 99bbdae6e7d7..5859c6a223d0 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -317,6 +317,12 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
+ public long getAvailableRestoreToken(String packageName) {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 7d156df5054e..34e8b7867f10 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -34,6 +34,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -61,6 +62,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Random;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -128,6 +130,7 @@ class AlarmManagerService extends SystemService {
final ResultReceiver mResultReceiver = new ResultReceiver();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
+ Random mRandom;
boolean mInteractive = true;
long mNonInteractiveStartTime;
long mNonInteractiveTime;
@@ -185,18 +188,20 @@ class AlarmManagerService extends SystemService {
final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
- boolean standalone; // certain "batches" don't participate in coalescing
+ int flags; // Flags for alarms, such as FLAG_STANDALONE.
final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
Batch() {
start = 0;
end = Long.MAX_VALUE;
+ flags = 0;
}
Batch(Alarm seed) {
start = seed.whenElapsed;
- end = seed.maxWhen;
+ end = seed.maxWhenElapsed;
+ flags = seed.flags;
alarms.add(seed);
}
@@ -227,9 +232,10 @@ class AlarmManagerService extends SystemService {
start = alarm.whenElapsed;
newStart = true;
}
- if (alarm.maxWhen < end) {
- end = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < end) {
+ end = alarm.maxWhenElapsed;
}
+ flags |= alarm.flags;
if (DEBUG_BATCH) {
Slog.v(TAG, " => now " + this);
@@ -241,6 +247,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.equals(operation)) {
@@ -253,9 +260,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -263,6 +271,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -271,6 +280,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.getTargetPackage().equals(packageName)) {
@@ -283,9 +293,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -293,6 +304,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -313,8 +325,8 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
i++;
}
@@ -357,8 +369,9 @@ class AlarmManagerService extends SystemService {
b.append(" num="); b.append(size());
b.append(" start="); b.append(start);
b.append(" end="); b.append(end);
- if (standalone) {
- b.append(" STANDALONE");
+ if (flags != 0) {
+ b.append(" flgs=0x");
+ b.append(Integer.toHexString(flags));
}
b.append('}');
return b.toString();
@@ -441,7 +454,12 @@ class AlarmManagerService extends SystemService {
// minimum recurrence period or alarm futurity for us to be able to fuzz it
static final long MIN_FUZZABLE_INTERVAL = 10000;
static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
- final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+ final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
+
+ // set to null if in idle mode; while in this mode, any alarms we don't want
+ // to run during this time are placed in mPendingWhileIdleAlarms
+ Alarm mPendingIdleUntil = null;
+ final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();
public AlarmManagerService(Context context) {
super(context);
@@ -486,7 +504,7 @@ class AlarmManagerService extends SystemService {
final int N = mAlarmBatches.size();
for (int i = 0; i < N; i++) {
Batch b = mAlarmBatches.get(i);
- if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+ if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
return i;
}
}
@@ -503,31 +521,56 @@ class AlarmManagerService extends SystemService {
void rebatchAllAlarmsLocked(boolean doValidate) {
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
+ Alarm oldPendingIdleUntil = mPendingIdleUntil;
final long nowElapsed = SystemClock.elapsedRealtime();
final int oldBatches = oldSet.size();
for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
Batch batch = oldSet.get(batchNum);
final int N = batch.size();
for (int i = 0; i < N; i++) {
- Alarm a = batch.get(i);
- long whenElapsed = convertToElapsed(a.when, a.type);
- final long maxElapsed;
- if (a.whenElapsed == a.maxWhen) {
- // Exact
- maxElapsed = whenElapsed;
- } else {
- // Not exact. Preserve any explicit window, otherwise recalculate
- // the window based on the alarm's new futurity. Note that this
- // reflects a policy of preferring timely to deferred delivery.
- maxElapsed = (a.windowLength > 0)
- ? (whenElapsed + a.windowLength)
- : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
- }
- setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
- a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource,
- a.alarmClock, a.userId);
+ reAddAlarmLocked(batch.get(i), nowElapsed, doValidate);
+ }
+ }
+ if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) {
+ Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil
+ + " to " + mPendingIdleUntil);
+ if (mPendingIdleUntil == null) {
+ // Somehow we lost this... we need to restore all of the pending alarms.
+ restorePendingWhileIdleAlarmsLocked();
}
}
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+
+ void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
+ a.when = a.origWhen;
+ long whenElapsed = convertToElapsed(a.when, a.type);
+ final long maxElapsed;
+ if (a.whenElapsed == a.maxWhenElapsed) {
+ // Exact
+ maxElapsed = whenElapsed;
+ } else {
+ // Not exact. Preserve any explicit window, otherwise recalculate
+ // the window based on the alarm's new futurity. Note that this
+ // reflects a policy of preferring timely to deferred delivery.
+ maxElapsed = (a.windowLength > 0)
+ ? (whenElapsed + a.windowLength)
+ : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+ }
+ a.whenElapsed = whenElapsed;
+ a.maxWhenElapsed = maxElapsed;
+ setImplLocked(a, true, doValidate);
+ }
+
+ void restorePendingWhileIdleAlarmsLocked() {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int i=mPendingWhileIdleAlarms.size() - 1; i >= 0 && mPendingIdleUntil != null; i --) {
+ Alarm a = mPendingWhileIdleAlarms.remove(i);
+ reAddAlarmLocked(a, nowElapsed, false);
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
static final class InFlight extends Intent {
@@ -687,7 +730,7 @@ class AlarmManagerService extends SystemService {
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, boolean isStandalone, WorkSource workSource,
+ PendingIntent operation, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
@@ -745,25 +788,66 @@ class AlarmManagerService extends SystemService {
Slog.v(TAG, "set(" + operation + ") : type=" + type
+ " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+ " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
- + " interval=" + interval + " standalone=" + isStandalone);
+ + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, isStandalone, true, workSource, alarmClock, userId);
+ interval, operation, flags, true, workSource, alarmClock, userId);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
+ long maxWhen, long interval, PendingIntent operation, int flags,
boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int userId) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, alarmClock, userId);
+ operation, workSource, flags, alarmClock, userId);
removeLocked(operation);
+ setImplLocked(a, false, doValidate);
+ }
+
+ private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // This is a special alarm that will put the system idle until it goes off.
+ // The caller has given the time they want this to happen at, however we need
+ // to pull that earlier if there are existing alarms that have requested to
+ // bring us out of idle.
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (a.whenElapsed > b.end) {
+ // There are no interesting things happening before our idle until,
+ // so keep the requested time.
+ break;
+ }
+ if ((b.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ a.when = a.whenElapsed = a.maxWhenElapsed = b.end;
+ break;
+ }
+ }
+ // Add fuzz to make the alarm go off some time before the actual desired time.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ long fuzz = fuzzForDuration(a.whenElapsed-nowElapsed);
+ if (fuzz > 0) {
+ if (mRandom == null) {
+ mRandom = new Random();
+ }
+ a.whenElapsed -= mRandom.nextLong() % fuzz;
+ }
- int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
+ } else if (mPendingIdleUntil != null) {
+ // We currently have an idle until alarm scheduled; if the new alarm has
+ // not explicitly stated it wants to run while idle, then put it on hold.
+ if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE))
+ == 0) {
+ mPendingWhileIdleAlarms.add(a);
+ return;
+ }
+ }
+
+ int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
+ ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
if (whichBatch < 0) {
Batch batch = new Batch(a);
- batch.standalone = isStandalone;
addBatchLocked(mAlarmBatches, batch);
} else {
Batch batch = mAlarmBatches.get(whichBatch);
@@ -775,28 +859,48 @@ class AlarmManagerService extends SystemService {
}
}
- if (alarmClock != null) {
+ if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
- updateNextAlarmClockLocked();
}
- if (DEBUG_VALIDATE) {
- if (doValidate && !validateConsistencyLocked()) {
- Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
- + " when(hex)=" + Long.toHexString(when)
- + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
- + " interval=" + interval + " op=" + operation
- + " standalone=" + isStandalone);
+ boolean needRebatch = false;
+
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ mPendingIdleUntil = a;
+ needRebatch = true;
+ } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0 && mPendingIdleUntil != null) {
+ // If we are adding an alarm that asks to wake from idle, and we are currently
+ // idling, then we need to rebatch alarms in case the idle until time needs to
+ // be updated.
+ needRebatch = true;
+ }
+
+ if (!rebatching) {
+ if (DEBUG_VALIDATE) {
+ if (doValidate && !validateConsistencyLocked()) {
+ Slog.v(TAG, "Tipping-point operation: type=" + a.type + " when=" + a.when
+ + " when(hex)=" + Long.toHexString(a.when)
+ + " whenElapsed=" + a.whenElapsed
+ + " maxWhenElapsed=" + a.maxWhenElapsed
+ + " interval=" + a.repeatInterval + " op=" + a.operation
+ + " flags=0x" + Integer.toHexString(a.flags));
+ rebatchAllAlarmsLocked(false);
+ needRebatch = false;
+ }
+ }
+
+ if (needRebatch) {
rebatchAllAlarmsLocked(false);
}
- }
- rescheduleKernelAlarmsLocked();
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval,
+ public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (workSource != null) {
@@ -805,8 +909,17 @@ class AlarmManagerService extends SystemService {
"AlarmManager.set");
}
+ if (windowLength == AlarmManager.WINDOW_EXACT) {
+ flags |= AlarmManager.FLAG_STANDALONE;
+ }
+ if (alarmClock != null) {
+ flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+ }
+ if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) {
+ flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+ }
setImpl(type, triggerAtTime, windowLength, interval, operation,
- windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
+ flags, workSource, alarmClock);
}
@Override
@@ -912,6 +1025,14 @@ class AlarmManagerService extends SystemService {
dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf);
}
}
+ if (mPendingIdleUntil != null) {
+ pw.println();
+ pw.println("Idle mode state:");
+ pw.print(" Idling until: "); pw.println(mPendingIdleUntil);
+ mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf);
+ pw.println(" Pending alarms:");
+ dumpAlarmList(pw, mPendingWhileIdleAlarms, " ", nowELAPSED, nowRTC, sdf);
+ }
pw.println();
pw.print("Past-due non-wakeup alarms: ");
@@ -1224,6 +1345,15 @@ class AlarmManagerService extends SystemService {
}
void rescheduleKernelAlarmsLocked() {
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, we will just blindly wait until
+ // it is time for that alarm to go off. We don't want to wake up for any
+ // other reasons.
+ mNextWakeup = mNextNonWakeup = mPendingIdleUntil.whenElapsed;
+ setLocked(ELAPSED_REALTIME_WAKEUP, mNextWakeup);
+ setLocked(ELAPSED_REALTIME, mNextNonWakeup);
+ return;
+ }
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
long nextNonWakeup = 0;
@@ -1260,13 +1390,26 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
+ boolean restorePending = false;
+ if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ mPendingIdleUntil = null;
+ restorePending = true;
+ }
rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
+ if (restorePending) {
+ restorePendingWhileIdleAlarmsLocked();
+ }
updateNextAlarmClockLocked();
}
}
@@ -1280,6 +1423,12 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1300,6 +1449,13 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ == userHandle) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1344,6 +1500,11 @@ class AlarmManagerService extends SystemService {
return true;
}
}
+ for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
return false;
}
@@ -1413,6 +1574,13 @@ class AlarmManagerService extends SystemService {
boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,
final long nowRTC) {
boolean hasWakeup = false;
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, don't trigger any alarms
+ // until we are past the idle period.
+ if (nowELAPSED < mPendingIdleUntil.whenElapsed) {
+ return false;
+ }
+ }
// batches are temporally sorted, so we need only pull from the
// start of the list until we either empty it or hit a batch
// that is not yet deliverable
@@ -1432,6 +1600,11 @@ class AlarmManagerService extends SystemService {
Alarm alarm = batch.get(i);
alarm.count = 1;
triggerList.add(alarm);
+ if (mPendingIdleUntil == alarm) {
+ mPendingIdleUntil = null;
+ rebatchAllAlarmsLocked(false);
+ restorePendingWhileIdleAlarmsLocked();
+ }
// Recurring alarms may have passed several alarm intervals while the
// phone was asleep or off, so pass a trigger count when sending them.
@@ -1445,7 +1618,7 @@ class AlarmManagerService extends SystemService {
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, batch.standalone, true,
+ alarm.repeatInterval, alarm.operation, alarm.flags, true,
alarm.workSource, alarm.alarmClock, alarm.userId);
}
@@ -1494,34 +1667,38 @@ class AlarmManagerService extends SystemService {
private static class Alarm {
public final int type;
+ public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
public final String tag;
public final WorkSource workSource;
+ public final int flags;
public int count;
public long when;
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
- public long maxWhen; // also in the elapsed time base
+ public long maxWhenElapsed; // also in the elapsed time base
public long repeatInterval;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int userId;
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws,
+ long _interval, PendingIntent _op, WorkSource _ws, int _flags,
AlarmManager.AlarmClockInfo _info, int _userId) {
type = _type;
+ origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
windowLength = _windowLength;
- maxWhen = _maxWhen;
+ maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
tag = makeTag(_op, _type);
workSource = _ws;
+ flags = _flags;
alarmClock = _info;
userId = _userId;
}
@@ -1561,7 +1738,8 @@ class AlarmManagerService extends SystemService {
pw.println();
pw.print(prefix); pw.print("window="); pw.print(windowLength);
pw.print(" repeatInterval="); pw.print(repeatInterval);
- pw.print(" count="); pw.println(count);
+ pw.print(" count="); pw.print(count);
+ pw.print(" flags=0x"); pw.println(Integer.toHexString(flags));
pw.print(prefix); pw.print("operation="); pw.println(operation);
}
}
@@ -1599,6 +1777,20 @@ class AlarmManagerService extends SystemService {
}
}
+ static long fuzzForDuration(long duration) {
+ if (duration < 15*60*1000) {
+ // If the duration until the time is less than 15 minutes, the maximum fuzz
+ // is the duration.
+ return duration;
+ } else if (duration < 90*60*1000) {
+ // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes,
+ return 15*60*1000;
+ } else {
+ // Otherwise, we will fuzz by at most half an hour.
+ return 30*60*1000;
+ }
+ }
+
boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) {
if (mInteractive) {
return false;
@@ -1886,7 +2078,7 @@ class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, true, workSource, null);
+ 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null);
}
public void scheduleDateChangedEvent() {
@@ -1899,8 +2091,8 @@ class AlarmManagerService extends SystemService {
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource,
- null);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
+ AlarmManager.FLAG_STANDALONE, workSource, null);
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4677f65eb46b..d92a89f976cd 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -15,6 +15,7 @@
package com.android.server;
+import android.annotation.NonNull;
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
@@ -1285,13 +1286,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(ClientState cs,
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
+ if (attribute != null) {
+ // We accept an empty package name as a valid data.
+ if (!TextUtils.isEmpty(attribute.packageName) &&
+ !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
+ attribute.packageName)) {
+ Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ + " uid=" + cs.uid + " package=" + attribute.packageName);
+ return mNoBinding;
+ }
+ }
+
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
mCurClientInKeyguard = isKeyguardLocked();
@@ -1855,16 +1867,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (mCurClient != null && mCurAttribute != null) {
- final int uid = mCurClient.uid;
- final String packageName = mCurAttribute.packageName;
- if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) {
- if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) {
- return;
- }
- // TODO: Do we need to lock the input method when the application reported an
- // incorrect package name?
- Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid
- + " package=" + packageName);
+ // We have already made sure that the package name belongs to the application's UID.
+ // No further UID check is required.
+ if (SystemConfig.getInstance().getFixedImeApps().contains(mCurAttribute.packageName)) {
+ return;
}
}
@@ -2148,7 +2154,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
-
+
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d2f52b4b5bb9..719338495001 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -100,6 +100,7 @@ import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -986,6 +987,12 @@ public final class ActivityManagerService extends ActivityManagerNative
private boolean mSleeping = false;
/**
+ * The process state used for processes that are running the top activities.
+ * This changes between TOP and TOP_SLEEPING to following mSleeping.
+ */
+ int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+
+ /**
* Set while we are running a voice interaction. This overrides
* sleeping while it is active.
*/
@@ -2466,6 +2473,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void batterySendBroadcast(Intent intent) {
+ broadcastIntentLocked(null, null, intent, null,
+ null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
+ Process.SYSTEM_UID, UserHandle.USER_ALL);
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
@@ -9726,10 +9740,14 @@ public final class ActivityManagerService extends ActivityManagerNative
void updateSleepIfNeededLocked() {
if (mSleeping && !shouldSleepLocked()) {
mSleeping = false;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
+ updateOomAdjLocked();
} else if (!mSleeping && shouldSleepLocked()) {
mSleeping = true;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
mStackSupervisor.goingToSleepLocked();
+ updateOomAdjLocked();
// Initialize the wake times of all processes.
checkExcessivePowerUsageLocked(false);
@@ -10700,7 +10718,8 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP
+ if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP
+ && proc.setProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -12893,7 +12912,7 @@ public final class ActivityManagerService extends ActivityManagerNative
StringBuilder sb = new StringBuilder();
sb.append(" ").append(proc).append('/');
UserHandle.formatUid(sb, uids.keyAt(j));
- Pair<Long, String> val = uids.valueAt(i);
+ Pair<Long, String> val = uids.valueAt(j);
sb.append(": "); DebugUtils.sizeValueToString(val.first, sb);
if (val.second != null) {
sb.append(", report to ").append(val.second);
@@ -13929,7 +13948,7 @@ public final class ActivityManagerService extends ActivityManagerNative
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
- pw.println(" -d: include dalvik details when dumping process details.");
+ pw.println(" -d: include dalvik details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
pw.println(" --local: only collect details locally, don't call process.");
@@ -14016,6 +14035,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
long nativePss = 0;
long dalvikPss = 0;
+ long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
long otherPss = 0;
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
@@ -14093,6 +14114,9 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14151,6 +14175,10 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14169,7 +14197,16 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<MemItem> catMems = new ArrayList<MemItem>();
catMems.add(new MemItem("Native", "Native", nativePss, -1));
- catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+ final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2);
+ if (dalvikSubitemPss.length > 0) {
+ dalvikItem.subitems = new ArrayList<MemItem>();
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ final String name = Debug.MemoryInfo.getOtherLabel(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j));
+ }
+ }
+ catMems.add(dalvikItem);
catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
String label = Debug.MemoryInfo.getOtherLabel(j);
@@ -16824,6 +16861,8 @@ public final class ActivityManagerService extends ActivityManagerNative
app.systemNoUi = false;
+ final int PROCESS_STATE_TOP = mTopProcessState;
+
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
int adj;
@@ -16837,7 +16876,7 @@ public final class ActivityManagerService extends ActivityManagerNative
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "top-activity";
foregroundActivities = true;
- procState = ActivityManager.PROCESS_STATE_TOP;
+ procState = PROCESS_STATE_TOP;
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -16890,8 +16929,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.VISIBLE_APP_ADJ;
app.adjType = "visible";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16903,8 +16942,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.adjType = "pausing";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16943,7 +16982,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
app.cached = false;
app.adjType = "fg-service";
schedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -17472,7 +17511,7 @@ public final class ActivityManagerService extends ActivityManagerNative
IApplicationThread thread = myProc.thread;
if (thread != null) {
try {
- if (true || DEBUG_PSS) Slog.d(TAG_PSS,
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
"Requesting dump heap from "
+ myProc + " to " + heapdumpFile);
thread.dumpHeap(true, heapdumpFile.toString(), fd);
@@ -18757,7 +18796,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " does not match last path " + mMemWatchDumpFile);
return;
}
- if (true || DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ddba1eb3a3f7..2362d287cf7c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1909,7 +1909,7 @@ final class ActivityStack {
next.sleeping = false;
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
- next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
mService.isNextTransitionForward(), resumeAnimOptions);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d08cddcf7f46..c2f6bfdcda35 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1229,7 +1229,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
app.hasShownUi = true;
app.pendingUiClean = true;
}
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ac70d8838f33..ed108c298d5d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -260,6 +260,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ public boolean isCharging() {
+ synchronized (mStats) {
+ return mStats.isCharging();
+ }
+ }
+
public long computeBatteryTimeRemaining() {
synchronized (mStats) {
long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c7aa94cb20f5..cdfcd0c15196 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -368,6 +368,12 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_TOP:
procState = "T ";
break;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ procState = "FS";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TS";
+ break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IF";
break;
@@ -475,6 +481,8 @@ final class ProcessList {
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
@@ -492,6 +500,8 @@ final class ProcessList {
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -509,6 +519,8 @@ final class ProcessList {
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -526,6 +538,8 @@ final class ProcessList {
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -543,6 +557,8 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 191df2f50f05..7866ddc88113 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -176,6 +176,7 @@ public class SyncManager {
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
+ volatile private boolean mDeviceIsIdle = false;
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
@@ -221,6 +222,20 @@ public class SyncManager {
}
};
+ private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ boolean idle = mPowerManager.isDeviceIdleMode();
+ mDeviceIsIdle = idle;
+ if (idle) {
+ cancelActiveSync(
+ SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+ null /* any sync */);
+ } else {
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -425,6 +440,9 @@ public class SyncManager {
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
+ intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ context.registerReceiver(mDeviceIdleReceiver, intentFilter);
+
intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
@@ -1312,6 +1330,7 @@ public class SyncManager {
pw.println();
}
pw.print("memory low: "); pw.println(mStorageIsLow);
+ pw.print("device idle: "); pw.println(mDeviceIsIdle);
final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
@@ -2358,6 +2377,13 @@ public class SyncManager {
return Long.MAX_VALUE;
}
+ if (mDeviceIsIdle) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: device idle, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
@@ -2984,6 +3010,7 @@ public class SyncManager {
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
+ if (mDeviceIsIdle) return;
// When the status bar notification should be raised
final long notificationTime =
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 309e034de7ba..7c2aead5cb07 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -47,10 +47,6 @@ public class BatteryController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
- private static final String ACTION_CHARGING_STABLE =
- "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
- /** Wait this long after phone is plugged in before doing any work. */
- private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private ChargingTracker mChargeTracker;
@@ -91,9 +87,6 @@ public class BatteryController extends StateController {
taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
}
}
- if (isOnStablePower) {
- mChargeTracker.setStableChargingAlarm();
- }
}
@Override
@@ -131,8 +124,6 @@ public class BatteryController extends StateController {
}
public class ChargingTracker extends BroadcastReceiver {
- private final AlarmManager mAlarm;
- private final PendingIntent mStableChargingTriggerIntent;
/**
* Track whether we're "charging", where charging means that we're ready to commit to
* doing work.
@@ -142,9 +133,6 @@ public class BatteryController extends StateController {
private boolean mBatteryHealthy;
public ChargingTracker() {
- mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(ACTION_CHARGING_STABLE);
- mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
}
public void startTracking() {
@@ -154,10 +142,8 @@ public class BatteryController extends StateController {
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_OKAY);
// Charging/not charging.
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- // Charging stable.
- filter.addAction(ACTION_CHARGING_STABLE);
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
mContext.registerReceiver(this, filter);
// Initialise tracker state.
@@ -195,44 +181,20 @@ public class BatteryController extends StateController {
}
mBatteryHealthy = true;
maybeReportNewChargingState();
- } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Received charging intent, setting alarm for "
- + STABLE_CHARGING_THRESHOLD_MILLIS);
+ Slog.d(TAG, "Received charging intent, fired @ "
+ + SystemClock.elapsedRealtime());
}
- // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
- // here if the user unplugs the phone immediately.
- setStableChargingAlarm();
mCharging = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ maybeReportNewChargingState();
+ } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
+ Slog.d(TAG, "Disconnected from power.");
}
- // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
- mAlarm.cancel(mStableChargingTriggerIntent);
mCharging = false;
maybeReportNewChargingState();
- }else if (ACTION_CHARGING_STABLE.equals(action)) {
- // Here's where we actually do the notify for a task being ready.
- if (DEBUG) {
- Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
- + " charging: " + mCharging);
- }
- if (mCharging) { // Should never receive this intent if mCharging is false.
- maybeReportNewChargingState();
- }
- }
- }
-
- void setStableChargingAlarm() {
- final long alarmTriggerElapsed =
- SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
- if (DEBUG) {
- Slog.d(TAG, "Setting stable alarm to go off in " +
- (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
}
- mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
- mStableChargingTriggerIntent);
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b4a44a646551..ce31f98a6f50 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -20,7 +20,9 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.text.TextUtils;
import android.util.Slog;
+
import dalvik.system.VMRuntime;
import com.android.internal.os.InstallerConnection;
@@ -42,9 +44,24 @@ public final class Installer extends SystemService {
ping();
}
+ private static String escapeNull(String arg) {
+ if (TextUtils.isEmpty(arg)) {
+ return "!";
+ } else {
+ return arg;
+ }
+ }
+
+ @Deprecated
public int install(String name, int uid, int gid, String seinfo) {
+ return install(null, name, uid, gid, seinfo);
+ }
+
+ public int install(String uuid, String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -55,43 +72,25 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
- return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
- }
-
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet);
+ return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable,
- @Nullable String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, @Nullable String outputPath) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
-
- return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode,
+ return mInstaller.dexopt(apkPath, uid, isPublic, pkgName,
+ instructionSet, dexoptNeeded, vmSafeMode,
debuggable, outputPath);
}
@@ -146,9 +145,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int remove(String name, int userId) {
+ return remove(null, name, userId);
+ }
+
+ public int remove(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -164,9 +170,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int fixUid(String name, int uid, int gid) {
+ return fixUid(null, name, uid, gid);
+ }
+
+ public int fixUid(String uuid, String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("fixuid");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -175,27 +188,48 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCacheFiles(String name, int userId) {
+ return deleteCacheFiles(null, name, userId);
+ }
+
+ public int deleteCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCodeCacheFiles(String name, int userId) {
+ return deleteCodeCacheFiles(null, name, userId);
+ }
+
+ public int deleteCodeCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcodecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int createUserData(String name, int uid, int userId, String seinfo) {
+ return createUserData(null, name, uid, userId, seinfo);
+ }
+
+ public int createUserData(String uuid, String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -213,16 +247,30 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int removeUserDataDirs(int userId) {
+ return removeUserDataDirs(null, userId);
+ }
+
+ public int removeUserDataDirs(String uuid, int userId) {
StringBuilder builder = new StringBuilder("rmuser");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int clearUserData(String name, int userId) {
+ return clearUserData(null, name, userId);
+ }
+
+ public int clearUserData(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -249,15 +297,30 @@ public final class Installer extends SystemService {
}
}
+ @Deprecated
public int freeCache(long freeStorageSize) {
+ return freeCache(null, freeStorageSize);
+ }
+
+ public int freeCache(String uuid, long freeStorageSize) {
StringBuilder builder = new StringBuilder("freecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(String.valueOf(freeStorageSize));
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) {
+ return getSizeInfo(null, pkgName, persona, apkPath, libDirPath, fwdLockApkPath, asecPath,
+ instructionSets, pStats);
+ }
+
+ public int getSizeInfo(String uuid, String pkgName, int persona, String apkPath,
+ String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
+ PackageStats pStats) {
for (String instructionSet : instructionSets) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
@@ -267,6 +330,8 @@ public final class Installer extends SystemService {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(persona);
@@ -306,6 +371,11 @@ public final class Installer extends SystemService {
return mInstaller.execute("movefiles");
}
+ @Deprecated
+ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ return linkNativeLibraryDirectory(null, dataPath, nativeLibPath32, userId);
+ }
+
/**
* Links the 32 bit native library directory in an application's data directory to the
* real location for backward compatibility. Note that no such symlink is created for
@@ -313,7 +383,8 @@ public final class Installer extends SystemService {
*
* @return -1 on error
*/
- public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32,
+ int userId) {
if (dataPath == null) {
Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
return -1;
@@ -322,7 +393,10 @@ public final class Installer extends SystemService {
return -1;
}
- StringBuilder builder = new StringBuilder("linklib ");
+ StringBuilder builder = new StringBuilder("linklib");
+ builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(dataPath);
builder.append(' ');
builder.append(nativeLibPath32);
@@ -332,9 +406,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public boolean restoreconData(String pkgName, String seinfo, int uid) {
+ return restoreconData(null, pkgName, seinfo, uid);
+ }
+
+ public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) {
StringBuilder builder = new StringBuilder("restorecondata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(seinfo != null ? seinfo : "!");
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 680ec4b4bb87..4c36fa6eea65 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -113,64 +113,48 @@ final class PackageDexOptimizer {
for (String path : paths) {
try {
- // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
- // package or the one we find does not match the image checksum (i.e. it was
- // compiled against an old image). It will return PATCHOAT_NEEDED if we can find a
- // odex file and it matches the checksum of the image but not its base address,
- // meaning we need to move it.
- final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
- pkg.packageName, dexCodeInstructionSet, defer);
- if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
- File oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
- Log.i(TAG, "Running dexopt on: " + path + " pkg="
- + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
- + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
- + " oatDir = " + oatDir);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ final int dexoptNeeded;
+ if (forceDex) {
+ dexoptNeeded = DexFile.DEX2OAT_NEEDED;
+ } else {
+ dexoptNeeded = DexFile.getDexOptNeeded(path,
+ pkg.packageName, dexCodeInstructionSet, defer);
+ }
- if (oatDir != null) {
- int ret = mPackageManagerService.mInstaller.dexopt(
- path, sharedGid, !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet, vmSafeMode, debuggable,
- oatDir.getAbsolutePath());
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
+ if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+ // paths and instruction sets. We'll deal with them all together when we process
+ // our list of deferred dexopts.
+ addPackageForDeferredDexopt(pkg);
+ return DEX_OPT_DEFERRED;
+ }
+
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ final String dexoptType;
+ String oatDir = null;
+ if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
+ dexoptType = "dex2oat";
+ oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
+ } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
+ dexoptType = "patchoat";
+ } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
+ dexoptType = "self patchoat";
} else {
- final int ret = mPackageManagerService.mInstaller
- .dexopt(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet,
- vmSafeMode, debuggable, null);
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
+ throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
}
-
- performedDexOpt = true;
- } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
- Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
+ Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mPackageManagerService.mInstaller.patchoat(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet);
-
+ final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
+ !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, vmSafeMode, debuggable, oatDir);
if (ret < 0) {
- // Don't bother running patchoat again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
return DEX_OPT_FAILED;
}
-
performedDexOpt = true;
}
-
- // We're deciding to defer a needed dexopt. Don't bother dexopting for other
- // paths and instruction sets. We'll deal with them all together when we process
- // our list of deferred dexopts.
- if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) {
- addPackageForDeferredDexopt(pkg);
- return DEX_OPT_DEFERRED;
- }
} catch (FileNotFoundException e) {
Slog.w(TAG, "Apk not found for dexopt: " + path);
return DEX_OPT_FAILED;
@@ -187,7 +171,7 @@ final class PackageDexOptimizer {
}
// At this point we haven't failed dexopt and we haven't deferred dexopt. We must
- // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+ // either have either succeeded dexopt, or have had getDexOptNeeded tell us
// it isn't required. We therefore mark that this package doesn't need dexopt unless
// it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
// it.
@@ -209,10 +193,11 @@ final class PackageDexOptimizer {
* <li>Package location is not a directory, i.e. monolithic install.</li>
* </ul>
*
- * @return oat directory or null, if oat directory cannot be created.
+ * @return Absolute path to the oat directory or null, if oat directory
+ * cannot be created.
*/
@Nullable
- private File createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
+ private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
throws IOException {
if (pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) {
return null;
@@ -222,7 +207,7 @@ final class PackageDexOptimizer {
File oatDir = getOatDir(codePath);
mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
dexInstructionSet);
- return oatDir;
+ return oatDir.getAbsolutePath();
}
return null;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 923b325bece0..f5042edae249 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1847,18 +1847,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired != DexFile.UP_TO_DATE) {
+ int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
-
- // The list of "shared libraries" we have at this point is
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else {
- mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- }
+ mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
@@ -1904,13 +1896,9 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
- mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
+ int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java
index dd0044654c9c..a23a87b1d177 100644
--- a/services/core/java/com/android/server/power/DeviceIdleController.java
+++ b/services/core/java/com/android/server/power/DeviceIdleController.java
@@ -377,7 +377,7 @@ public class DeviceIdleController extends SystemService {
}
}
- void scheduleAlarmLocked(long delay, boolean wakeup) {
+ void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (mSigMotionSensor == null) {
// If there is no significant motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
@@ -386,8 +386,13 @@ public class DeviceIdleController extends SystemService {
return;
}
mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
- mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP
- : AlarmManager.ELAPSED_REALTIME, mNextAlarmTime, mAlarmIntent);
+ if (idleUntil) {
+ mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ } else {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ }
}
private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 9b4b5228b809..3262cc6515d0 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -204,6 +204,14 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The input list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+
+ @Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(getChangingUserId());
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 1d2180e33aa8..c1c5c566aae8 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -372,8 +372,8 @@ public class MidiService extends IMidiManager.Stub {
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type) {
int uid = Binder.getCallingUid();
- if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
- throw new SecurityException("only system can create non-virtual devices");
+ if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create USB devices");
}
synchronized (mDevicesByInfo) {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 7383478c44eb..7ea5aa7f7e28 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
@@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class),
+ anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 6ece888c26ae..671cf0169c61 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -45,7 +45,8 @@ public final class UsbMidiDevice implements Closeable {
private MidiDeviceServer mServer;
- private final MidiEventScheduler mEventScheduler;
+ // event schedulers for each output port
+ private final MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512;
@@ -99,10 +100,11 @@ public final class UsbMidiDevice implements Closeable {
}
mOutputStreams = new FileOutputStream[outputCount];
+ mEventSchedulers = new MidiEventScheduler[outputCount];
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+ mEventSchedulers[i] = new MidiEventScheduler();
}
- mEventScheduler = new MidiEventScheduler(inputCount);
}
private boolean register(Context context, Bundle properties) {
@@ -116,7 +118,7 @@ public final class UsbMidiDevice implements Closeable {
int outputCount = mOutputStreams.length;
MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+ inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
}
mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
@@ -126,7 +128,7 @@ public final class UsbMidiDevice implements Closeable {
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // Create input thread
+ // Create input thread which will read from all input ports
new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
@@ -161,38 +163,46 @@ public final class UsbMidiDevice implements Closeable {
}
}.start();
- // Create output thread
- new Thread("UsbMidiDevice output thread") {
- @Override
- public void run() {
- while (true) {
- MidiEvent event;
- try {
- event = (MidiEvent)mEventScheduler.waitNextEvent();
- } catch (InterruptedException e) {
- // try again
- continue;
- }
- if (event == null) {
- break;
- }
- try {
- mOutputStreams[event.portNumber].write(event.data, 0, event.count);
- } catch (IOException e) {
- Log.e(TAG, "write failed for port " + event.portNumber);
+ // Create output thread for each output port
+ for (int port = 0; port < outputCount; port++) {
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+ final FileOutputStream outputStreamF = mOutputStreams[port];
+ final int portF = port;
+
+ new Thread("UsbMidiDevice output thread " + port) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ outputStreamF.write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + portF);
+ }
+ eventSchedulerF.addEventToPool(event);
}
- mEventScheduler.addEventToPool(event);
+ Log.d(TAG, "output thread exit");
}
- Log.d(TAG, "output thread exit");
- }
- }.start();
+ }.start();
+ }
return true;
}
@Override
public void close() throws IOException {
- mEventScheduler.close();
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mEventSchedulers[i].close();
+ }
if (mServer != null) {
mServer.close();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 607df2dce321..fb8395673fd3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -162,8 +162,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
mBindIntent.setComponent(mSessionComponentName);
mBound = mContext.bindServiceAsUser(mBindIntent, this,
- Context.BIND_AUTO_CREATE|Context.BIND_WAIVE_PRIORITY
- |Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
if (mBound) {
try {
mIWindowManager.addWindowToken(mToken,
diff --git a/tests/Compatibility/Android.mk b/tests/Compatibility/Android.mk
index 0ec4d9d5d77e..c2f89ddc7619 100644
--- a/tests/Compatibility/Android.mk
+++ b/tests/Compatibility/Android.mk
@@ -25,7 +25,7 @@ LOCAL_SRC_FILES := \
LOCAL_PACKAGE_NAME := AppCompatibilityTest
-
+LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/Compatibility/AndroidManifest.xml b/tests/Compatibility/AndroidManifest.xml
index 28845328eb24..8ae5bc516050 100644
--- a/tests/Compatibility/AndroidManifest.xml
+++ b/tests/Compatibility/AndroidManifest.xml
@@ -19,7 +19,7 @@
<application >
<uses-library android:name="android.test.runner" />
</application>
-
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
<instrumentation
android:name=".AppCompatibilityRunner"
android:targetPackage="com.android.compatibilitytest"