diff options
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><project_directory>/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 @com.google.android.gms.common.annotation.KeepName class * +-keepclassmembernames class * { + @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—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" |