diff options
128 files changed, 5998 insertions, 1648 deletions
diff --git a/api/current.txt b/api/current.txt index eda087d..f6bc276 100644 --- a/api/current.txt +++ b/api/current.txt @@ -864,6 +864,7 @@ package android { field public static final int paddingBottom = 16842969; // 0x10100d9 field public static final int paddingEnd = 16843700; // 0x10103b4 field public static final int paddingLeft = 16842966; // 0x10100d6 + field public static final int paddingMode = 16843866; // 0x101045a field public static final int paddingRight = 16842968; // 0x10100d8 field public static final int paddingStart = 16843699; // 0x10103b3 field public static final int paddingTop = 16842967; // 0x10100d7 @@ -1640,7 +1641,6 @@ package android { field public static final int selectAll = 16908319; // 0x102001f field public static final int selectTextMode = 16908333; // 0x102002d field public static final int selectedIcon = 16908302; // 0x102000e - field public static final int shared_element = 16908354; // 0x1020042 field public static final int startSelectingText = 16908328; // 0x1020028 field public static final int stopSelectingText = 16908329; // 0x1020029 field public static final int summary = 16908304; // 0x1020010 @@ -2830,6 +2830,12 @@ package android.animation { method public java.lang.Object evaluate(float, java.lang.Object, java.lang.Object); } + public abstract class BidirectionalTypeConverter extends android.animation.TypeConverter { + ctor public BidirectionalTypeConverter(java.lang.Class<T>, java.lang.Class<V>); + method public abstract T convertBack(V); + method public android.animation.BidirectionalTypeConverter<V, T> invert(); + } + public class FloatArrayEvaluator implements android.animation.TypeEvaluator { ctor public FloatArrayEvaluator(); ctor public FloatArrayEvaluator(float[]); @@ -3008,7 +3014,6 @@ package android.animation { public abstract class TypeConverter { ctor public TypeConverter(java.lang.Class<T>, java.lang.Class<V>); method public abstract V convert(T); - method public T convertBack(V); } public abstract interface TypeEvaluator { @@ -3336,6 +3341,8 @@ package android.app { method public void setContentView(android.view.View); method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public final void setDefaultKeyMode(int); + method public void setEnterSharedElementListener(android.app.SharedElementListener); + method public void setExitSharedElementListener(android.app.SharedElementListener); method public final void setFeatureDrawable(int, android.graphics.drawable.Drawable); method public final void setFeatureDrawableAlpha(int, int); method public final void setFeatureDrawableResource(int, int); @@ -3351,7 +3358,6 @@ package android.app { method public final void setResult(int); method public final void setResult(int, android.content.Intent); method public final void setSecondaryProgress(int); - method public void setSharedElementListener(android.app.SharedElementListener); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); @@ -4418,6 +4424,8 @@ package android.app { ctor public Notification(android.os.Parcel); method public android.app.Notification clone(); method public int describeContents(); + method public java.lang.String getGroup(); + method public java.lang.String getSortKey(); method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public void writeToParcel(android.os.Parcel, int); field public static final java.lang.String CATEGORY_ALARM = "alarm"; @@ -4460,6 +4468,7 @@ package android.app { field public static final java.lang.String EXTRA_TITLE_BIG = "android.title.big"; field public static final int FLAG_AUTO_CANCEL = 16; // 0x10 field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40 + field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200 field public static final deprecated int FLAG_HIGH_PRIORITY = 128; // 0x80 field public static final int FLAG_INSISTENT = 4; // 0x4 field public static final int FLAG_LOCAL_ONLY = 256; // 0x100 @@ -4510,6 +4519,7 @@ package android.app { method public android.app.Notification.Action clone(); method public int describeContents(); method public android.os.Bundle getExtras(); + method public android.app.RemoteInput[] getRemoteInputs(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public android.app.PendingIntent actionIntent; @@ -4517,14 +4527,20 @@ package android.app { field public java.lang.CharSequence title; } - public static class Notification.Action.Builder { + public static final class Notification.Action.Builder { ctor public Notification.Action.Builder(int, java.lang.CharSequence, android.app.PendingIntent); ctor public Notification.Action.Builder(android.app.Notification.Action); method public android.app.Notification.Action.Builder addExtras(android.os.Bundle); + method public android.app.Notification.Action.Builder addRemoteInput(android.app.RemoteInput); + method public android.app.Notification.Action.Builder apply(android.app.Notification.Action.Builder.Extender); method public android.app.Notification.Action build(); method public android.os.Bundle getExtras(); } + public static abstract interface Notification.Action.Builder.Extender { + method public abstract android.app.Notification.Action.Builder applyTo(android.app.Notification.Action.Builder); + } + public static class Notification.BigPictureStyle extends android.app.Notification.Style { ctor public Notification.BigPictureStyle(); ctor public Notification.BigPictureStyle(android.app.Notification.Builder); @@ -4548,6 +4564,7 @@ package android.app { method public android.app.Notification.Builder addAction(android.app.Notification.Action); method public android.app.Notification.Builder addExtras(android.os.Bundle); method public android.app.Notification.Builder addPerson(java.lang.String); + method public android.app.Notification.Builder apply(android.app.Notification.Builder.Extender); method public android.app.Notification build(); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); @@ -4563,6 +4580,8 @@ package android.app { method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent); method public android.app.Notification.Builder setExtras(android.os.Bundle); method public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean); + method public android.app.Notification.Builder setGroup(java.lang.String); + method public android.app.Notification.Builder setGroupSummary(boolean); method public android.app.Notification.Builder setLargeIcon(android.graphics.Bitmap); method public android.app.Notification.Builder setLights(int, int, int); method public android.app.Notification.Builder setLocalOnly(boolean); @@ -4575,6 +4594,7 @@ package android.app { method public android.app.Notification.Builder setShowWhen(boolean); method public android.app.Notification.Builder setSmallIcon(int); method public android.app.Notification.Builder setSmallIcon(int, int); + method public android.app.Notification.Builder setSortKey(java.lang.String); method public android.app.Notification.Builder setSound(android.net.Uri); method public android.app.Notification.Builder setSound(android.net.Uri, int); method public android.app.Notification.Builder setStyle(android.app.Notification.Style); @@ -4587,6 +4607,10 @@ package android.app { method public android.app.Notification.Builder setWhen(long); } + public static abstract interface Notification.Builder.Extender { + method public abstract android.app.Notification.Builder applyTo(android.app.Notification.Builder); + } + public static class Notification.InboxStyle extends android.app.Notification.Style { ctor public Notification.InboxStyle(); ctor public Notification.InboxStyle(android.app.Notification.Builder); @@ -4690,6 +4714,31 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RemoteInput implements android.os.Parcelable { + method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); + method public int describeContents(); + method public boolean getAllowFreeFormInput(); + method public java.lang.CharSequence[] getChoices(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getLabel(); + method public java.lang.String getResultKey(); + method public static android.os.Bundle getResultsFromIntent(android.content.Intent); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; + field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results"; + } + + public static final class RemoteInput.Builder { + ctor public RemoteInput.Builder(java.lang.String); + method public android.app.RemoteInput.Builder addExtras(android.os.Bundle); + method public android.app.RemoteInput build(); + method public android.os.Bundle getExtras(); + method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); + method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); + method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); + } + public class SearchManager implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener { method public android.content.ComponentName getGlobalSearchActivity(); method public android.app.SearchableInfo getSearchableInfo(android.content.ComponentName); @@ -4810,7 +4859,7 @@ package android.app { field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0 } - public class SharedElementListener { + public abstract class SharedElementListener { ctor public SharedElementListener(); method public void handleRejectedSharedElements(java.util.List<android.view.View>); method public void remapSharedElements(java.util.List<java.lang.String>, java.util.Map<java.lang.String, android.view.View>); @@ -5047,6 +5096,7 @@ package android.app.admin { method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); + method public boolean isApplicationBlocked(android.content.ComponentName, java.lang.String); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(android.content.ComponentName); method public boolean isProfileOwnerApp(java.lang.String); @@ -5054,8 +5104,11 @@ package android.app.admin { method public void removeActiveAdmin(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); + method public boolean setApplicationBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); + method public int setApplicationsBlocked(android.content.ComponentName, android.content.Intent, boolean); method public void setCameraDisabled(android.content.ComponentName, boolean); + method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setKeyguardDisabledFeatures(android.content.ComponentName, int); method public void setLockTaskComponents(android.content.ComponentName[]) throws java.lang.SecurityException; method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); @@ -5071,6 +5124,7 @@ package android.app.admin { method public void setPasswordMinimumUpperCase(android.content.ComponentName, int); method public void setPasswordQuality(android.content.ComponentName, int); method public void setProfileEnabled(android.content.ComponentName); + method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void wipeData(int); field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; @@ -5364,6 +5418,8 @@ package android.bluetooth { method public boolean disable(); method public boolean enable(); method public java.lang.String getAddress(); + method public android.bluetooth.BluetoothLeAdvertiser getBluetoothLeAdvertiser(); + method public android.bluetooth.BluetoothLeScanner getBluetoothLeScanner(); method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter(); method public java.lang.String getName(); @@ -6006,6 +6062,178 @@ package android.bluetooth { method public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int); } + public final class BluetoothLeAdvertiseScanData { + ctor public BluetoothLeAdvertiseScanData(); + field public static final int ADVERTISING_DATA = 0; // 0x0 + field public static final int PARSED_SCAN_RECORD = 2; // 0x2 + } + + public static abstract class BluetoothLeAdvertiseScanData.AdvertiseBaseData { + method public int getDataType(); + method public int getManufacturerId(); + method public byte[] getManufacturerSpecificData(); + method public byte[] getServiceData(); + method public android.os.ParcelUuid getServiceDataUuid(); + method public java.util.List<android.os.ParcelUuid> getServiceUuids(); + } + + public static final class BluetoothLeAdvertiseScanData.AdvertisementData extends android.bluetooth.BluetoothLeAdvertiseScanData.AdvertiseBaseData implements android.os.Parcelable { + method public int describeContents(); + method public boolean getIncludeTxPowerLevel(); + method public static android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder newBuilder(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class BluetoothLeAdvertiseScanData.AdvertisementData.Builder { + ctor public BluetoothLeAdvertiseScanData.AdvertisementData.Builder(); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData build(); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder dataType(int); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder includeTxPowerLevel(boolean); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder manufacturerData(int, byte[]); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder serviceData(android.os.ParcelUuid, byte[]); + method public android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData.Builder serviceUuids(java.util.List<android.os.ParcelUuid>); + } + + public static final class BluetoothLeAdvertiseScanData.ScanRecord extends android.bluetooth.BluetoothLeAdvertiseScanData.AdvertiseBaseData { + method public int getAdvertiseFlags(); + method public java.lang.String getLocalName(); + method public static android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord.Parser getParser(); + method public int getTxPowerLevel(); + } + + public static final class BluetoothLeAdvertiseScanData.ScanRecord.Parser { + ctor public BluetoothLeAdvertiseScanData.ScanRecord.Parser(); + method public android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord parseFromScanRecord(byte[]); + } + + public class BluetoothLeAdvertiser { + method public void startAdvertising(android.bluetooth.BluetoothLeAdvertiser.Settings, android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData, android.bluetooth.BluetoothLeAdvertiser.AdvertiseCallback); + method public void stopAdvertising(android.bluetooth.BluetoothLeAdvertiser.Settings, android.bluetooth.BluetoothLeAdvertiser.AdvertiseCallback); + } + + public static abstract interface BluetoothLeAdvertiser.AdvertiseCallback { + method public abstract void onFailure(int); + method public abstract void onSuccess(android.bluetooth.BluetoothLeAdvertiser.Settings); + field public static final int ADVERISING_NOT_STARTED = 4; // 0x4 + field public static final int ADVERTISING_ALREADY_STARTED = 3; // 0x3 + field public static final int ADVERTISING_SERVICE_UNKNOWN = 1; // 0x1 + field public static final int CONTROLLER_FAILURE = 5; // 0x5 + field public static final int TOO_MANY_ADVERTISERS = 2; // 0x2 + } + + public static final class BluetoothLeAdvertiser.Settings implements android.os.Parcelable { + method public int describeContents(); + method public int getMode(); + method public int getTxPowerLevel(); + method public int getType(); + method public static android.bluetooth.BluetoothLeAdvertiser.Settings.Builder newBuilder(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1 + field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2 + field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0 + field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3 + field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1 + field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2 + field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0 + field public static final int ADVERTISE_TYPE_CONNECTABLE = 2; // 0x2 + field public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; // 0x0 + field public static final int ADVERTISE_TYPE_SCANNABLE = 1; // 0x1 + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class BluetoothLeAdvertiser.Settings.Builder { + method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder advertiseMode(int); + method public android.bluetooth.BluetoothLeAdvertiser.Settings build(); + method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder txPowerLevel(int); + method public android.bluetooth.BluetoothLeAdvertiser.Settings.Builder type(int); + } + + public final class BluetoothLeScanFilter implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getDeviceAddress(); + method public java.lang.String getLocalName(); + method public byte[] getManufacturerData(); + method public byte[] getManufacturerDataMask(); + method public int getManufacturerId(); + method public int getMaxRssi(); + method public int getMinRssi(); + method public byte[] getServiceData(); + method public byte[] getServiceDataMask(); + method public android.os.ParcelUuid getServiceUuid(); + method public android.os.ParcelUuid getServiceUuidMask(); + method public boolean matches(android.bluetooth.BluetoothLeScanner.ScanResult); + method public static android.bluetooth.BluetoothLeScanFilter.Builder newBuilder(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class BluetoothLeScanFilter.Builder { + method public android.bluetooth.BluetoothLeScanFilter build(); + method public android.bluetooth.BluetoothLeScanFilter.Builder macAddress(java.lang.String); + method public android.bluetooth.BluetoothLeScanFilter.Builder manufacturerData(int, byte[]); + method public android.bluetooth.BluetoothLeScanFilter.Builder manufacturerDataMask(byte[]); + method public android.bluetooth.BluetoothLeScanFilter.Builder name(java.lang.String); + method public android.bluetooth.BluetoothLeScanFilter.Builder rssiRange(int, int); + method public android.bluetooth.BluetoothLeScanFilter.Builder serviceData(byte[]); + method public android.bluetooth.BluetoothLeScanFilter.Builder serviceDataMask(byte[]); + method public android.bluetooth.BluetoothLeScanFilter.Builder serviceUuid(android.os.ParcelUuid); + method public android.bluetooth.BluetoothLeScanFilter.Builder serviceUuidMask(android.os.ParcelUuid); + } + + public class BluetoothLeScanner { + method public void startScan(java.util.List<android.bluetooth.BluetoothLeScanFilter>, android.bluetooth.BluetoothLeScanner.Settings, android.bluetooth.BluetoothLeScanner.ScanCallback); + method public void stopScan(android.bluetooth.BluetoothLeScanner.Settings); + } + + public static abstract interface BluetoothLeScanner.ScanCallback { + method public abstract void onBatchScanResults(java.util.List<android.bluetooth.BluetoothLeScanner.ScanResult>); + method public abstract void onDeviceFound(android.bluetooth.BluetoothLeScanner.ScanResult); + method public abstract void onDeviceLost(android.bluetooth.BluetoothDevice); + method public abstract void onDeviceUpdate(android.bluetooth.BluetoothLeScanner.ScanResult); + method public abstract void onScanFailed(int); + field public static final int APPLICATION_REGISTRATION_FAILED = 2; // 0x2 + field public static final int CONTROLLER_FAILURE = 4; // 0x4 + field public static final int GATT_SERVICE_FAILURE = 3; // 0x3 + field public static final int SCAN_ALREADY_STARTED = 1; // 0x1 + } + + public static final class BluetoothLeScanner.ScanResult implements android.os.Parcelable { + ctor public BluetoothLeScanner.ScanResult(android.bluetooth.BluetoothDevice, byte[], int, long); + method public int describeContents(); + method public android.bluetooth.BluetoothDevice getDevice(); + method public int getRssi(); + method public byte[] getScanRecord(); + method public long getTimestampMicros(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class BluetoothLeScanner.Settings implements android.os.Parcelable { + method public int describeContents(); + method public int getCallbackType(); + method public long getReportDelayMicros(); + method public int getScanMode(); + method public int getScanResultType(); + method public static android.bluetooth.BluetoothLeScanner.Settings.Builder newBuilder(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALLBACK_TYPE_ON_FOUND = 1; // 0x1 + field public static final int CALLBACK_TYPE_ON_LOST = 2; // 0x2 + field public static final int CALLBACK_TYPE_ON_UPDATE = 0; // 0x0 + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int SCAN_MODE_BALANCED = 1; // 0x1 + field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2 + field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0 + field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0 + } + + public static class BluetoothLeScanner.Settings.Builder { + method public android.bluetooth.BluetoothLeScanner.Settings build(); + method public android.bluetooth.BluetoothLeScanner.Settings.Builder callbackType(int); + method public android.bluetooth.BluetoothLeScanner.Settings.Builder reportDelayMicros(long); + method public android.bluetooth.BluetoothLeScanner.Settings.Builder scanMode(int); + } + public final class BluetoothManager { method public android.bluetooth.BluetoothAdapter getAdapter(); method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int); @@ -8091,6 +8319,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory"; field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch"; + field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview"; field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi"; field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final int GET_ACTIVITIES = 1; // 0x1 @@ -10908,7 +11137,6 @@ package android.graphics.drawable { method public void applyTheme(android.content.res.Resources.Theme); method public boolean canApplyTheme(); method public void clearColorFilter(); - method public void clearHotspots(); method public final void copyBounds(android.graphics.Rect); method public final android.graphics.Rect copyBounds(); method public static android.graphics.drawable.Drawable createFromPath(java.lang.String); @@ -10951,7 +11179,6 @@ package android.graphics.drawable { method protected void onBoundsChange(android.graphics.Rect); method protected boolean onLevelChange(int); method protected boolean onStateChange(int[]); - method public void removeHotspot(int); method public static int resolveOpacity(int, int); method public void scheduleSelf(java.lang.Runnable, long); method public abstract void setAlpha(int); @@ -10964,11 +11191,11 @@ package android.graphics.drawable { method public void setColorFilter(int, android.graphics.PorterDuff.Mode); method public void setDither(boolean); method public void setFilterBitmap(boolean); - method public void setHotspot(int, float, float); + method public void setHotspot(float, float); + method public void setHotspotBounds(int, int, int, int); method public final boolean setLevel(int); method public boolean setState(int[]); method public boolean setVisible(boolean, boolean); - method public boolean supportsHotspots(); method public void unscheduleSelf(java.lang.Runnable); } @@ -11027,30 +11254,6 @@ package android.graphics.drawable { method public final void setVariablePadding(boolean); } - public class DrawableWrapper extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { - ctor public DrawableWrapper(); - method public void draw(android.graphics.Canvas); - method public android.graphics.Rect getDirtyBounds(); - method protected final android.graphics.drawable.Drawable getDrawable(); - method public int getOpacity(); - method public final int[] getState(); - method public void invalidateDrawable(android.graphics.drawable.Drawable); - method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); - method public void setAlpha(int); - method public final void setBounds(int, int, int, int); - method public final void setBounds(android.graphics.Rect); - method public void setColorFilter(android.graphics.ColorFilter); - method protected void setConstantState(android.graphics.drawable.DrawableWrapper.WrapperState, android.content.res.Resources); - method protected final void setDrawable(android.graphics.drawable.Drawable, android.content.res.Resources); - method public final boolean setState(int[]); - method public void setXfermode(android.graphics.Xfermode); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); - } - - public static abstract class DrawableWrapper.WrapperState extends android.graphics.drawable.Drawable.ConstantState { - method public int getChangingConfigurations(); - } - public class GradientDrawable extends android.graphics.drawable.Drawable { ctor public GradientDrawable(); ctor public GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation, int[]); @@ -11174,6 +11377,13 @@ package android.graphics.drawable { method public void setPicture(android.graphics.Picture); } + public class RippleDrawable extends android.graphics.drawable.LayerDrawable { + method public android.graphics.Rect getDirtyBounds(); + method public android.content.res.ColorStateList getTint(); + method public void setTint(android.content.res.ColorStateList); + method public void setTintMode(android.graphics.PorterDuff.Mode); + } + public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { ctor public RotateDrawable(); method public void draw(android.graphics.Canvas); @@ -11243,13 +11453,6 @@ package android.graphics.drawable { method public void addState(int[], android.graphics.drawable.Drawable); } - public class TouchFeedbackDrawable extends android.graphics.drawable.LayerDrawable { - method public android.graphics.Rect getDirtyBounds(); - method public android.content.res.ColorStateList getTint(); - method public void setTint(android.content.res.ColorStateList); - method public void setTintMode(android.graphics.PorterDuff.Mode); - } - public class TransitionDrawable extends android.graphics.drawable.LayerDrawable implements android.graphics.drawable.Drawable.Callback { ctor public TransitionDrawable(android.graphics.drawable.Drawable[]); method public boolean isCrossFadeEnabled(); @@ -12673,15 +12876,14 @@ package android.hardware.usb { public class UsbConfiguration implements android.os.Parcelable { method public int describeContents(); - method public int getAttributes(); method public int getId(); method public android.hardware.usb.UsbInterface getInterface(int); method public int getInterfaceCount(); method public int getMaxPower(); method public java.lang.String getName(); + method public boolean isRemoteWakeup(); + method public boolean isSelfPowered(); method public void writeToParcel(android.os.Parcel, int); - field public static final int ATTR_REMOTE_WAKEUP_MASK = 32; // 0x20 - field public static final int ATTR_SELF_POWERED_MASK = 64; // 0x40 field public static final android.os.Parcelable.Creator CREATOR; } @@ -14065,6 +14267,34 @@ package android.media { field public static final int H263ProfileISWV3 = 16; // 0x10 field public static final int H263ProfileInterlace = 128; // 0x80 field public static final int H263ProfileInternet = 64; // 0x40 + field public static final int HEVCHighTierLevel1 = 2; // 0x2 + field public static final int HEVCHighTierLevel2 = 8; // 0x8 + field public static final int HEVCHighTierLevel21 = 32; // 0x20 + field public static final int HEVCHighTierLevel3 = 128; // 0x80 + field public static final int HEVCHighTierLevel31 = 512; // 0x200 + field public static final int HEVCHighTierLevel4 = 2048; // 0x800 + field public static final int HEVCHighTierLevel41 = 8192; // 0x2000 + field public static final int HEVCHighTierLevel5 = 32768; // 0x8000 + field public static final int HEVCHighTierLevel51 = 131072; // 0x20000 + field public static final int HEVCHighTierLevel52 = 524288; // 0x80000 + field public static final int HEVCHighTierLevel6 = 2097152; // 0x200000 + field public static final int HEVCHighTierLevel61 = 8388608; // 0x800000 + field public static final int HEVCHighTierLevel62 = 33554432; // 0x2000000 + field public static final int HEVCMainTierLevel1 = 1; // 0x1 + field public static final int HEVCMainTierLevel2 = 4; // 0x4 + field public static final int HEVCMainTierLevel21 = 16; // 0x10 + field public static final int HEVCMainTierLevel3 = 64; // 0x40 + field public static final int HEVCMainTierLevel31 = 256; // 0x100 + field public static final int HEVCMainTierLevel4 = 1024; // 0x400 + field public static final int HEVCMainTierLevel41 = 4096; // 0x1000 + field public static final int HEVCMainTierLevel5 = 16384; // 0x4000 + field public static final int HEVCMainTierLevel51 = 65536; // 0x10000 + field public static final int HEVCMainTierLevel52 = 262144; // 0x40000 + field public static final int HEVCMainTierLevel6 = 1048576; // 0x100000 + field public static final int HEVCMainTierLevel61 = 4194304; // 0x400000 + field public static final int HEVCMainTierLevel62 = 16777216; // 0x1000000 + field public static final int HEVCProfileMain = 1; // 0x1 + field public static final int HEVCProfileMain10 = 2; // 0x2 field public static final int MPEG4Level0 = 1; // 0x1 field public static final int MPEG4Level0b = 2; // 0x2 field public static final int MPEG4Level1 = 4; // 0x4 @@ -14754,6 +14984,7 @@ package android.media { ctor public RemoteControlClient(android.app.PendingIntent); ctor public RemoteControlClient(android.app.PendingIntent, android.os.Looper); method public android.media.RemoteControlClient.MetadataEditor editMetadata(boolean); + method public android.media.session.MediaSession getMediaSession(); method public void setMetadataUpdateListener(android.media.RemoteControlClient.OnMetadataUpdateListener); method public void setOnGetPlaybackPositionListener(android.media.RemoteControlClient.OnGetPlaybackPositionListener); method public void setPlaybackPositionUpdateListener(android.media.RemoteControlClient.OnPlaybackPositionUpdateListener); @@ -20811,6 +21042,7 @@ package android.os { field public static final java.lang.String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user"; field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location"; + field public static final java.lang.String DISALLOW_TELEPHONY = "no_telephony"; field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps"; field public static final java.lang.String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone"; field public static final java.lang.String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer"; @@ -23625,7 +23857,7 @@ package android.provider { field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; field public static final java.lang.String DEVICE_PROVISIONED = "device_provisioned"; field public static final java.lang.String HTTP_PROXY = "http_proxy"; - field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; + field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field public static final java.lang.String MODE_RINGER = "mode_ringer"; field public static final java.lang.String NETWORK_PREFERENCE = "network_preference"; field public static final java.lang.String RADIO_BLUETOOTH = "bluetooth"; @@ -23695,7 +23927,7 @@ package android.provider { field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods"; field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy"; field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility"; - field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; + field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field public static final java.lang.String LOCATION_MODE = "location_mode"; field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 @@ -25408,20 +25640,24 @@ package android.service.notification { method public final deprecated void cancelNotification(java.lang.String, java.lang.String, int); method public final void cancelNotification(java.lang.String); method public final void cancelNotifications(java.lang.String[]); + method public java.lang.String[] getActiveNotificationKeys(); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[]); - method public java.lang.String[] getOrderedNotificationKeys(); + method public android.service.notification.NotificationListenerService.Ranking getCurrentRanking(); method public android.os.IBinder onBind(android.content.Intent); method public void onListenerConnected(java.lang.String[]); - method public void onNotificationOrderUpdate(); method public abstract void onNotificationPosted(android.service.notification.StatusBarNotification); + method public void onNotificationRankingUpdate(); method public abstract void onNotificationRemoved(android.service.notification.StatusBarNotification); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; } - public class NotificationOrderUpdate implements android.os.Parcelable { - ctor public NotificationOrderUpdate(android.os.Parcel); + public static class NotificationListenerService.Ranking implements android.os.Parcelable { method public int describeContents(); + method public int getIndexOfKey(java.lang.String); + method public java.lang.String[] getOrderedKeys(); + method public boolean isAmbient(java.lang.String); + method public boolean isInterceptedByDoNotDisturb(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -30662,6 +30898,7 @@ package android.view { field public static final int SOURCE_CLASS_TRACKBALL = 4; // 0x4 field public static final int SOURCE_DPAD = 513; // 0x201 field public static final int SOURCE_GAMEPAD = 1025; // 0x401 + field public static final int SOURCE_HDMI = 33554433; // 0x2000001 field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010 field public static final int SOURCE_KEYBOARD = 257; // 0x101 field public static final int SOURCE_MOUSE = 8194; // 0x2002 @@ -30830,6 +31067,8 @@ package android.view { field public static final deprecated int FLAG_WOKE_HERE = 1; // 0x1 field public static final int KEYCODE_0 = 7; // 0x7 field public static final int KEYCODE_1 = 8; // 0x8 + field public static final int KEYCODE_11 = 227; // 0xe3 + field public static final int KEYCODE_12 = 228; // 0xe4 field public static final int KEYCODE_2 = 9; // 0x9 field public static final int KEYCODE_3 = 10; // 0xa field public static final int KEYCODE_3D_MODE = 206; // 0xce @@ -30949,6 +31188,7 @@ package android.view { field public static final int KEYCODE_KATAKANA_HIRAGANA = 215; // 0xd7 field public static final int KEYCODE_L = 40; // 0x28 field public static final int KEYCODE_LANGUAGE_SWITCH = 204; // 0xcc + field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5 field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47 field public static final int KEYCODE_M = 41; // 0x29 field public static final int KEYCODE_MANNER_MODE = 205; // 0xcd @@ -30964,6 +31204,7 @@ package android.view { field public static final int KEYCODE_MEDIA_RECORD = 130; // 0x82 field public static final int KEYCODE_MEDIA_REWIND = 89; // 0x59 field public static final int KEYCODE_MEDIA_STOP = 86; // 0x56 + field public static final int KEYCODE_MEDIA_TOP_MENU = 226; // 0xe2 field public static final int KEYCODE_MENU = 82; // 0x52 field public static final int KEYCODE_META_LEFT = 117; // 0x75 field public static final int KEYCODE_META_RIGHT = 118; // 0x76 @@ -31036,6 +31277,7 @@ package android.view { field public static final int KEYCODE_T = 48; // 0x30 field public static final int KEYCODE_TAB = 61; // 0x3d field public static final int KEYCODE_TV = 170; // 0xaa + field public static final int KEYCODE_TV_DATA_SERVICE = 230; // 0xe6 field public static final int KEYCODE_TV_INPUT = 178; // 0xb2 field public static final int KEYCODE_TV_POWER = 177; // 0xb1 field public static final int KEYCODE_U = 49; // 0x31 diff --git a/core/java/android/animation/BidirectionalTypeConverter.java b/core/java/android/animation/BidirectionalTypeConverter.java new file mode 100644 index 0000000..960650e --- /dev/null +++ b/core/java/android/animation/BidirectionalTypeConverter.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.animation; + +/** + * Abstract base class used convert type T to another type V and back again. This + * is necessary when the value types of in animation are different from the property + * type. BidirectionalTypeConverter is needed when only the final value for the + * animation is supplied to animators. + * @see PropertyValuesHolder#setConverter(TypeConverter) + */ +public abstract class BidirectionalTypeConverter<T, V> extends TypeConverter<T, V> { + private BidirectionalTypeConverter mInvertedConverter; + + public BidirectionalTypeConverter(Class<T> fromClass, Class<V> toClass) { + super(fromClass, toClass); + } + + /** + * Does a conversion from the target type back to the source type. The subclass + * must implement this when a TypeConverter is used in animations and current + * values will need to be read for an animation. + * @param value The Object to convert. + * @return A value of type T, converted from <code>value</code>. + */ + public abstract T convertBack(V value); + + /** + * Returns the inverse of this converter, where the from and to classes are reversed. + * The inverted converter uses this convert to call {@link #convertBack(Object)} for + * {@link #convert(Object)} calls and {@link #convert(Object)} for + * {@link #convertBack(Object)} calls. + * @return The inverse of this converter, where the from and to classes are reversed. + */ + public BidirectionalTypeConverter<V, T> invert() { + if (mInvertedConverter == null) { + mInvertedConverter = new InvertedConverter(this); + } + return mInvertedConverter; + } + + private static class InvertedConverter<From, To> extends BidirectionalTypeConverter<From, To> { + private BidirectionalTypeConverter<To, From> mConverter; + + public InvertedConverter(BidirectionalTypeConverter<To, From> converter) { + super(converter.getTargetType(), converter.getSourceType()); + mConverter = converter; + } + + @Override + public From convertBack(To value) { + return mConverter.convert(value); + } + + @Override + public To convert(From value) { + return mConverter.convertBack(value); + } + } +} diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index c0ce795..130754e 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -610,8 +610,8 @@ public final class ObjectAnimator extends ValueAnimator { * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). This variant supplies a <code>TypeConverter</code> to * convert from the animated values to the type of the property. If only one value is - * supplied, the <code>TypeConverter</code> must implement - * {@link TypeConverter#convertBack(Object)} to retrieve the current value. + * supplied, the <code>TypeConverter</code> must be a + * {@link android.animation.BidirectionalTypeConverter} to retrieve the current value. * * @param target The object whose property is to be animated. * @param property The property being animated. diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index 8fce80a..bf2924c 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -456,7 +456,7 @@ public class PropertyValuesHolder implements Cloneable { * cannot automatically interpolate between objects of unknown type. This variant also * takes a <code>TypeConverter</code> to convert from animated values to the type * of the property. If only one value is supplied, the <code>TypeConverter</code> - * must implement {@link TypeConverter#convertBack(Object)} to retrieve the current + * must be a {@link android.animation.BidirectionalTypeConverter} to retrieve the current * value. * * @param property The property being animated. Should not be null. @@ -635,6 +635,8 @@ public class PropertyValuesHolder implements Cloneable { /** * Sets the converter to convert from the values type to the setter's parameter type. + * If only one value is supplied, <var>converter</var> must be a + * {@link android.animation.BidirectionalTypeConverter}. * @param converter The converter to use to convert values. */ public void setConverter(TypeConverter converter) { @@ -816,12 +818,12 @@ public class PropertyValuesHolder implements Cloneable { private Object convertBack(Object value) { if (mConverter != null) { - value = mConverter.convertBack(value); - if (value == null) { + if (!(mConverter instanceof BidirectionalTypeConverter)) { throw new IllegalArgumentException("Converter " + mConverter.getClass().getName() - + " must implement convertBack and not return null."); + + " must be a BidirectionalTypeConverter"); } + value = ((BidirectionalTypeConverter) mConverter).convertBack(value); } return value; } diff --git a/core/java/android/animation/TypeConverter.java b/core/java/android/animation/TypeConverter.java index 03b3eb5..9ead2ad 100644 --- a/core/java/android/animation/TypeConverter.java +++ b/core/java/android/animation/TypeConverter.java @@ -53,16 +53,4 @@ public abstract class TypeConverter<T, V> { * @return A value of type V, converted from <code>value</code>. */ public abstract V convert(T value); - - /** - * Does a conversion from the target type back to the source type. The subclass - * must implement this when a TypeConverter is used in animations and current - * values will need to be read for an animation. By default, this will return null, - * indicating that back-conversion is not supported. - * @param value The Object to convert. - * @return A value of type T, converted from <code>value</code>. - */ - public T convertBack(V value) { - return null; - } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e1a94d7..3de971c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -779,7 +779,8 @@ public class Activity extends ContextThemeWrapper final Handler mHandler = new Handler(); private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState(); - SharedElementListener mTransitionListener = new SharedElementListener(); + SharedElementListener mEnterTransitionListener = SharedElementListener.NULL_LISTENER; + SharedElementListener mExitTransitionListener = SharedElementListener.NULL_LISTENER; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -5557,16 +5558,32 @@ public class Activity extends ContextThemeWrapper /** * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, * android.view.View, String)} was used to start an Activity, <var>listener</var> - * will be called to handle shared elements. This requires + * will be called to handle shared elements on the <i>launched</i> Activity. This requires * {@link Window#FEATURE_CONTENT_TRANSITIONS}. * - * @param listener Used to manipulate how shared element transitions function. + * @param listener Used to manipulate shared element transitions on the launched Activity. */ - public void setSharedElementListener(SharedElementListener listener) { + public void setEnterSharedElementListener(SharedElementListener listener) { if (listener == null) { - listener = new SharedElementListener(); + listener = SharedElementListener.NULL_LISTENER; } - mTransitionListener = listener; + mEnterTransitionListener = listener; + } + + /** + * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, + * android.view.View, String)} was used to start an Activity, <var>listener</var> + * will be called to handle shared elements on the <i>launching</i> Activity. Most + * calls will only come when returning from the started Activity. + * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}. + * + * @param listener Used to manipulate shared element transitions on the launching Activity. + */ + public void setExitSharedElementListener(SharedElementListener listener) { + if (listener == null) { + listener = SharedElementListener.NULL_LISTENER; + } + mExitTransitionListener = listener; } // ------------------ Internal API ------------------ @@ -5882,7 +5899,8 @@ public class Activity extends ContextThemeWrapper * have completed drawing. This is necessary only after an {@link Activity} has been made * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn * translucent again following a call to {@link - * Activity#convertToTranslucent(TranslucentConversionListener)}. + * Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener, + * ActivityOptions)} * * @hide */ diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 6c6a52f..2acf5b2 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -189,15 +189,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { final protected SharedElementListener mListener; protected ResultReceiver mResultReceiver; final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + final protected boolean mIsReturning; public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, ArrayList<String> accepted, ArrayList<String> localNames, - SharedElementListener listener) { + SharedElementListener listener, boolean isReturning) { super(new Handler()); mWindow = window; mListener = listener; mAllSharedElementNames = allSharedElementNames; + mIsReturning = isReturning; setSharedElements(accepted, localNames); if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); @@ -330,7 +332,21 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { mResultReceiver = resultReceiver; } - protected abstract Transition getViewsTransition(); + protected Transition getViewsTransition() { + if (mIsReturning) { + return getWindow().getExitTransition(); + } else { + return getWindow().getEnterTransition(); + } + } + + protected Transition getSharedElementTransition() { + if (mIsReturning) { + return getWindow().getSharedElementExitTransition(); + } else { + return getWindow().getSharedElementEnterTransition(); + } + } private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; @@ -342,4 +358,5 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return mEpicenter; } } + } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 636205b..b40d16c 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -51,7 +51,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private static final long MAX_WAIT_MS = 1500; private boolean mSharedElementTransitionStarted; - private boolean mIsReturning; private Activity mActivity; private boolean mHasStopped; private Handler mHandler; @@ -61,9 +60,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { ArrayList<String> sharedElementNames, ArrayList<String> acceptedNames, ArrayList<String> mappedNames) { super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames, - activity.mTransitionListener); + getListener(activity, acceptedNames), acceptedNames != null); mActivity = activity; - mIsReturning = acceptedNames != null; setResultReceiver(resultReceiver); prepareEnter(); Bundle resultReceiverBundle = new Bundle(); @@ -80,6 +78,12 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } + private static SharedElementListener getListener(Activity activity, + ArrayList<String> acceptedNames) { + boolean isReturning = acceptedNames != null; + return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; + } + @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { @@ -299,7 +303,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (sharedElementBundle != null) { Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); View snapshot = new View(context); - snapshot.setId(com.android.internal.R.id.shared_element); Resources resources = getWindow().getContext().getResources(); snapshot.setBackground(new BitmapDrawable(resources, bitmap)); snapshot.setViewName(name); @@ -420,12 +423,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } } - @Override - protected Transition getViewsTransition() { - return getWindow().getEnterTransition(); - } - - protected Transition getSharedElementTransition() { - return getWindow().getSharedElementEnterTransition(); - } } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 43a60a3..1d78b30 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -53,20 +53,22 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsBackgroundReady; - private boolean mIsReturning; - private boolean mIsCanceled; private Handler mHandler; public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { - super(activity.getWindow(), names, accepted, mapped, activity.mTransitionListener); - mIsReturning = isReturning; + super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), + isReturning); mIsBackgroundReady = !mIsReturning; mActivity = activity; } + private static SharedElementListener getListener(Activity activity, boolean isReturning) { + return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; + } + @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { @@ -271,13 +273,4 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } return -1; } - - @Override - protected Transition getViewsTransition() { - return getWindow().getExitTransition(); - } - - protected Transition getSharedElementTransition() { - return getWindow().getSharedElementExitTransition(); - } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index fd76b9c4..59b3a27 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -45,6 +45,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Collections; /** * A class that represents how a persistent notification is to be presented to @@ -370,6 +371,14 @@ public class Notification implements Parcelable */ public static final int FLAG_LOCAL_ONLY = 0x00000100; + /** + * Bit to be bitswise-ored into the {@link #flags} field that should be + * set if this notification is the group summary for a group of notifications. + * Grouped notifications may display in a cluster or stack on devices which + * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. + */ + public static final int FLAG_GROUP_SUMMARY = 0x00000200; + public int flags; /** @hide */ @@ -539,6 +548,34 @@ public class Notification implements Parcelable */ public String category; + private String mGroupKey; + + /** + * Get the key used to group this notification into a cluster or stack + * with other notifications on devices which support such rendering. + */ + public String getGroup() { + return mGroupKey; + } + + private String mSortKey; + + /** + * Get a sort key that orders this notification among other notifications from the + * same package. This can be useful if an external sort was already applied and an app + * would like to preserve this. Notifications will be sorted lexicographically using this + * value, although providing different priorities in addition to providing sort key may + * cause this value to be ignored. + * + * <p>This sort key can also be used to order members of a notification group. See + * {@link Builder#setGroup}. + * + * @see String#compareTo(String) + */ + public String getSortKey() { + return mSortKey; + } + /** * Additional semantic data to be carried around with this Notification. * <p> @@ -706,15 +743,18 @@ public class Notification implements Parcelable */ public static class Action implements Parcelable { private final Bundle mExtras; + private RemoteInput[] mRemoteInputs; /** * Small icon representing the action. */ public int icon; + /** * Title of the action. */ public CharSequence title; + /** * Intent to send when the user invokes this action. May be null, in which case the action * may be rendered in a disabled presentation by the system UI. @@ -728,19 +768,23 @@ public class Notification implements Parcelable actionIntent = PendingIntent.CREATOR.createFromParcel(in); } mExtras = in.readBundle(); + mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); } + /** * Use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}. */ public Action(int icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle()); + this(icon, title, intent, new Bundle(), null); } - private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras) { + private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras, + RemoteInput[] remoteInputs) { this.icon = icon; this.title = title; this.actionIntent = intent; this.mExtras = extras != null ? extras : new Bundle(); + this.mRemoteInputs = remoteInputs; } /** @@ -751,13 +795,22 @@ public class Notification implements Parcelable } /** + * Get the list of inputs to be collected from the user when this action is sent. + * May return null if no remote inputs were added. + */ + public RemoteInput[] getRemoteInputs() { + return mRemoteInputs; + } + + /** * Builder class for {@link Action} objects. */ - public static class Builder { + public static final class Builder { private final int mIcon; private final CharSequence mTitle; private final PendingIntent mIntent; private final Bundle mExtras; + private ArrayList<RemoteInput> mRemoteInputs; /** * Construct a new builder for {@link Action} object. @@ -766,7 +819,7 @@ public class Notification implements Parcelable * @param intent the {@link PendingIntent} to fire when users trigger this action */ public Builder(int icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle()); + this(icon, title, intent, new Bundle(), null); } /** @@ -775,14 +828,20 @@ public class Notification implements Parcelable * @param action the action to read fields from. */ public Builder(Action action) { - this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras)); + this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras), + action.getRemoteInputs()); } - private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras) { + private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras, + RemoteInput[] remoteInputs) { mIcon = icon; mTitle = title; mIntent = intent; mExtras = extras; + if (remoteInputs != null) { + mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length); + Collections.addAll(mRemoteInputs, remoteInputs); + } } /** @@ -809,22 +868,62 @@ public class Notification implements Parcelable } /** + * Add an input to be collected from the user when this action is sent. + * Response values can be retrieved from the fired intent by using the + * {@link RemoteInput#getResultsFromIntent} function. + * @param remoteInput a {@link RemoteInput} to add to the action + * @return this object for method chaining + */ + public Builder addRemoteInput(RemoteInput remoteInput) { + if (mRemoteInputs == null) { + mRemoteInputs = new ArrayList<RemoteInput>(); + } + mRemoteInputs.add(remoteInput); + return this; + } + + /** + * Apply an extender to this action builder. Extenders may be used to add + * metadata or change options on this builder. + */ + public Builder apply(Extender extender) { + extender.applyTo(this); + return this; + } + + /** + * Extender interface for use with {@link #apply}. Extenders may be used to add + * metadata or change options on this builder. + */ + public interface Extender { + /** + * Apply this extender to a notification action builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder applyTo(Builder builder); + } + + /** * Combine all of the options that have been set and return a new {@link Action} * object. * @return the built action */ public Action build() { - return new Action(mIcon, mTitle, mIntent, mExtras); + RemoteInput[] remoteInputs = mRemoteInputs != null + ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null; + return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs); } } @Override public Action clone() { return new Action( - this.icon, - this.title, - this.actionIntent, // safe to alias - new Bundle(this.mExtras)); + icon, + title, + actionIntent, // safe to alias + new Bundle(mExtras), + getRemoteInputs()); } @Override public int describeContents() { @@ -841,6 +940,7 @@ public class Notification implements Parcelable out.writeInt(0); } out.writeBundle(mExtras); + out.writeTypedArray(mRemoteInputs, flags); } public static final Parcelable.Creator<Action> CREATOR = new Parcelable.Creator<Action>() { @@ -960,6 +1060,10 @@ public class Notification implements Parcelable category = parcel.readString(); + mGroupKey = parcel.readString(); + + mSortKey = parcel.readString(); + extras = parcel.readBundle(); // may be null actions = parcel.createTypedArray(Action.CREATOR); // may be null @@ -1037,6 +1141,10 @@ public class Notification implements Parcelable that.category = this.category; + that.mGroupKey = this.mGroupKey; + + that.mSortKey = this.mSortKey; + if (this.extras != null) { try { that.extras = new Bundle(this.extras); @@ -1188,6 +1296,10 @@ public class Notification implements Parcelable parcel.writeString(category); + parcel.writeString(mGroupKey); + + parcel.writeString(mSortKey); + parcel.writeBundle(extras); // null ok parcel.writeTypedArray(actions, 0); // null ok @@ -1325,7 +1437,18 @@ public class Notification implements Parcelable sb.append(" flags=0x"); sb.append(Integer.toHexString(this.flags)); sb.append(String.format(" color=0x%08x", this.color)); - sb.append(" category="); sb.append(this.category); + if (this.category != null) { + sb.append(" category="); + sb.append(this.category); + } + if (this.mGroupKey != null) { + sb.append(" groupKey="); + sb.append(this.mGroupKey); + } + if (this.mSortKey != null) { + sb.append(" sortKey="); + sb.append(this.mSortKey); + } if (actions != null) { sb.append(" "); sb.append(actions.length); @@ -1408,6 +1531,8 @@ public class Notification implements Parcelable private int mProgress; private boolean mProgressIndeterminate; private String mCategory; + private String mGroupKey; + private String mSortKey; private Bundle mExtras; private int mPriority; private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); @@ -1839,6 +1964,51 @@ public class Notification implements Parcelable } /** + * Set this notification to be part of a group of notifications sharing the same key. + * Grouped notifications may display in a cluster or stack on devices which + * support such rendering. + * + * <p>To make this notification the summary for its group, also call + * {@link #setGroupSummary}. A sort order can be specified for group members by using + * {@link #setSortKey}. + * @param groupKey The group key of the group. + * @return this object for method chaining + */ + public Builder setGroup(String groupKey) { + mGroupKey = groupKey; + return this; + } + + /** + * Set this notification to be the group summary for a group of notifications. + * Grouped notifications may display in a cluster or stack on devices which + * support such rendering. Requires a group key also be set using {@link #setGroup}. + * @param isGroupSummary Whether this notification should be a group summary. + * @return this object for method chaining + */ + public Builder setGroupSummary(boolean isGroupSummary) { + setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); + return this; + } + + /** + * Set a sort key that orders this notification among other notifications from the + * same package. This can be useful if an external sort was already applied and an app + * would like to preserve this. Notifications will be sorted lexicographically using this + * value, although providing different priorities in addition to providing sort key may + * cause this value to be ignored. + * + * <p>This sort key can also be used to order members of a notification group. See + * {@link #setGroup}. + * + * @see String#compareTo(String) + */ + public Builder setSortKey(String sortKey) { + mSortKey = sortKey; + return this; + } + + /** * Merge additional metadata into this notification. * * <p>Values within the Bundle will replace existing extras values in this Builder. @@ -1949,7 +2119,7 @@ public class Notification implements Parcelable /** * Specify the value of {@link #visibility}. - + * * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default), * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}. * @@ -1971,6 +2141,28 @@ public class Notification implements Parcelable return this; } + /** + * Apply an extender to this notification builder. Extenders may be used to add + * metadata or change options on this builder. + */ + public Builder apply(Extender extender) { + extender.applyTo(this); + return this; + } + + /** + * Extender interface for use with {@link #apply}. Extenders may be used to add + * metadata or change options on this builder. + */ + public interface Extender { + /** + * Apply this extender to a notification builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder applyTo(Builder builder); + } + private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; @@ -2298,6 +2490,8 @@ public class Notification implements Parcelable n.flags |= FLAG_SHOW_LIGHTS; } n.category = mCategory; + n.mGroupKey = mGroupKey; + n.mSortKey = mSortKey; n.priority = mPriority; if (mActions.size() > 0) { n.actions = new Action[mActions.size()]; diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java new file mode 100644 index 0000000..9cfc541 --- /dev/null +++ b/core/java/android/app/RemoteInput.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A {@code RemoteInput} object specifies input to be collected from a user to be passed along with + * an intent inside a {@link android.app.PendingIntent} that is sent. + * Always use {@link RemoteInput.Builder} to create instances of this class. + * <p class="note"> See + * <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from + * a Notification</a> for more information on how to use this class. + * + * <p>The following example adds a {@code RemoteInput} to a {@link Notification.Action}, + * sets the result key as {@code quick_reply}, and sets the label as {@code Quick reply}. + * Users are prompted to input a response when they trigger the action. The results are sent along + * with the intent and can be retrieved with the result key (provided to the {@link Builder} + * constructor) from the Bundle returned by {@link #getResultsFromIntent}. + * + * <pre class="prettyprint"> + * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply"; + * Notification.Action action = new Notification.Action.Builder( + * R.drawable.reply, "Reply", actionIntent) + * <b>.addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT) + * .setLabel("Quick reply").build()</b>) + * .build();</pre> + * + * <p>When the {@link android.app.PendingIntent} is fired, the intent inside will contain the + * input results if collected. To access these results, use the {@link #getResultsFromIntent} + * function. The result values will present under the result key passed to the {@link Builder} + * constructor. + * + * <pre class="prettyprint"> + * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply"; + * Bundle results = RemoteInput.getResultsFromIntent(intent); + * if (results != null) { + * CharSequence quickReplyResult = results.getCharSequence(KEY_QUICK_REPLY_TEXT); + * }</pre> + */ +public final class RemoteInput implements Parcelable { + /** Label used to denote the clip data type used for remote input transport */ + public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results"; + + /** Extra added to a clip data intent object to hold the results bundle. */ + public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; + + private final String mResultKey; + private final CharSequence mLabel; + private final CharSequence[] mChoices; + private final boolean mAllowFreeFormInput; + private final Bundle mExtras; + + private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, + boolean allowFreeFormInput, Bundle extras) { + this.mResultKey = resultKey; + this.mLabel = label; + this.mChoices = choices; + this.mAllowFreeFormInput = allowFreeFormInput; + this.mExtras = extras; + } + + /** + * Get the key that the result of this input will be set in from the Bundle returned by + * {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent. + */ + public String getResultKey() { + return mResultKey; + } + + /** + * Get the label to display to users when collecting this input. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * Get possible input choices. This can be {@code null} if there are no choices to present. + */ + public CharSequence[] getChoices() { + return mChoices; + } + + /** + * Get whether or not users can provide an arbitrary value for + * input. If you set this to {@code false}, users must select one of the + * choices in {@link #getChoices}. An {@link IllegalArgumentException} is thrown + * if you set this to false and {@link #getChoices} returns {@code null} or empty. + */ + public boolean getAllowFreeFormInput() { + return mAllowFreeFormInput; + } + + /** + * Get additional metadata carried around with this remote input. + */ + public Bundle getExtras() { + return mExtras; + } + + /** + * Builder class for {@link RemoteInput} objects. + */ + public static final class Builder { + private final String mResultKey; + private CharSequence mLabel; + private CharSequence[] mChoices; + private boolean mAllowFreeFormInput = true; + private Bundle mExtras = new Bundle(); + + /** + * Create a builder object for {@link RemoteInput} objects. + * @param resultKey the Bundle key that refers to this input when collected from the user + */ + public Builder(String resultKey) { + if (resultKey == null) { + throw new IllegalArgumentException("Result key can't be null"); + } + mResultKey = resultKey; + } + + /** + * Set a label to be displayed to the user when collecting this input. + * @param label The label to show to users when they input a response. + * @return this object for method chaining + */ + public Builder setLabel(CharSequence label) { + mLabel = Notification.safeCharSequence(label); + return this; + } + + /** + * Specifies choices available to the user to satisfy this input. + * @param choices an array of pre-defined choices for users input. + * You must provide a non-null and non-empty array if + * you disabled free form input using {@link #setAllowFreeFormInput}. + * @return this object for method chaining + */ + public Builder setChoices(CharSequence[] choices) { + if (choices == null) { + mChoices = null; + } else { + mChoices = new CharSequence[choices.length]; + for (int i = 0; i < choices.length; i++) { + mChoices[i] = Notification.safeCharSequence(choices[i]); + } + } + return this; + } + + /** + * Specifies whether the user can provide arbitrary values. + * + * @param allowFreeFormInput The default is {@code true}. + * If you specify {@code false}, you must provide a non-null + * and non-empty array to {@link #setChoices} or an + * {@link IllegalArgumentException} is thrown. + * @return this object for method chaining + */ + public Builder setAllowFreeFormInput(boolean allowFreeFormInput) { + mAllowFreeFormInput = allowFreeFormInput; + return this; + } + + /** + * Merge additional metadata into this builder. + * + * <p>Values within the Bundle will replace existing extras values in this Builder. + * + * @see RemoteInput#getExtras + */ + public Builder addExtras(Bundle extras) { + if (extras != null) { + mExtras.putAll(extras); + } + return this; + } + + /** + * Get the metadata Bundle used by this Builder. + * + * <p>The returned Bundle is shared with this Builder. + */ + public Bundle getExtras() { + return mExtras; + } + + /** + * Combine all of the options that have been set and return a new {@link RemoteInput} + * object. + */ + public RemoteInput build() { + return new RemoteInput(mResultKey, mLabel, mChoices, mAllowFreeFormInput, mExtras); + } + } + + private RemoteInput(Parcel in) { + mResultKey = in.readString(); + mLabel = in.readCharSequence(); + mChoices = in.readCharSequenceArray(); + mAllowFreeFormInput = in.readInt() != 0; + mExtras = in.readBundle(); + } + + /** + * Get the remote input results bundle from an intent. The returned Bundle will + * contain a key/value for every result key populated by remote input collector. + * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value. + * @param intent The intent object that fired in response to an action or content intent + * which also had one or more remote input requested. + */ + public static Bundle getResultsFromIntent(Intent intent) { + ClipData clipData = intent.getClipData(); + if (clipData == null) { + return null; + } + ClipDescription clipDescription = clipData.getDescription(); + if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { + return null; + } + if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) { + return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA); + } + return null; + } + + /** + * Populate an intent object with the results gathered from remote input. This method + * should only be called by remote input collection services when sending results to a + * pending intent. + * @param remoteInputs The remote inputs for which results are being provided + * @param intent The intent to add remote inputs to. The {@link ClipData} + * field of the intent will be modified to contain the results. + * @param results A bundle holding the remote input results. This bundle should + * be populated with keys matching the result keys specified in + * {@code remoteInputs} with values being the result per key. + */ + public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, + Bundle results) { + Bundle resultsBundle = new Bundle(); + for (RemoteInput remoteInput : remoteInputs) { + Object result = results.get(remoteInput.getResultKey()); + if (result instanceof CharSequence) { + resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result); + } + } + Intent clipIntent = new Intent(); + clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle); + intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mResultKey); + out.writeCharSequence(mLabel); + out.writeCharSequenceArray(mChoices); + out.writeInt(mAllowFreeFormInput ? 1 : 0); + out.writeBundle(mExtras); + } + + public static final Creator<RemoteInput> CREATOR = new Creator<RemoteInput>() { + @Override + public RemoteInput createFromParcel(Parcel in) { + return new RemoteInput(in); + } + + @Override + public RemoteInput[] newArray(int size) { + return new RemoteInput[size]; + } + }; +} diff --git a/core/java/android/app/SharedElementListener.java b/core/java/android/app/SharedElementListener.java index d4bc019..e03e42e 100644 --- a/core/java/android/app/SharedElementListener.java +++ b/core/java/android/app/SharedElementListener.java @@ -22,15 +22,27 @@ import java.util.Map; /** * Listener provided in - * {@link Activity#setSharedElementListener(SharedElementListener)} - * to monitor the Activity transitions. The events can be used to customize or override Activity + * {@link Activity#setEnterSharedElementListener(SharedElementListener)} and + * {@link Activity#setExitSharedElementListener(SharedElementListener)} + * to monitor the Activity transitions. The events can be used to customize Activity * Transition behavior. */ -public class SharedElementListener { +public abstract class SharedElementListener { + + static final SharedElementListener NULL_LISTENER = new SharedElementListener() { + }; + /** - * Called to allow the listener to customize the start state of the shared element for - * the shared element entering transition. By default, the shared element is placed in - * the position and with the size of the shared element in the calling Activity or Fragment. + * Called to allow the listener to customize the start state of the shared element when + * transferring in shared element state. + * <p> + * The shared element will start at the size and position of the shared element + * in the launching Activity or Fragment. It will also transfer ImageView scaleType + * and imageMatrix if the shared elements in the calling and called Activities are + * ImageViews. Some applications may want to make additional changes, such as + * changing the clip bounds, scaling, or rotation if the shared element end state + * does not map well to the start state. + * </p> * * @param sharedElementNames The names of the shared elements that were accepted into * the View hierarchy. @@ -44,8 +56,17 @@ public class SharedElementListener { List<View> sharedElements, List<View> sharedElementSnapshots) {} /** - * Called to allow the listener to customize the end state of the shared element for - * the shared element entering transition. + * Called to allow the listener to customize the end state of the shared element when + * transferring in shared element state. + * <p> + * Any customization done in + * {@link #setSharedElementStart(java.util.List, java.util.List, java.util.List)} + * may need to be modified to the final state of the shared element if it is not + * automatically corrected by layout. For example, rotation or scale will not + * be affected by layout and if changed in {@link #setSharedElementStart(java.util.List, + * java.util.List, java.util.List)}, it will also have to be set here again to correct + * the end state. + * </p> * * @param sharedElementNames The names of the shared elements that were accepted into * the View hierarchy. @@ -59,13 +80,18 @@ public class SharedElementListener { List<View> sharedElements, List<View> sharedElementSnapshots) {} /** - * If nothing is done, all shared elements that were not accepted by - * {@link #remapSharedElements(java.util.List, java.util.Map)} will be Transitioned - * out of the entering scene automatically. Any elements removed from - * rejectedSharedElements must be handled by the ActivityTransitionListener. - * <p>Views in rejectedSharedElements will have their position and size set to the - * position of the calling shared element, relative to the Window decor View. This - * view may be safely added to the decor View's overlay to remain in position.</p> + * Called after {@link #remapSharedElements(java.util.List, java.util.Map)} when + * transferring shared elements in. Any shared elements that have no mapping will be in + * <var>rejectedSharedElements</var>. The elements remaining in + * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a + * View is removed from <var>rejectedSharedElements</var>, it must be handled by the + * <code>SharedElementListener</code>. + * <p> + * Views in rejectedSharedElements will have their position and size set to the + * position of the calling shared element, relative to the Window decor View and contain + * snapshots of the View from the calling Activity or Fragment. This + * view may be safely added to the decor View's overlay to remain in position. + * </p> * * @param rejectedSharedElements Views containing visual information of shared elements * that are not part of the entering scene. These Views @@ -78,6 +104,7 @@ public class SharedElementListener { /** * Lets the ActivityTransitionListener adjust the mapping of shared element names to * Views. + * * @param names The names of all shared elements transferred from the calling Activity * to the started Activity. * @param sharedElements The mapping of shared element names to Views. The best guess diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 58d707c..48ff5b6 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -911,6 +911,35 @@ public class WallpaperManager { */ public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { try { + /** + * The framework makes no attempt to limit the window size + * to the maximum texture size. Any window larger than this + * cannot be composited. + * + * Read maximum texture size from system property and scale down + * minimumWidth and minimumHeight accordingly. + */ + int maximumTextureSize; + try { + maximumTextureSize = SystemProperties.getInt("sys.max_texture_size", 0); + } catch (Exception e) { + maximumTextureSize = 0; + } + + if (maximumTextureSize > 0) { + if ((minimumWidth > maximumTextureSize) || + (minimumHeight > maximumTextureSize)) { + float aspect = (float)minimumHeight / (float)minimumWidth; + if (minimumWidth > minimumHeight) { + minimumWidth = maximumTextureSize; + minimumHeight = (int)((minimumWidth * aspect) + 0.5); + } else { + minimumHeight = maximumTextureSize; + minimumWidth = (int)((minimumHeight / aspect) + 0.5); + } + } + } + if (sGlobals.mService == null) { Log.w(TAG, "WallpaperService not running"); } else { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8884446..18e2a95 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -32,6 +32,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.provider.Settings; import android.service.trust.TrustAgentService; import android.util.Log; @@ -2050,6 +2051,68 @@ public class DevicePolicyManager { } /** + * Called by device or profile owner to block or unblock packages. When a package is blocked it + * is unavailable for use, but the data and actual package file remain. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package to block or unblock. + * @param blocked {@code true} if the package should be blocked, {@code false} if it should be + * unblocked. + * @return boolean Whether the blocked setting of the package was successfully updated. + */ + public boolean setApplicationBlocked(ComponentName admin, String packageName, + boolean blocked) { + if (mService != null) { + try { + return mService.setApplicationBlocked(admin, packageName, blocked); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return false; + } + + /** + * Called by profile or device owner to block or unblock currently installed packages. This + * should only be called by a profile or device owner running within a managed profile. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param intent An intent matching the app(s) to be updated. All apps that resolve for this + * intent will be updated in the current profile. + * @param blocked {@code true} if the packages should be blocked, {@code false} if they should + * be unblocked. + * @return int The number of activities that matched the intent and were updated. + */ + public int setApplicationsBlocked(ComponentName admin, Intent intent, boolean blocked) { + if (mService != null) { + try { + return mService.setApplicationsBlocked(admin, intent, blocked); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + + /** + * Called by device or profile owner to determine if a package is blocked. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package to retrieve the blocked status of. + * @return boolean {@code true} if the package is blocked, {@code false} otherwise. + */ + public boolean isApplicationBlocked(ComponentName admin, String packageName) { + if (mService != null) { + try { + return mService.isApplicationBlocked(admin, packageName); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return false; + } + + /** * Called by profile or device owner to re-enable a system app that was disabled by default * when the managed profile was created. This should only be called from a profile or device * owner running within a managed profile. @@ -2181,4 +2244,42 @@ public class DevicePolicyManager { } return false; } + + /** + * Called by device owners to update {@link Settings.Global} settings. Validation that the value + * of the setting is in the correct form for the setting type should be performed by the caller. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param setting The name of the setting to update. + * @param value The value to update the setting to. + */ + public void setGlobalSetting(ComponentName admin, String setting, String value) { + if (mService != null) { + try { + mService.setGlobalSetting(admin, setting, value); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by profile or device owners to update {@link Settings.Secure} settings. Validation + * that the value of the setting is in the correct form for the setting type should be performed + * by the caller. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param setting The name of the setting to update. + * @param value The value to update the setting to. + */ + public void setSecureSetting(ComponentName admin, String setting, String value) { + if (mService != null) { + try { + mService.setSecureSetting(admin, setting, value); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 03ced0f..7257158 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -124,6 +124,10 @@ interface IDevicePolicyManager { void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); void clearForwardingIntentFilters(in ComponentName admin); + boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked); + int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked); + boolean isApplicationBlocked(in ComponentName admin, in String packageName); + void enableSystemApp(in ComponentName admin, in String packageName); int enableSystemAppWithIntent(in ComponentName admin, in Intent intent); @@ -133,4 +137,7 @@ interface IDevicePolicyManager { void setLockTaskComponents(in ComponentName[] components); ComponentName[] getLockTaskComponents(); boolean isLockTaskPermitted(in ComponentName component); + + void setGlobalSetting(in ComponentName who, in String setting, in String value); + void setSecureSetting(in ComponentName who, in String setting, in String value); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index e79deec..9e1c995 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -498,6 +498,34 @@ public final class BluetoothAdapter { } /** + * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations. + */ + public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { + // TODO: Return null if this feature is not supported by hardware. + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + return new BluetoothLeAdvertiser(iGatt); + } catch (RemoteException e) { + Log.e(TAG, "failed to get BluetoothLeAdvertiser, error: " + e); + return null; + } + } + + /** + * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations. + */ + public BluetoothLeScanner getBluetoothLeScanner() { + // TODO: Return null if BLE scan is not supported by hardware. + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + return new BluetoothLeScanner(iGatt); + } catch (RemoteException e) { + Log.e(TAG, "failed to get BluetoothLeScanner, error: " + e); + return null; + } + } + + /** * Interface for BLE advertising callback. * * @hide @@ -2024,6 +2052,10 @@ public final class BluetoothAdapter { } } + @Override + public void onMultiAdvertiseCallback(int status) { + // no op + } /** * Callback reporting LE ATT MTU. * @hide diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 601d9ee..c9df9c0 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -581,7 +581,15 @@ public final class BluetoothGatt implements BluetoothProfile { public void onAdvertiseStateChange(int state, int status) { if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = " + state + " status=" + status); - } + } + + /** + * @hide + */ + @Override + public void onMultiAdvertiseCallback(int status) { + // no op. + } /** * Callback invoked when the MTU for a given connection changes diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl new file mode 100644 index 0000000..4aa8881 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothLeAdvertiseScanData.AdvertisementData;
\ No newline at end of file diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java new file mode 100644 index 0000000..d12ac6c --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents Bluetooth LE advertise and scan response data. This could be either the advertisement + * data to be advertised, or the scan record obtained from BLE scans. + * <p> + * The exact bluetooth advertising and scan response data fields and types are defined in Bluetooth + * 4.0 specification, Volume 3, Part C, Section 11 and 18, as well as Supplement to the Bluetooth + * Core Specification Version 4. Currently the following fields are allowed to be set: + * <li>Service UUIDs which identify the bluetooth gatt services running on the device. + * <li>Tx power level which is the transmission power level. + * <li>Service data which is the data associated with a service. + * <li>Manufacturer specific data which is the data associated with a particular manufacturer. + * + * @see BluetoothLeAdvertiser + */ +public final class BluetoothLeAdvertiseScanData { + private static final String TAG = "BluetoothLeAdvertiseScanData"; + + /** + * Bluetooth LE Advertising Data type, the data will be placed in AdvData field of advertising + * packet. + */ + public static final int ADVERTISING_DATA = 0; + /** + * Bluetooth LE scan response data, the data will be placed in ScanRspData field of advertising + * packet. + * <p> + * TODO: unhide when stack supports setting scan response data. + * + * @hide + */ + public static final int SCAN_RESPONSE_DATA = 1; + /** + * Scan record parsed from Bluetooth LE scans. The content can contain a concatenation of + * advertising data and scan response data. + */ + public static final int PARSED_SCAN_RECORD = 2; + + /** + * Base data type which contains the common fields for {@link AdvertisementData} and + * {@link ScanRecord}. + */ + public abstract static class AdvertiseBaseData { + + private final int mDataType; + + @Nullable + private final List<ParcelUuid> mServiceUuids; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerSpecificData; + + @Nullable + private final ParcelUuid mServiceDataUuid; + @Nullable + private final byte[] mServiceData; + + private AdvertiseBaseData(int dataType, + List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData) { + mDataType = dataType; + mServiceUuids = serviceUuids; + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + } + + /** + * Returns the type of data, indicating whether the data is advertising data, scan response + * data or scan record. + */ + public int getDataType() { + return mDataType; + } + + /** + * Returns a list of service uuids within the advertisement that are used to identify the + * bluetooth gatt services. + */ + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** + * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth + * SIG. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + /** + * Returns the manufacturer specific data which is the content of manufacturer specific data + * field. The first 2 bytes of the data contain the company id. + */ + public byte[] getManufacturerSpecificData() { + return mManufacturerSpecificData; + } + + /** + * Returns a 16 bit uuid of the service that the service data is associated with. + */ + public ParcelUuid getServiceDataUuid() { + return mServiceDataUuid; + } + + /** + * Returns service data. The first two bytes should be a 16 bit service uuid associated with + * the service data. + */ + public byte[] getServiceData() { + return mServiceData; + } + + @Override + public String toString() { + return "AdvertiseBaseData [mDataType=" + mDataType + ", mServiceUuids=" + mServiceUuids + + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" + + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" + + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + "]"; + } + } + + /** + * Advertisement data packet for Bluetooth LE advertising. This represents the data to be + * broadcasted in Bluetooth LE advertising. + * <p> + * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to + * be advertised. + * + * @see BluetoothLeAdvertiser + */ + public static final class AdvertisementData extends AdvertiseBaseData implements Parcelable { + + private boolean mIncludeTxPowerLevel; + + /** + * Whether the transmission power level will be included in the advertisement packet. + */ + public boolean getIncludeTxPowerLevel() { + return mIncludeTxPowerLevel; + } + + /** + * Returns a {@link Builder} to build {@link AdvertisementData}. + */ + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(getDataType()); + List<ParcelUuid> uuids = getServiceUuids(); + if (uuids == null) { + dest.writeInt(0); + } else { + dest.writeInt(uuids.size()); + dest.writeList(uuids); + } + + dest.writeInt(getManufacturerId()); + byte[] manufacturerData = getManufacturerSpecificData(); + if (manufacturerData == null) { + dest.writeInt(0); + } else { + dest.writeInt(manufacturerData.length); + dest.writeByteArray(manufacturerData); + } + + ParcelUuid serviceDataUuid = getServiceDataUuid(); + if (serviceDataUuid == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(serviceDataUuid, flags); + byte[] serviceData = getServiceData(); + if (serviceData == null) { + dest.writeInt(0); + } else { + dest.writeInt(serviceData.length); + dest.writeByteArray(serviceData); + } + } + dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); + } + + private AdvertisementData(int dataType, + List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, boolean mIncludeTxPowerLevel) { + super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, + manufacturerSpecificData); + this.mIncludeTxPowerLevel = mIncludeTxPowerLevel; + } + + public static final Parcelable.Creator<AdvertisementData> CREATOR = + new Creator<AdvertisementData>() { + @Override + public AdvertisementData[] newArray(int size) { + return new AdvertisementData[size]; + } + + @Override + public AdvertisementData createFromParcel(Parcel in) { + Builder builder = newBuilder(); + int dataType = in.readInt(); + builder.dataType(dataType); + if (in.readInt() > 0) { + List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); + in.readList(uuids, ParcelUuid.class.getClassLoader()); + builder.serviceUuids(uuids); + } + int manufacturerId = in.readInt(); + int manufacturerDataLength = in.readInt(); + if (manufacturerDataLength > 0) { + byte[] manufacturerData = new byte[manufacturerDataLength]; + in.readByteArray(manufacturerData); + builder.manufacturerData(manufacturerId, manufacturerData); + } + if (in.readInt() == 1) { + ParcelUuid serviceDataUuid = in.readParcelable( + ParcelUuid.class.getClassLoader()); + int serviceDataLength = in.readInt(); + if (serviceDataLength > 0) { + byte[] serviceData = new byte[serviceDataLength]; + in.readByteArray(serviceData); + builder.serviceData(serviceDataUuid, serviceData); + } + } + builder.includeTxPowerLevel(in.readByte() == 1); + return builder.build(); + } + }; + + /** + * Builder for {@link BluetoothLeAdvertiseScanData.AdvertisementData}. Use + * {@link AdvertisementData#newBuilder()} to get an instance of the Builder. + */ + public static final class Builder { + private static final int MAX_ADVERTISING_DATA_BYTES = 31; + // Each fields need one byte for field length and another byte for field type. + private static final int OVERHEAD_BYTES_PER_FIELD = 2; + // Flags field will be set by system. + private static final int FLAGS_FIELD_BYTES = 3; + + private int mDataType; + @Nullable + private List<ParcelUuid> mServiceUuids; + private boolean mIncludeTxPowerLevel; + private int mManufacturerId; + @Nullable + private byte[] mManufacturerSpecificData; + @Nullable + private ParcelUuid mServiceDataUuid; + @Nullable + private byte[] mServiceData; + + /** + * Set data type. + * + * @param dataType Data type, could only be + * {@link BluetoothLeAdvertiseScanData#ADVERTISING_DATA} + * @throws IllegalArgumentException If the {@code dataType} is invalid. + */ + public Builder dataType(int dataType) { + if (mDataType != ADVERTISING_DATA && mDataType != SCAN_RESPONSE_DATA) { + throw new IllegalArgumentException("invalid data type - " + dataType); + } + mDataType = dataType; + return this; + } + + /** + * Set the service uuids. Note the corresponding bluetooth Gatt services need to be + * already added on the device before start BLE advertising. + * + * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or + * 128-bit uuids. + * @throws IllegalArgumentException If the {@code serviceUuids} are null. + */ + public Builder serviceUuids(List<ParcelUuid> serviceUuids) { + if (serviceUuids == null) { + throw new IllegalArgumentException("serivceUuids are null"); + } + mServiceUuids = serviceUuids; + return this; + } + + /** + * Add service data to advertisement. + * + * @param serviceDataUuid A 16 bit uuid of the service data + * @param serviceData Service data - the first two bytes of the service data are the + * service data uuid. + * @throws IllegalArgumentException If the {@code serviceDataUuid} or + * {@code serviceData} is empty. + */ + public Builder serviceData(ParcelUuid serviceDataUuid, byte[] serviceData) { + if (serviceDataUuid == null || serviceData == null) { + throw new IllegalArgumentException( + "serviceDataUuid or serviceDataUuid is null"); + } + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + return this; + } + + /** + * Set manufacturer id and data. See <a + * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned + * manufacturer identifies</a> for the existing company identifiers. + * + * @param manufacturerId Manufacturer id assigned by Bluetooth SIG. + * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of + * the manufacturer specific data are the manufacturer id. + * @throws IllegalArgumentException If the {@code manufacturerId} is negative or + * {@code manufacturerSpecificData} is null. + */ + public Builder manufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { + if (manufacturerId < 0) { + throw new IllegalArgumentException( + "invalid manufacturerId - " + manufacturerId); + } + if (manufacturerSpecificData == null) { + throw new IllegalArgumentException("manufacturerSpecificData is null"); + } + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + return this; + } + + /** + * Whether the transmission power level should be included in the advertising packet. + */ + public Builder includeTxPowerLevel(boolean includeTxPowerLevel) { + mIncludeTxPowerLevel = includeTxPowerLevel; + return this; + } + + /** + * Build the {@link BluetoothLeAdvertiseScanData}. + * + * @throws IllegalArgumentException If the data size is larger than 31 bytes. + */ + public AdvertisementData build() { + if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) { + throw new IllegalArgumentException( + "advertisement data size is larger than 31 bytes"); + } + return new AdvertisementData(mDataType, + mServiceUuids, + mServiceDataUuid, + mServiceData, mManufacturerId, mManufacturerSpecificData, + mIncludeTxPowerLevel); + } + + // Compute the size of the advertisement data. + private int totalBytes() { + int size = FLAGS_FIELD_BYTES; // flags field is always set. + if (mServiceUuids != null) { + int num16BitUuids = 0; + int num32BitUuids = 0; + int num128BitUuids = 0; + for (ParcelUuid uuid : mServiceUuids) { + if (BluetoothUuid.is16BitUuid(uuid)) { + ++num16BitUuids; + } else if (BluetoothUuid.is32BitUuid(uuid)) { + ++num32BitUuids; + } else { + ++num128BitUuids; + } + } + // 16 bit service uuids are grouped into one field when doing advertising. + if (num16BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; + } + // 32 bit service uuids are grouped into one field when doing advertising. + if (num32BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; + } + // 128 bit service uuids are grouped into one field when doing advertising. + if (num128BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; + } + } + if (mServiceData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length; + } + if (mManufacturerSpecificData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length; + } + if (mIncludeTxPowerLevel) { + size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. + } + return size; + } + } + + } + + /** + * Represents a scan record from Bluetooth LE scan. + */ + public static final class ScanRecord extends AdvertiseBaseData { + // Flags of the advertising data. + private final int mAdvertiseFlags; + + // Transmission power level(in dB). + private final int mTxPowerLevel; + + // Local name of the Bluetooth LE device. + private final String mLocalName; + + /** + * Returns the advertising flags indicating the discoverable mode and capability of the + * device. Returns -1 if the flag field is not set. + */ + public int getAdvertiseFlags() { + return mAdvertiseFlags; + } + + /** + * Returns the transmission power level of the packet in dBm. Returns + * {@link Integer#MIN_VALUE} if the field is not set. This value can be used to calculate + * the path loss of a received packet using the following equation: + * <p> + * <code>pathloss = txPowerLevel - rssi</code> + */ + public int getTxPowerLevel() { + return mTxPowerLevel; + } + + /** + * Returns the local name of the BLE device. The is a UTF-8 encoded string. + */ + @Nullable + public String getLocalName() { + return mLocalName; + } + + ScanRecord(int dataType, + List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, + String localName) { + super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, + manufacturerSpecificData); + mLocalName = localName; + mAdvertiseFlags = advertiseFlags; + mTxPowerLevel = txPowerLevel; + } + + /** + * Get a {@link Parser} to parse the scan record byte array into {@link ScanRecord}. + */ + public static Parser getParser() { + return new Parser(); + } + + /** + * A parser class used to parse a Bluetooth LE scan record to + * {@link BluetoothLeAdvertiseScanData}. Note not all field types would be parsed. + */ + public static final class Parser { + private static final String PARSER_TAG = "BluetoothLeAdvertiseDataParser"; + + // The following data type values are assigned by Bluetooth SIG. + // For more details refer to Bluetooth 4.0 specification, Volume 3, Part C, Section 18. + private static final int DATA_TYPE_FLAGS = 0x01; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; + private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; + private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; + private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; + private static final int DATA_TYPE_SERVICE_DATA = 0x16; + private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; + + // Helper method to extract bytes from byte array. + private static byte[] extractBytes(byte[] scanRecord, int start, int length) { + byte[] bytes = new byte[length]; + System.arraycopy(scanRecord, start, bytes, 0, length); + return bytes; + } + + /** + * Parse scan record to {@link BluetoothLeAdvertiseScanData.ScanRecord}. + * <p> + * The format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11 + * and 18. + * <p> + * All numerical multi-byte entities and values shall use little-endian + * <strong>byte</strong> order. + * + * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. + */ + public ScanRecord parseFromScanRecord(byte[] scanRecord) { + if (scanRecord == null) { + return null; + } + + int currentPos = 0; + int advertiseFlag = -1; + List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); + String localName = null; + int txPowerLevel = Integer.MIN_VALUE; + ParcelUuid serviceDataUuid = null; + byte[] serviceData = null; + int manufacturerId = -1; + byte[] manufacturerSpecificData = null; + + try { + while (currentPos < scanRecord.length) { + // length is unsigned int. + int length = scanRecord[currentPos++] & 0xFF; + if (length == 0) { + break; + } + // Note the length includes the length of the field type itself. + int dataLength = length - 1; + // fieldType is unsigned int. + int fieldType = scanRecord[currentPos++] & 0xFF; + switch (fieldType) { + case DATA_TYPE_FLAGS: + advertiseFlag = scanRecord[currentPos] & 0xFF; + break; + case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, + dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); + break; + case DATA_TYPE_LOCAL_NAME_SHORT: + case DATA_TYPE_LOCAL_NAME_COMPLETE: + localName = new String( + extractBytes(scanRecord, currentPos, dataLength)); + break; + case DATA_TYPE_TX_POWER_LEVEL: + txPowerLevel = scanRecord[currentPos]; + break; + case DATA_TYPE_SERVICE_DATA: + serviceData = extractBytes(scanRecord, currentPos, dataLength); + // The first two bytes of the service data are service data uuid. + int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; + byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, + serviceUuidLength); + serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); + break; + case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: + manufacturerSpecificData = extractBytes(scanRecord, currentPos, + dataLength); + // The first two bytes of the manufacturer specific data are + // manufacturer ids in little endian. + manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) + + (manufacturerSpecificData[0] & 0xFF); + break; + default: + // Just ignore, we don't handle such data type. + break; + } + currentPos += dataLength; + } + + if (serviceUuids.isEmpty()) { + serviceUuids = null; + } + return new ScanRecord(PARSED_SCAN_RECORD, + serviceUuids, serviceDataUuid, serviceData, + manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, + localName); + } catch (IndexOutOfBoundsException e) { + Log.e(PARSER_TAG, + "unable to parse scan record: " + Arrays.toString(scanRecord)); + return null; + } + } + + // Parse service uuids. + private int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, + int uuidLength, List<ParcelUuid> serviceUuids) { + while (dataLength > 0) { + byte[] uuidBytes = extractBytes(scanRecord, currentPos, + uuidLength); + serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); + dataLength -= uuidLength; + currentPos += uuidLength; + } + return currentPos; + } + } + } + +} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl b/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl new file mode 100644 index 0000000..3108610 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothLeAdvertiser.Settings;
\ No newline at end of file diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/BluetoothLeAdvertiser.java new file mode 100644 index 0000000..2a8aa23 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAdvertiser.java @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop + * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by + * {@link BluetoothLeAdvertiseScanData.AdvertisementData}. + * <p> + * To get an instance of {@link BluetoothLeAdvertiser}, call the + * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. + * <p> + * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see BluetoothLeAdvertiseScanData.AdvertisementData + */ +public class BluetoothLeAdvertiser { + + private static final String TAG = "BluetoothLeAdvertiser"; + + /** + * The {@link Settings} provide a way to adjust advertising preferences for each individual + * advertisement. Use {@link Settings.Builder} to create a {@link Settings} instance. + */ + public static final class Settings implements Parcelable { + /** + * Perform Bluetooth LE advertising in low power mode. This is the default and preferred + * advertising mode as it consumes the least power. + */ + public static final int ADVERTISE_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE advertising in balanced power mode. This is balanced between + * advertising frequency and power consumption. + */ + public static final int ADVERTISE_MODE_BALANCED = 1; + /** + * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest + * power consumption and should not be used for background continuous advertising. + */ + public static final int ADVERTISE_MODE_LOW_LATENCY = 2; + + /** + * Advertise using the lowest transmission(tx) power level. An app can use low transmission + * power to restrict the visibility range of its advertising packet. + */ + public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; + /** + * Advertise using low tx power level. + */ + public static final int ADVERTISE_TX_POWER_LOW = 1; + /** + * Advertise using medium tx power level. + */ + public static final int ADVERTISE_TX_POWER_MEDIUM = 2; + /** + * Advertise using high tx power level. This is corresponding to largest visibility range of + * the advertising packet. + */ + public static final int ADVERTISE_TX_POWER_HIGH = 3; + + /** + * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.0 + * vol6, part B, section 4.4.2 - Advertising state. + */ + public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; + /** + * Scannable undirected advertise type, as defined in same spec mentioned above. This event + * type allows a scanner to send a scan request asking additional information about the + * advertiser. + */ + public static final int ADVERTISE_TYPE_SCANNABLE = 1; + /** + * Connectable undirected advertising type, as defined in same spec mentioned above. This + * event type allows a scanner to send scan request asking additional information about the + * advertiser. It also allows an initiator to send a connect request for connection. + */ + public static final int ADVERTISE_TYPE_CONNECTABLE = 2; + + private final int mAdvertiseMode; + private final int mAdvertiseTxPowerLevel; + private final int mAdvertiseEventType; + + private Settings(int advertiseMode, int advertiseTxPowerLevel, + int advertiseEventType) { + mAdvertiseMode = advertiseMode; + mAdvertiseTxPowerLevel = advertiseTxPowerLevel; + mAdvertiseEventType = advertiseEventType; + } + + private Settings(Parcel in) { + mAdvertiseMode = in.readInt(); + mAdvertiseTxPowerLevel = in.readInt(); + mAdvertiseEventType = in.readInt(); + } + + /** + * Creates a {@link Builder} to construct a {@link Settings} object. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Returns the advertise mode. + */ + public int getMode() { + return mAdvertiseMode; + } + + /** + * Returns the tx power level for advertising. + */ + public int getTxPowerLevel() { + return mAdvertiseTxPowerLevel; + } + + /** + * Returns the advertise event type. + */ + public int getType() { + return mAdvertiseEventType; + } + + @Override + public String toString() { + return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" + + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mAdvertiseMode); + dest.writeInt(mAdvertiseTxPowerLevel); + dest.writeInt(mAdvertiseEventType); + } + + public static final Parcelable.Creator<Settings> CREATOR = + new Creator<BluetoothLeAdvertiser.Settings>() { + @Override + public Settings[] newArray(int size) { + return new Settings[size]; + } + + @Override + public Settings createFromParcel(Parcel in) { + return new Settings(in); + } + }; + + /** + * Builder class for {@link BluetoothLeAdvertiser.Settings}. Caller should use + * {@link Settings#newBuilder()} to get an instance of the builder. + */ + public static final class Builder { + private int mMode = ADVERTISE_MODE_LOW_POWER; + private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; + private int mType = ADVERTISE_TYPE_NON_CONNECTABLE; + + // Private constructor, use Settings.newBuilder() get an instance of BUILDER. + private Builder() { + } + + /** + * Set advertise mode to control the advertising power and latency. + * + * @param advertiseMode Bluetooth LE Advertising mode, can only be one of + * {@link Settings#ADVERTISE_MODE_LOW_POWER}, + * {@link Settings#ADVERTISE_MODE_BALANCED}, or + * {@link Settings#ADVERTISE_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the advertiseMode is invalid. + */ + public Builder advertiseMode(int advertiseMode) { + if (advertiseMode < ADVERTISE_MODE_LOW_POWER + || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("unknown mode " + advertiseMode); + } + mMode = advertiseMode; + return this; + } + + /** + * Set advertise tx power level to control the transmission power level for the + * advertising. + * + * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one + * of {@link Settings#ADVERTISE_TX_POWER_ULTRA_LOW}, + * {@link Settings#ADVERTISE_TX_POWER_LOW}, + * {@link Settings#ADVERTISE_TX_POWER_MEDIUM} or + * {@link Settings#ADVERTISE_TX_POWER_HIGH}. + * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. + */ + public Builder txPowerLevel(int txPowerLevel) { + if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW + || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { + throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); + } + mTxPowerLevel = txPowerLevel; + return this; + } + + /** + * Set advertise type to control the event type of advertising. + * + * @param type Bluetooth LE Advertising type, can be either + * {@link Settings#ADVERTISE_TYPE_NON_CONNECTABLE}, + * {@link Settings#ADVERTISE_TYPE_SCANNABLE} or + * {@link Settings#ADVERTISE_TYPE_CONNECTABLE}. + * @throws IllegalArgumentException If the {@code type} is invalid. + */ + public Builder type(int type) { + if (type < ADVERTISE_TYPE_NON_CONNECTABLE + || type > ADVERTISE_TYPE_CONNECTABLE) { + throw new IllegalArgumentException("unknown advertise type " + type); + } + mType = type; + return this; + } + + /** + * Build the {@link Settings} object. + */ + public Settings build() { + return new Settings(mMode, mTxPowerLevel, mType); + } + } + } + + /** + * Callback of Bluetooth LE advertising, which is used to deliver operation status for start and + * stop advertising. + */ + public interface AdvertiseCallback { + + /** + * The operation is success. + * + * @hide + */ + public static final int SUCCESS = 0; + /** + * Fails to start advertising as the advertisement data contains services that are not added + * to the local bluetooth Gatt server. + */ + public static final int ADVERTISING_SERVICE_UNKNOWN = 1; + /** + * Fails to start advertising as system runs out of quota for advertisers. + */ + public static final int TOO_MANY_ADVERTISERS = 2; + + /** + * Fails to start advertising as the advertising is already started. + */ + public static final int ADVERTISING_ALREADY_STARTED = 3; + /** + * Fails to stop advertising as the advertising is not started. + */ + public static final int ADVERISING_NOT_STARTED = 4; + + /** + * Operation fails due to bluetooth controller failure. + */ + public static final int CONTROLLER_FAILURE = 5; + + /** + * Callback when advertising operation succeeds. + * + * @param settingsInEffect The actual settings used for advertising, which may be different + * from what the app asks. + */ + public void onSuccess(Settings settingsInEffect); + + /** + * Callback when advertising operation fails. + * + * @param errorCode Error code for failures. + */ + public void onFailure(int errorCode); + } + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<Settings, AdvertiseCallbackWrapper> + mLeAdvertisers = new HashMap<Settings, AdvertiseCallbackWrapper>(); + + // Package private constructor, use BluetoothAdapter.getLeAdvertiser() instead. + BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + } + + /** + * Bluetooth GATT interface callbacks for advertising. + */ + private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; + private final AdvertiseCallback mAdvertiseCallback; + private final AdvertisementData mAdvertisement; + private final Settings mSettings; + private final IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // >0: registered and scan started + private int mLeHandle; + private boolean isAdvertising = false; + + public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, + AdvertisementData advertiseData, Settings settings, + IBluetoothGatt bluetoothGatt) { + mAdvertiseCallback = advertiseCallback; + mAdvertisement = advertiseData; + mSettings = settings; + mBluetoothGatt = bluetoothGatt; + mLeHandle = 0; + } + + public boolean advertiseStarted() { + boolean started = false; + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + started = (mLeHandle > 0 && isAdvertising); + } + return started; + } + + public boolean advertiseStopped() { + synchronized (this) { + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + return !isAdvertising; + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); + synchronized (this) { + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, mSettings); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le advertise: " + e); + mLeHandle = -1; + notifyAll(); + } catch (Exception e) { + Log.e(TAG, "fail to start advertise: " + e.getStackTrace()); + } + } else { + // registration failed + mLeHandle = -1; + notifyAll(); + } + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + // no op + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + synchronized (this) { + if (status == 0) { + isAdvertising = !isAdvertising; + if (!isAdvertising) { + try { + mBluetoothGatt.unregisterClient(mLeHandle); + mLeHandle = -1; + } catch (RemoteException e) { + Log.e(TAG, "remote exception when unregistering", e); + } + } + mAdvertiseCallback.onSuccess(null); + } else { + mAdvertiseCallback.onFailure(status); + } + notifyAll(); + } + + } + + /** + * Callback reporting LE ATT MTU. + * + * @hide + */ + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + /** + * Start Bluetooth LE Advertising. + * + * @param settings {@link Settings} for Bluetooth LE advertising. + * @param advertiseData {@link AdvertisementData} to be advertised. + * @param callback {@link AdvertiseCallback} for advertising status. + */ + public void startAdvertising(Settings settings, + AdvertisementData advertiseData, final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + if (mLeAdvertisers.containsKey(settings)) { + postCallbackFailure(callback, AdvertiseCallback.ADVERTISING_ALREADY_STARTED); + return; + } + AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, + settings, + mBluetoothGatt); + UUID uuid = UUID.randomUUID(); + try { + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.advertiseStarted()) { + mLeAdvertisers.put(settings, wrapper); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + /** + * Stop Bluetooth LE advertising. Returns immediately, the operation status will be delivered + * through the {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param settings {@link Settings} used to start Bluetooth LE advertising. + * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. + */ + public void stopAdvertising(final Settings settings, final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(settings); + if (wrapper == null) { + postCallbackFailure(callback, AdvertiseCallback.ADVERISING_NOT_STARTED); + return; + } + try { + mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle); + if (wrapper.advertiseStopped()) { + mLeAdvertisers.remove(settings); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + private void postCallbackFailure(final AdvertiseCallback callback, final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onFailure(error); + } + }); + } +} diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl b/core/java/android/bluetooth/BluetoothLeScanFilter.aidl new file mode 100644 index 0000000..86ee06d --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeScanFilter.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothLeScanFilter; diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.java b/core/java/android/bluetooth/BluetoothLeScanFilter.java new file mode 100644 index 0000000..2ed85ba --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeScanFilter.java @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord; +import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * {@link BluetoothLeScanFilter} abstracts different scan filters across Bluetooth Advertisement + * packet fields. + * <p> + * Current filtering on the following fields are supported: + * <li>Service UUIDs which identify the bluetooth gatt services running on the device. + * <li>Name of remote Bluetooth LE device. + * <li>Mac address of the remote device. + * <li>Rssi which indicates the received power level. + * <li>Service data which is the data associated with a service. + * <li>Manufacturer specific data which is the data associated with a particular manufacturer. + * + * @see BluetoothLeAdvertiseScanData.ScanRecord + * @see BluetoothLeScanner + */ +public final class BluetoothLeScanFilter implements Parcelable { + + @Nullable + private final String mLocalName; + + @Nullable + private final String mMacAddress; + + @Nullable + private final ParcelUuid mServiceUuid; + @Nullable + private final ParcelUuid mServiceUuidMask; + + @Nullable + private final byte[] mServiceData; + @Nullable + private final byte[] mServiceDataMask; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerData; + @Nullable + private final byte[] mManufacturerDataMask; + + private final int mMinRssi; + private final int mMaxRssi; + + private BluetoothLeScanFilter(String name, String macAddress, ParcelUuid uuid, + ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask, + int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, + int minRssi, int maxRssi) { + mLocalName = name; + mServiceUuid = uuid; + mServiceUuidMask = uuidMask; + mMacAddress = macAddress; + mServiceData = serviceData; + mServiceDataMask = serviceDataMask; + mManufacturerId = manufacturerId; + mManufacturerData = manufacturerData; + mManufacturerDataMask = manufacturerDataMask; + mMinRssi = minRssi; + mMaxRssi = maxRssi; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLocalName == null ? 0 : 1); + if (mLocalName != null) { + dest.writeString(mLocalName); + } + dest.writeInt(mMacAddress == null ? 0 : 1); + if (mMacAddress != null) { + dest.writeString(mMacAddress); + } + dest.writeInt(mServiceUuid == null ? 0 : 1); + if (mServiceUuid != null) { + dest.writeParcelable(mServiceUuid, flags); + } + dest.writeInt(mServiceUuidMask == null ? 0 : 1); + if (mServiceUuidMask != null) { + dest.writeParcelable(mServiceUuidMask, flags); + } + dest.writeInt(mServiceData == null ? 0 : mServiceData.length); + if (mServiceData != null) { + dest.writeByteArray(mServiceData); + } + dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length); + if (mServiceDataMask != null) { + dest.writeByteArray(mServiceDataMask); + } + dest.writeInt(mManufacturerId); + dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length); + if (mManufacturerData != null) { + dest.writeByteArray(mManufacturerData); + } + dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length); + if (mManufacturerDataMask != null) { + dest.writeByteArray(mManufacturerDataMask); + } + dest.writeInt(mMinRssi); + dest.writeInt(mMaxRssi); + } + + /** + * A {@link android.os.Parcelable.Creator} to create {@link BluetoothLeScanFilter} form parcel. + */ + public static final Creator<BluetoothLeScanFilter> + CREATOR = new Creator<BluetoothLeScanFilter>() { + + @Override + public BluetoothLeScanFilter[] newArray(int size) { + return new BluetoothLeScanFilter[size]; + } + + @Override + public BluetoothLeScanFilter createFromParcel(Parcel in) { + Builder builder = newBuilder(); + if (in.readInt() == 1) { + builder.name(in.readString()); + } + if (in.readInt() == 1) { + builder.macAddress(in.readString()); + } + if (in.readInt() == 1) { + ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); + builder.serviceUuid(uuid); + } + if (in.readInt() == 1) { + ParcelUuid uuidMask = in.readParcelable(ParcelUuid.class.getClassLoader()); + builder.serviceUuidMask(uuidMask); + } + int serviceDataLength = in.readInt(); + if (serviceDataLength > 0) { + byte[] serviceData = new byte[serviceDataLength]; + in.readByteArray(serviceData); + builder.serviceData(serviceData); + } + int serviceDataMaskLength = in.readInt(); + if (serviceDataMaskLength > 0) { + byte[] serviceDataMask = new byte[serviceDataMaskLength]; + in.readByteArray(serviceDataMask); + builder.serviceDataMask(serviceDataMask); + } + int manufacturerId = in.readInt(); + int manufacturerDataLength = in.readInt(); + if (manufacturerDataLength > 0) { + byte[] manufacturerData = new byte[manufacturerDataLength]; + in.readByteArray(manufacturerData); + builder.manufacturerData(manufacturerId, manufacturerData); + } + int manufacturerDataMaskLength = in.readInt(); + if (manufacturerDataMaskLength > 0) { + byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; + in.readByteArray(manufacturerDataMask); + builder.manufacturerDataMask(manufacturerDataMask); + } + int minRssi = in.readInt(); + int maxRssi = in.readInt(); + builder.rssiRange(minRssi, maxRssi); + return builder.build(); + } + }; + + /** + * Returns the filter set the local name field of Bluetooth advertisement data. + */ + @Nullable + public String getLocalName() { + return mLocalName; + } + + @Nullable /** + * Returns the filter set on the service uuid. + */ + public ParcelUuid getServiceUuid() { + return mServiceUuid; + } + + @Nullable + public ParcelUuid getServiceUuidMask() { + return mServiceUuidMask; + } + + @Nullable + public String getDeviceAddress() { + return mMacAddress; + } + + @Nullable + public byte[] getServiceData() { + return mServiceData; + } + + @Nullable + public byte[] getServiceDataMask() { + return mServiceDataMask; + } + + /** + * Returns the manufacturer id. -1 if the manufacturer filter is not set. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + @Nullable + public byte[] getManufacturerData() { + return mManufacturerData; + } + + @Nullable + public byte[] getManufacturerDataMask() { + return mManufacturerDataMask; + } + + /** + * Returns minimum value of rssi for the scan filter. {@link Integer#MIN_VALUE} if not set. + */ + public int getMinRssi() { + return mMinRssi; + } + + /** + * Returns maximum value of the rssi for the scan filter. {@link Integer#MAX_VALUE} if not set. + */ + public int getMaxRssi() { + return mMaxRssi; + } + + /** + * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match + * if it matches all the field filters. + */ + public boolean matches(ScanResult scanResult) { + if (scanResult == null) { + return false; + } + BluetoothDevice device = scanResult.getDevice(); + // Device match. + if (mMacAddress != null && (device == null || !mMacAddress.equals(device.getAddress()))) { + return false; + } + + int rssi = scanResult.getRssi(); + if (rssi < mMinRssi || rssi > mMaxRssi) { + return false; + } + + byte[] scanRecordBytes = scanResult.getScanRecord(); + ScanRecord scanRecord = ScanRecord.getParser().parseFromScanRecord(scanRecordBytes); + + // Scan record is null but there exist filters on it. + if (scanRecord == null + && (mLocalName != null || mServiceUuid != null || mManufacturerData != null + || mServiceData != null)) { + return false; + } + + // Local name match. + if (mLocalName != null && !mLocalName.equals(scanRecord.getLocalName())) { + return false; + } + + // UUID match. + if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, + scanRecord.getServiceUuids())) { + return false; + } + + // Service data match + if (mServiceData != null && + !matchesPartialData(mServiceData, mServiceDataMask, scanRecord.getServiceData())) { + return false; + } + + // Manufacturer data match. + if (mManufacturerData != null && !matchesPartialData(mManufacturerData, + mManufacturerDataMask, scanRecord.getManufacturerSpecificData())) { + return false; + } + // All filters match. + return true; + } + + // Check if the uuid pattern is contained in a list of parcel uuids. + private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, + List<ParcelUuid> uuids) { + if (uuid == null) { + return true; + } + if (uuids == null) { + return false; + } + + for (ParcelUuid parcelUuid : uuids) { + UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); + if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { + return true; + } + } + return false; + } + + // Check if the uuid pattern matches the particular service uuid. + private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { + if (mask == null) { + return uuid.equals(data); + } + if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) != + (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) { + return false; + } + return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) == + (data.getMostSignificantBits() & mask.getMostSignificantBits())); + } + + // Check whether the data pattern matches the parsed data. + private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { + if (dataMask == null) { + return Arrays.equals(data, parsedData); + } + if (parsedData == null) { + return false; + } + for (int i = 0; i < data.length; ++i) { + if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return "BluetoothLeScanFilter [mLocalName=" + mLocalName + ", mMacAddress=" + mMacAddress + + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask + ", mServiceData=" + + Arrays.toString(mServiceData) + ", mServiceDataMask=" + + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId + + ", mManufacturerData=" + Arrays.toString(mManufacturerData) + + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + + ", mMinRssi=" + mMinRssi + ", mMaxRssi=" + mMaxRssi + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mLocalName, mMacAddress, mManufacturerId, mManufacturerData, + mManufacturerDataMask, mMaxRssi, mMinRssi, mServiceData, mServiceDataMask, + mServiceUuid, mServiceUuidMask); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BluetoothLeScanFilter other = (BluetoothLeScanFilter) obj; + return Objects.equals(mLocalName, other.mLocalName) && + Objects.equals(mMacAddress, other.mMacAddress) && + mManufacturerId == other.mManufacturerId && + Objects.deepEquals(mManufacturerData, other.mManufacturerData) && + Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) && + mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi && + Objects.deepEquals(mServiceData, other.mServiceData) && + Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) && + Objects.equals(mServiceUuid, other.mServiceUuid) && + Objects.equals(mServiceUuidMask, other.mServiceUuidMask); + } + + /** + * Returns the {@link Builder} for {@link BluetoothLeScanFilter}. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder class for {@link BluetoothLeScanFilter}. Use + * {@link BluetoothLeScanFilter#newBuilder()} to get an instance of the {@link Builder}. + */ + public static class Builder { + + private String mLocalName; + private String mMacAddress; + + private ParcelUuid mServiceUuid; + private ParcelUuid mUuidMask; + + private byte[] mServiceData; + private byte[] mServiceDataMask; + + private int mManufacturerId = -1; + private byte[] mManufacturerData; + private byte[] mManufacturerDataMask; + + private int mMinRssi = Integer.MIN_VALUE; + private int mMaxRssi = Integer.MAX_VALUE; + + // Private constructor, use BluetoothLeScanFilter.newBuilder instead. + private Builder() { + } + + /** + * Set filtering on local name. + */ + public Builder name(String localName) { + mLocalName = localName; + return this; + } + + /** + * Set filtering on device mac address. + * + * @param macAddress The device mac address for the filter. It needs to be in the format of + * "01:02:03:AB:CD:EF". The mac address can be validated using + * {@link BluetoothAdapter#checkBluetoothAddress}. + * @throws IllegalArgumentException If the {@code macAddress} is invalid. + */ + public Builder macAddress(String macAddress) { + if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) { + throw new IllegalArgumentException("invalid mac address " + macAddress); + } + mMacAddress = macAddress; + return this; + } + + /** + * Set filtering on service uuid. + */ + public Builder serviceUuid(ParcelUuid serviceUuid) { + mServiceUuid = serviceUuid; + return this; + } + + /** + * Set partial uuid filter. The {@code uuidMask} is the bit mask for the {@code uuid} set + * through {@link #serviceUuid(ParcelUuid)} method. Set any bit in the mask to 1 to indicate + * a match is needed for the bit in {@code serviceUuid}, and 0 to ignore that bit. + * <p> + * The length of {@code uuidMask} must be the same as {@code serviceUuid}. + */ + public Builder serviceUuidMask(ParcelUuid uuidMask) { + mUuidMask = uuidMask; + return this; + } + + /** + * Set service data filter. + */ + public Builder serviceData(byte[] serviceData) { + mServiceData = serviceData; + return this; + } + + /** + * Set partial service data filter bit mask. For any bit in the mask, set it to 1 if it + * needs to match the one in service data, otherwise set it to 0 to ignore that bit. + * <p> + * The {@code serviceDataMask} must have the same length of the {@code serviceData} set + * through {@link #serviceData(byte[])}. + */ + public Builder serviceDataMask(byte[] serviceDataMask) { + mServiceDataMask = serviceDataMask; + return this; + } + + /** + * Set manufacturerId and manufacturerData. A negative manufacturerId is considered as + * invalid id. + * <p> + * Note the first two bytes of the {@code manufacturerData} is the manufacturerId. + */ + public Builder manufacturerData(int manufacturerId, byte[] manufacturerData) { + if (manufacturerData != null && manufacturerId < 0) { + throw new IllegalArgumentException("invalid manufacture id"); + } + mManufacturerId = manufacturerId; + mManufacturerData = manufacturerData; + return this; + } + + /** + * Set partial manufacture data filter bit mask. For any bit in the mask, set it the 1 if it + * needs to match the one in manufacturer data, otherwise set it to 0. + * <p> + * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData} + * set through {@link #manufacturerData(int, byte[])}. + */ + public Builder manufacturerDataMask(byte[] manufacturerDataMask) { + mManufacturerDataMask = manufacturerDataMask; + return this; + } + + /** + * Set the desired rssi range for the filter. A scan result with rssi in the range of + * [minRssi, maxRssi] will be consider as a match. + */ + public Builder rssiRange(int minRssi, int maxRssi) { + mMinRssi = minRssi; + mMaxRssi = maxRssi; + return this; + } + + /** + * Build {@link BluetoothLeScanFilter}. + * + * @throws IllegalArgumentException If the filter cannot be built. + */ + public BluetoothLeScanFilter build() { + if (mUuidMask != null && mServiceUuid == null) { + throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); + } + + if (mServiceDataMask != null) { + if (mServiceData == null) { + throw new IllegalArgumentException( + "serviceData is null while serviceDataMask is not null"); + } + // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two + // byte array need to be the same. + if (mServiceData.length != mServiceDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for service data and service data mask"); + } + } + + if (mManufacturerDataMask != null) { + if (mManufacturerData == null) { + throw new IllegalArgumentException( + "manufacturerData is null while manufacturerDataMask is not null"); + } + // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths + // of the two byte array need to be the same. + if (mManufacturerData.length != mManufacturerDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for manufacturerData and manufacturerDataMask"); + } + } + return new BluetoothLeScanFilter(mLocalName, mMacAddress, + mServiceUuid, mUuidMask, + mServiceData, mServiceDataMask, + mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi); + } + } +} diff --git a/core/java/android/bluetooth/BluetoothLeScanner.aidl b/core/java/android/bluetooth/BluetoothLeScanner.aidl new file mode 100644 index 0000000..8cecdd7 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeScanner.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothLeScanner.ScanResult; +parcelable BluetoothLeScanner.Settings; diff --git a/core/java/android/bluetooth/BluetoothLeScanner.java b/core/java/android/bluetooth/BluetoothLeScanner.java new file mode 100644 index 0000000..ed3188b --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeScanner.java @@ -0,0 +1,759 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.bluetooth; + +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * This class provides methods to perform scan related operations for Bluetooth LE devices. An + * application can scan for a particular type of BLE devices using {@link BluetoothLeScanFilter}. It + * can also request different types of callbacks for delivering the result. + * <p> + * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of + * {@link BluetoothLeScanner}. + * <p> + * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see BluetoothLeScanFilter + */ +public class BluetoothLeScanner { + + private static final String TAG = "BluetoothLeScanner"; + private static final boolean DBG = true; + + /** + * Settings for Bluetooth LE scan. + */ + public static final class Settings implements Parcelable { + /** + * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes + * the least power. + */ + public static final int SCAN_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE scan in balanced power mode. + */ + public static final int SCAN_MODE_BALANCED = 1; + /** + * Scan using highest duty cycle. It's recommended only using this mode when the application + * is running in foreground. + */ + public static final int SCAN_MODE_LOW_LATENCY = 2; + + /** + * Callback each time when a bluetooth advertisement is found. + */ + public static final int CALLBACK_TYPE_ON_UPDATE = 0; + /** + * Callback when a bluetooth advertisement is found for the first time. + */ + public static final int CALLBACK_TYPE_ON_FOUND = 1; + /** + * Callback when a bluetooth advertisement is found for the first time, then lost. + */ + public static final int CALLBACK_TYPE_ON_LOST = 2; + + /** + * Full scan result which contains device mac address, rssi, advertising and scan response + * and scan timestamp. + */ + public static final int SCAN_RESULT_TYPE_FULL = 0; + /** + * Truncated scan result which contains device mac address, rssi and scan timestamp. Note + * it's possible for an app to get more scan results that it asks if there are multiple apps + * using this type. TODO: decide whether we could unhide this setting. + * + * @hide + */ + public static final int SCAN_RESULT_TYPE_TRUNCATED = 1; + + // Bluetooth LE scan mode. + private int mScanMode; + + // Bluetooth LE scan callback type + private int mCallbackType; + + // Bluetooth LE scan result type + private int mScanResultType; + + // Time of delay for reporting the scan result + private long mReportDelayMicros; + + public int getScanMode() { + return mScanMode; + } + + public int getCallbackType() { + return mCallbackType; + } + + public int getScanResultType() { + return mScanResultType; + } + + /** + * Returns report delay timestamp based on the device clock. + */ + public long getReportDelayMicros() { + return mReportDelayMicros; + } + + /** + * Creates a new {@link Builder} to build {@link Settings} object. + */ + public static Builder newBuilder() { + return new Builder(); + } + + private Settings(int scanMode, int callbackType, int scanResultType, + long reportDelayMicros) { + mScanMode = scanMode; + mCallbackType = callbackType; + mScanResultType = scanResultType; + mReportDelayMicros = reportDelayMicros; + } + + private Settings(Parcel in) { + mScanMode = in.readInt(); + mCallbackType = in.readInt(); + mScanResultType = in.readInt(); + mReportDelayMicros = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mScanMode); + dest.writeInt(mCallbackType); + dest.writeInt(mScanResultType); + dest.writeLong(mReportDelayMicros); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<Settings> CREATOR = new Creator<Settings>() { + @Override + public Settings[] newArray(int size) { + return new Settings[size]; + } + + @Override + public Settings createFromParcel(Parcel in) { + return new Settings(in); + } + }; + + /** + * Builder for {@link BluetoothLeScanner.Settings}. + */ + public static class Builder { + private int mScanMode = SCAN_MODE_LOW_POWER; + private int mCallbackType = CALLBACK_TYPE_ON_UPDATE; + private int mScanResultType = SCAN_RESULT_TYPE_FULL; + private long mReportDelayMicros = 0; + + // Hidden constructor. + private Builder() { + } + + /** + * Set scan mode for Bluetooth LE scan. + * + * @param scanMode The scan mode can be one of {@link Settings#SCAN_MODE_LOW_POWER}, + * {@link Settings#SCAN_MODE_BALANCED} or + * {@link Settings#SCAN_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the {@code scanMode} is invalid. + */ + public Builder scanMode(int scanMode) { + if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("invalid scan mode " + scanMode); + } + mScanMode = scanMode; + return this; + } + + /** + * Set callback type for Bluetooth LE scan. + * + * @param callbackType The callback type for the scan. Can be either one of + * {@link Settings#CALLBACK_TYPE_ON_UPDATE}, + * {@link Settings#CALLBACK_TYPE_ON_FOUND} or + * {@link Settings#CALLBACK_TYPE_ON_LOST}. + * @throws IllegalArgumentException If the {@code callbackType} is invalid. + */ + public Builder callbackType(int callbackType) { + if (callbackType < CALLBACK_TYPE_ON_UPDATE + || callbackType > CALLBACK_TYPE_ON_LOST) { + throw new IllegalArgumentException("invalid callback type - " + callbackType); + } + mCallbackType = callbackType; + return this; + } + + /** + * Set scan result type for Bluetooth LE scan. + * + * @param scanResultType Type for scan result, could be either + * {@link Settings#SCAN_RESULT_TYPE_FULL} or + * {@link Settings#SCAN_RESULT_TYPE_TRUNCATED}. + * @throws IllegalArgumentException If the {@code scanResultType} is invalid. + * @hide + */ + public Builder scanResultType(int scanResultType) { + if (scanResultType < SCAN_RESULT_TYPE_FULL + || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) { + throw new IllegalArgumentException( + "invalid scanResultType - " + scanResultType); + } + mScanResultType = scanResultType; + return this; + } + + /** + * Set report delay timestamp for Bluetooth LE scan. + */ + public Builder reportDelayMicros(long reportDelayMicros) { + mReportDelayMicros = reportDelayMicros; + return this; + } + + /** + * Build {@link Settings}. + */ + public Settings build() { + return new Settings(mScanMode, mCallbackType, mScanResultType, mReportDelayMicros); + } + } + } + + /** + * ScanResult for Bluetooth LE scan. + */ + public static final class ScanResult implements Parcelable { + // Remote bluetooth device. + private BluetoothDevice mDevice; + + // Scan record, including advertising data and scan response data. + private byte[] mScanRecord; + + // Received signal strength. + private int mRssi; + + // Device timestamp when the result was last seen. + private long mTimestampMicros; + + // Constructor of scan result. + public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, long timestampMicros) { + mDevice = device; + mScanRecord = scanRecord; + mRssi = rssi; + mTimestampMicros = timestampMicros; + } + + private ScanResult(Parcel in) { + readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mDevice != null) { + dest.writeInt(1); + mDevice.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + if (mScanRecord != null) { + dest.writeInt(1); + dest.writeByteArray(mScanRecord); + } else { + dest.writeInt(0); + } + dest.writeInt(mRssi); + dest.writeLong(mTimestampMicros); + } + + private void readFromParcel(Parcel in) { + if (in.readInt() == 1) { + mDevice = BluetoothDevice.CREATOR.createFromParcel(in); + } + if (in.readInt() == 1) { + mScanRecord = in.createByteArray(); + } + mRssi = in.readInt(); + mTimestampMicros = in.readLong(); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the remote bluetooth device identified by the bluetooth device address. + */ + @Nullable + public BluetoothDevice getDevice() { + return mDevice; + } + + @Nullable /** + * Returns the scan record, which can be a combination of advertisement and scan response. + */ + public byte[] getScanRecord() { + return mScanRecord; + } + + /** + * Returns the received signal strength in dBm. The valid range is [-127, 127]. + */ + public int getRssi() { + return mRssi; + } + + /** + * Returns timestamp since boot when the scan record was observed. + */ + public long getTimestampMicros() { + return mTimestampMicros; + } + + @Override + public int hashCode() { + return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampMicros); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ScanResult other = (ScanResult) obj; + return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && + Objects.deepEquals(mScanRecord, other.mScanRecord) + && (mTimestampMicros == other.mTimestampMicros); + } + + @Override + public String toString() { + return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord=" + + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampMicros=" + + mTimestampMicros + '}'; + } + + public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { + @Override + public ScanResult createFromParcel(Parcel source) { + return new ScanResult(source); + } + + @Override + public ScanResult[] newArray(int size) { + return new ScanResult[size]; + } + }; + + } + + /** + * Callback of Bluetooth LE scans. The results of the scans will be delivered through the + * callbacks. + */ + public interface ScanCallback { + /** + * Callback when any BLE beacon is found. + * + * @param result A Bluetooth LE scan result. + */ + public void onDeviceUpdate(ScanResult result); + + /** + * Callback when the BLE beacon is found for the first time. + * + * @param result The Bluetooth LE scan result when the onFound event is triggered. + */ + public void onDeviceFound(ScanResult result); + + /** + * Callback when the BLE device was lost. Note a device has to be "found" before it's lost. + * + * @param device The Bluetooth device that is lost. + */ + public void onDeviceLost(BluetoothDevice device); + + /** + * Callback when batch results are delivered. + * + * @param results List of scan results that are previously scanned. + */ + public void onBatchScanResults(List<ScanResult> results); + + /** + * Fails to start scan as BLE scan with the same settings is already started by the app. + */ + public static final int SCAN_ALREADY_STARTED = 1; + /** + * Fails to start scan as app cannot be registered. + */ + public static final int APPLICATION_REGISTRATION_FAILED = 2; + /** + * Fails to start scan due to gatt service failure. + */ + public static final int GATT_SERVICE_FAILURE = 3; + /** + * Fails to start scan due to controller failure. + */ + public static final int CONTROLLER_FAILURE = 4; + + /** + * Callback when scan failed. + */ + public void onScanFailed(int errorCode); + } + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<Settings, BleScanCallbackWrapper> mLeScanClients; + + BluetoothLeScanner(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + mLeScanClients = new HashMap<Settings, BleScanCallbackWrapper>(); + } + + /** + * Bluetooth GATT interface callbacks + */ + private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; + + private final ScanCallback mScanCallback; + private final List<BluetoothLeScanFilter> mFilters; + private Settings mSettings; + private IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // > 0: registered and scan started + private int mLeHandle; + + public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, + List<BluetoothLeScanFilter> filters, Settings settings, ScanCallback scanCallback) { + mBluetoothGatt = bluetoothGatt; + mFilters = filters; + mSettings = settings; + mScanCallback = scanCallback; + mLeHandle = 0; + } + + public boolean scanStarted() { + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + } + return mLeHandle > 0; + } + + public void stopLeScan() { + synchronized (this) { + if (mLeHandle <= 0) { + Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); + return; + } + try { + mBluetoothGatt.stopScan(mLeHandle, false); + mBluetoothGatt.unregisterClient(mLeHandle); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop scan and unregister" + e); + } + mLeHandle = -1; + notifyAll(); + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + + " clientIf=" + clientIf); + + synchronized (this) { + if (mLeHandle == -1) { + if (DBG) + Log.d(TAG, "onClientRegistered LE scan canceled"); + } + + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le scan: " + e); + mLeHandle = -1; + } + } else { + // registration failed + mLeHandle = -1; + } + notifyAll(); + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + /** + * Callback reporting an LE scan result. + * + * @hide + */ + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + if (DBG) + Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi); + + // Check null in case the scan has been stopped + synchronized (this) { + if (mLeHandle <= 0) + return; + } + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + address); + long scanMicros = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos()); + ScanResult result = new ScanResult(device, advData, rssi, + scanMicros); + mScanCallback.onDeviceUpdate(result); + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + // no op + } + + @Override + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + /** + * Scan Bluetooth LE scan. The scan results will be delivered through {@code callback}. + * + * @param filters {@link BluetoothLeScanFilter}s for finding exact BLE devices. + * @param settings Settings for ble scan. + * @param callback Callback when scan results are delivered. + * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. + */ + public void startScan(List<BluetoothLeScanFilter> filters, Settings settings, + final ScanCallback callback) { + if (settings == null || callback == null) { + throw new IllegalArgumentException("settings or callback is null"); + } + synchronized (mLeScanClients) { + if (mLeScanClients.get(settings) != null) { + postCallbackError(callback, ScanCallback.SCAN_ALREADY_STARTED); + return; + } + BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters, + settings, callback); + try { + UUID uuid = UUID.randomUUID(); + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.scanStarted()) { + mLeScanClients.put(settings, wrapper); + } else { + postCallbackError(callback, ScanCallback.APPLICATION_REGISTRATION_FAILED); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "GATT service exception when starting scan", e); + postCallbackError(callback, ScanCallback.GATT_SERVICE_FAILURE); + } + } + } + + private void postCallbackError(final ScanCallback callback, final int errorCode) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onScanFailed(errorCode); + } + }); + } + + /** + * Stop Bluetooth LE scan. + * + * @param settings The same settings as used in {@link #startScan}, which is used to identify + * the BLE scan. + */ + public void stopScan(Settings settings) { + synchronized (mLeScanClients) { + BleScanCallbackWrapper wrapper = mLeScanClients.remove(settings); + if (wrapper == null) { + return; + } + wrapper.stopLeScan(); + } + } + + /** + * Returns available storage size for batch scan results. It's recommended not to use batch scan + * if available storage size is small (less than 1k bytes, for instance). + * + * @hide TODO: unhide when batching is supported in stack. + */ + public int getAvailableBatchStorageSizeBytes() { + throw new UnsupportedOperationException("not impelemented"); + } + + /** + * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results + * batched on bluetooth controller. + * + * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one + * used to start scan. + * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will + * get batch scan callback if the batch scan buffer is flushed. + * @return Batch Scan results. + * @hide TODO: unhide when batching is supported in stack. + */ + public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) { + throw new UnsupportedOperationException("not impelemented"); + } + +} diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index ab53fb0..1e22eb3 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -273,11 +273,29 @@ public final class BluetoothUuid { * @param parcelUuid * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. */ - public static boolean isShortUuid(ParcelUuid parcelUuid) { + public static boolean is16BitUuid(ParcelUuid parcelUuid) { UUID uuid = parcelUuid.getUuid(); if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { return false; } return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); } + + + /** + * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid. + * + * @param parcelUuid + * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise. + */ + public static boolean is32BitUuid(ParcelUuid parcelUuid) { + UUID uuid = parcelUuid.getUuid(); + if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { + return false; + } + if (is16BitUuid(parcelUuid)) { + return false; + } + return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L); + } } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 3dd7094..091b806 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -17,6 +17,10 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAdvertiseScanData; +import android.bluetooth.BluetoothLeAdvertiser; +import android.bluetooth.BluetoothLeScanFilter; +import android.bluetooth.BluetoothLeScanner; import android.os.ParcelUuid; import android.bluetooth.IBluetoothGattCallback; @@ -33,8 +37,13 @@ interface IBluetoothGatt { void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids); void startScanWithUuidsScanParam(in int appIf, in boolean isServer, in ParcelUuid[] ids, int scanWindow, int scanInterval); + void startScanWithFilters(in int appIf, in boolean isServer, + in BluetoothLeScanner.Settings settings, + in List<BluetoothLeScanFilter> filters); void stopScan(in int appIf, in boolean isServer); - + void startMultiAdvertising(in int appIf, in BluetoothLeAdvertiseScanData.AdvertisementData data, + in BluetoothLeAdvertiser.Settings settings); + void stopMultiAdvertising(in int appIf); void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback); void unregisterClient(in int clientIf); void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport); diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl index a78c29b..bf9e0a7 100644 --- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl @@ -64,5 +64,6 @@ interface IBluetoothGattCallback { in byte[] value); void onReadRemoteRssi(in String address, in int rssi, in int status); oneway void onAdvertiseStateChange(in int advertiseState, in int status); + oneway void onMultiAdvertiseCallback(in int status); void onConfigureMTU(in String address, in int mtu, in int status); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0e2eab7..35bcc02 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1405,6 +1405,14 @@ public abstract class PackageManager { public static final String FEATURE_MANAGEDPROFILES = "android.software.managedprofiles"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a full implementation of the android.webkit.* APIs. Devices + * lacking this feature will not have a functioning WebView implementation. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WEBVIEW = "android.software.webview"; + + /** * Action to external storage service to clean out removed apps. * @hide */ diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 5674154..3f01dd2 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -64,7 +64,6 @@ import java.util.Arrays; * List Resource</a>.</p> */ public class ColorStateList implements Parcelable { - private int[][] mStateSpecs; // must be parallel to mColors private int[] mColors; // must be parallel to mStateSpecs private int mDefaultColor = 0xffff0000; @@ -100,9 +99,9 @@ public class ColorStateList implements Parcelable { public static ColorStateList valueOf(int color) { // TODO: should we collect these eventually? synchronized (sCache) { - WeakReference<ColorStateList> ref = sCache.get(color); - ColorStateList csl = ref != null ? ref.get() : null; + final WeakReference<ColorStateList> ref = sCache.get(color); + ColorStateList csl = ref != null ? ref.get() : null; if (csl != null) { return csl; } @@ -118,8 +117,7 @@ public class ColorStateList implements Parcelable { */ public static ColorStateList createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { - - AttributeSet attrs = Xml.asAttributeSet(parser); + final AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.START_TAG @@ -133,22 +131,22 @@ public class ColorStateList implements Parcelable { return createFromXmlInner(r, parser, attrs); } - /* Create from inside an XML document. Called on a parser positioned at - * a tag in an XML document, tries to create a ColorStateList from that tag. - * Returns null if the tag is not a valid ColorStateList. + /** + * Create from inside an XML document. Called on a parser positioned at a + * tag in an XML document, tries to create a ColorStateList from that tag. + * + * @throws XmlPullParserException if the current tag is not <selector> + * @return A color state list for the current tag. */ private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { - - ColorStateList colorStateList; - + final ColorStateList colorStateList; final String name = parser.getName(); - if (name.equals("selector")) { colorStateList = new ColorStateList(); } else { throw new XmlPullParserException( - parser.getPositionDescription() + ": invalid drawable tag " + name); + parser.getPositionDescription() + ": invalid drawable tag " + name); } colorStateList.inflate(r, parser, attrs); @@ -161,9 +159,8 @@ public class ColorStateList implements Parcelable { * (0-255). */ public ColorStateList withAlpha(int alpha) { - int[] colors = new int[mColors.length]; - - int len = colors.length; + final int[] colors = new int[mColors.length]; + final int len = colors.length; for (int i = 0; i < len; i++) { colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24); } @@ -176,7 +173,6 @@ public class ColorStateList implements Parcelable { */ private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { - int type; final int innerDepth = parser.getDepth()+1; @@ -259,10 +255,25 @@ public class ColorStateList implements Parcelable { System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); } + /** + * Indicates whether this color state list contains more than one state spec + * and will change color based on state. + * + * @return True if this color state list changes color based on state, false + * otherwise. + * @see #getColorForState(int[], int) + */ public boolean isStateful() { return mStateSpecs.length > 1; } + /** + * Indicates whether this color state list is opaque, which means that every + * color returned from {@link #getColorForState(int[], int)} has an alpha + * value of 255. + * + * @return True if this color state list is opaque. + */ public boolean isOpaque() { final int n = mColors.length; for (int i = 0; i < n; i++) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 1692a79..a78f8e2 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -21,6 +21,7 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.graphics.Movie; import android.graphics.drawable.Drawable; @@ -719,12 +720,12 @@ public class Resources { * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. - * @param theme The theme used to style the drawable attributes. + * @param theme The theme used to style the drawable attributes, may be {@code null}. * @return Drawable An object that can be used to draw this resource. * @throws NotFoundException Throws NotFoundException if the given ID does * not exist. */ - public Drawable getDrawable(int id, Theme theme) throws NotFoundException { + public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -777,12 +778,12 @@ public class Resources { * The value 0 is an invalid identifier. * @param density The desired screen density indicated by the resource as * found in {@link DisplayMetrics}. - * @param theme The theme used to style the drawable attributes. + * @param theme The theme used to style the drawable attributes, may be {@code null}. * @return Drawable An object that can be used to draw this resource. * @throws NotFoundException Throws NotFoundException if the given ID does * not exist. */ - public Drawable getDrawableForDensity(int id, int density, Theme theme) { + public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java index 92d6f75..da5c128 100644 --- a/core/java/android/hardware/usb/UsbConfiguration.java +++ b/core/java/android/hardware/usb/UsbConfiguration.java @@ -44,13 +44,13 @@ public class UsbConfiguration implements Parcelable { * Mask for "self-powered" bit in the configuration's attributes. * @see #getAttributes */ - public static final int ATTR_SELF_POWERED_MASK = 1 << 6; + private static final int ATTR_SELF_POWERED = 1 << 6; /** * Mask for "remote wakeup" bit in the configuration's attributes. * @see #getAttributes */ - public static final int ATTR_REMOTE_WAKEUP_MASK = 1 << 5; + private static final int ATTR_REMOTE_WAKEUP = 1 << 5; /** * UsbConfiguration should only be instantiated by UsbService implementation @@ -83,19 +83,23 @@ public class UsbConfiguration implements Parcelable { } /** - * Returns the configuration's attributes field. - * This field contains a bit field with the following flags: + * Returns the self-powered attribute value configuration's attributes field. + * This attribute indicates that the device has a power source other than the USB connection. * - * Bit 7: always set to 1 - * Bit 6: self-powered - * Bit 5: remote wakeup enabled - * Bit 0-4: reserved - * @see #ATTR_SELF_POWERED_MASK - * @see #ATTR_REMOTE_WAKEUP_MASK - * @return the configuration's attributes + * @return the configuration's self-powered attribute */ - public int getAttributes() { - return mAttributes; + public boolean isSelfPowered() { + return (mAttributes & ATTR_SELF_POWERED) != 0; + } + + /** + * Returns the remote-wakeup attribute value configuration's attributes field. + * This attributes that the device may signal the host to wake from suspend. + * + * @return the configuration's remote-wakeup attribute + */ + public boolean isRemoteWakeup() { + return (mAttributes & ATTR_REMOTE_WAKEUP) != 0; } /** diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 6283951..c062b3a 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -104,7 +104,7 @@ public class UsbDeviceConnection { * Sets the current {@link android.hardware.usb.UsbInterface}. * Used to select between two interfaces with the same ID but different alternate setting. * - * @return true if the interface was successfully released + * @return true if the interface was successfully selected */ public boolean setInterface(UsbInterface intf) { return native_set_interface(intf.getId(), intf.getAlternateSetting()); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1837335..80a9598 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1655,9 +1655,16 @@ public class ConnectivityManager { } /** {@hide} */ - public void registerNetworkFactory(Messenger messenger) { + public void registerNetworkFactory(Messenger messenger, String name) { try { - mService.registerNetworkFactory(messenger); + mService.registerNetworkFactory(messenger, name); + } catch (RemoteException e) { } + } + + /** {@hide} */ + public void unregisterNetworkFactory(Messenger messenger) { + try { + mService.unregisterNetworkFactory(messenger); } catch (RemoteException e) { } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 885b8b6..d97b1e9 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -153,7 +153,9 @@ interface IConnectivityManager void setAirplaneMode(boolean enable); - void registerNetworkFactory(in Messenger messenger); + void registerNetworkFactory(in Messenger messenger, in String name); + + void unregisterNetworkFactory(in Messenger messenger); void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, int score); diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index ceedd98..7ea6bae 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -155,9 +155,6 @@ public class ProxyInfo implements Parcelable { mHost = source.getHost(); mPort = source.getPort(); mPacFileUrl = source.mPacFileUrl; - if (mPacFileUrl == null) { - mPacFileUrl = Uri.EMPTY; - } mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } else { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 312cdbe..ee219e3 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -266,6 +266,17 @@ public class UserManager { */ public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; + /** + * Key for user restrictions. Specifies that the user is not allowed to send or receive + * phone calls or text messages. Emergency calls may still be permitted. + * The default value is <code>false</code>. + * <p/> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_TELEPHONY = "no_telephony"; + /** @hide */ public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3; /** @hide */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e896063..2d03e1d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1088,6 +1088,9 @@ public final class Settings { MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_COUNT); MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_DELAY_MS); MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS); + + // At one time in System, then Global, but now back in Secure + MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS); } private static final HashSet<String> MOVED_TO_GLOBAL; @@ -1102,7 +1105,6 @@ public final class Settings { MOVED_TO_SECURE_THEN_GLOBAL.add(Global.BLUETOOTH_ON); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DATA_ROAMING); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DEVICE_PROVISIONED); - MOVED_TO_SECURE_THEN_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.HTTP_PROXY); @@ -2573,10 +2575,10 @@ public final class Settings { public static final String HTTP_PROXY = Global.HTTP_PROXY; /** - * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead + * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead */ @Deprecated - public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS; + public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS; /** * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED} @@ -2814,7 +2816,6 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_SIZE_FORCED); MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); - MOVED_TO_GLOBAL.add(Settings.Global.INSTALL_NON_MARKET_APPS); MOVED_TO_GLOBAL.add(Settings.Global.MOBILE_DATA); MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_BUCKET_DURATION); MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_DELETE_AGE); @@ -3404,10 +3405,13 @@ public final class Settings { public static final String HTTP_PROXY = Global.HTTP_PROXY; /** - * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead + * Whether applications can be installed for this user via the system's + * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism. + * + * <p>1 = permit app installation via the system package installer intent + * <p>0 = do not allow use of the package installer */ - @Deprecated - public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS; + public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; /** * Comma-separated list of location providers that activities may access. Do not rely on @@ -5066,13 +5070,10 @@ public final class Settings { "download_manager_recommended_max_bytes_over_mobile"; /** - * Whether the package installer should allow installation of apps downloaded from - * sources other than Google Play. - * - * 1 = allow installing from other sources - * 0 = only allow installing from Google Play + * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead */ - public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; + @Deprecated + public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS; /** * Whether mobile data connections are allowed by the user. See @@ -6238,6 +6239,13 @@ public final class Settings { CALL_METHOD_GET_GLOBAL, CALL_METHOD_PUT_GLOBAL); + // Certain settings have been moved from global to the per-user secure namespace + private static final HashSet<String> MOVED_TO_SECURE; + static { + MOVED_TO_SECURE = new HashSet<String>(1); + MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS); + } + /** * Look up a name in the database. * @param resolver to access the database with @@ -6251,6 +6259,11 @@ public final class Settings { /** @hide */ public static String getStringForUser(ContentResolver resolver, String name, int userHandle) { + if (MOVED_TO_SECURE.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + + " to android.provider.Settings.Secure, returning read-only value."); + return Secure.getStringForUser(resolver, name, userHandle); + } return sNameValueCache.getStringForUser(resolver, name, userHandle); } @@ -6273,6 +6286,12 @@ public final class Settings { Log.v(TAG, "Global.putString(name=" + name + ", value=" + value + " for " + userHandle); } + // Global and Secure have the same access policy so we can forward writes + if (MOVED_TO_SECURE.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + + " to android.provider.Settings.Secure, value is unchanged."); + return Secure.putStringForUser(resolver, name, value, userHandle); + } return sNameValueCache.putStringForUser(resolver, name, value, userHandle); } diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index d4919eb..b3705d8 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -17,15 +17,15 @@ package android.service.notification; import android.service.notification.StatusBarNotification; -import android.service.notification.NotificationOrderUpdate; +import android.service.notification.NotificationRankingUpdate; /** @hide */ oneway interface INotificationListener { - void onListenerConnected(in NotificationOrderUpdate update); + void onListenerConnected(in NotificationRankingUpdate update); void onNotificationPosted(in StatusBarNotification notification, - in NotificationOrderUpdate update); + in NotificationRankingUpdate update); void onNotificationRemoved(in StatusBarNotification notification, - in NotificationOrderUpdate update); - void onNotificationOrderUpdate(in NotificationOrderUpdate update); + in NotificationRankingUpdate update); + void onNotificationRankingUpdate(in NotificationRankingUpdate update); }
\ No newline at end of file diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index e2e9ff4..7f84877 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -24,12 +24,15 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; /** - * A service that receives calls from the system when new notifications are posted or removed. + * A service that receives calls from the system when new notifications are + * posted or removed, or their ranking changed. * <p>To extend this class, you must declare the service in your manifest file with * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> @@ -48,7 +51,7 @@ public abstract class NotificationListenerService extends Service { + "[" + getClass().getSimpleName() + "]"; private INotificationListenerWrapper mWrapper = null; - private String[] mNotificationKeys; + private Ranking mRanking; private INotificationManager mNoMan; @@ -102,11 +105,11 @@ public abstract class NotificationListenerService extends Service { } /** - * Implement this method to be notified when the notification order cahnges. - * - * Call {@link #getOrderedNotificationKeys()} to retrieve the new order. + * Implement this method to be notified when the notification ranking changes. + * <P> + * Call {@link #getCurrentRanking()} to retrieve the new ranking. */ - public void onNotificationOrderUpdate() { + public void onNotificationRankingUpdate() { // optional } @@ -224,6 +227,19 @@ public abstract class NotificationListenerService extends Service { } /** + * Request the list of notification keys in their current ranking order. + * <p> + * You can use the notification keys for subsequent retrieval via + * {@link #getActiveNotifications(String[]) or dismissal via + * {@link #cancelNotifications(String[]). + * + * @return An array of active notification keys, in their ranking order. + */ + public String[] getActiveNotificationKeys() { + return mRanking.getOrderedKeys(); + } + + /** * Request the list of outstanding notifications (that is, those that are visible to the * current user). Useful when you don't know what's already been posted. * @@ -242,15 +258,20 @@ public abstract class NotificationListenerService extends Service { } /** - * Request the list of notification keys in their current natural order. - * You can use the notification keys for subsequent retrieval via - * {@link #getActiveNotifications(String[]) or dismissal via - * {@link #cancelNotifications(String[]). + * Returns current ranking information. + * + * <p> + * The returned object represents the current ranking snapshot and only + * applies for currently active notifications. Hence you must retrieve a + * new Ranking after each notification event such as + * {@link #onNotificationPosted(StatusBarNotification)}, + * {@link #onNotificationRemoved(StatusBarNotification)}, etc. * - * @return An array of active notification keys, in their natural order. + * @return A {@link NotificationListenerService.Ranking} object providing + * access to ranking information */ - public String[] getOrderedNotificationKeys() { - return mNotificationKeys; + public Ranking getCurrentRanking() { + return mRanking; } @Override @@ -308,59 +329,163 @@ public abstract class NotificationListenerService extends Service { private class INotificationListenerWrapper extends INotificationListener.Stub { @Override public void onNotificationPosted(StatusBarNotification sbn, - NotificationOrderUpdate update) { - try { - // protect subclass from concurrent modifications of (@link mNotificationKeys}. - synchronized (mWrapper) { - updateNotificationKeys(update); + NotificationRankingUpdate update) { + // protect subclass from concurrent modifications of (@link mNotificationKeys}. + synchronized (mWrapper) { + applyUpdate(update); + try { NotificationListenerService.this.onNotificationPosted(sbn); + } catch (Throwable t) { + Log.w(TAG, "Error running onNotificationPosted", t); } - } catch (Throwable t) { - Log.w(TAG, "Error running onOrderedNotificationPosted", t); } } @Override public void onNotificationRemoved(StatusBarNotification sbn, - NotificationOrderUpdate update) { - try { - // protect subclass from concurrent modifications of (@link mNotificationKeys}. - synchronized (mWrapper) { - updateNotificationKeys(update); + NotificationRankingUpdate update) { + // protect subclass from concurrent modifications of (@link mNotificationKeys}. + synchronized (mWrapper) { + applyUpdate(update); + try { NotificationListenerService.this.onNotificationRemoved(sbn); + } catch (Throwable t) { + Log.w(TAG, "Error running onNotificationRemoved", t); } - } catch (Throwable t) { - Log.w(TAG, "Error running onNotificationRemoved", t); } } @Override - public void onListenerConnected(NotificationOrderUpdate update) { - try { - // protect subclass from concurrent modifications of (@link mNotificationKeys}. - synchronized (mWrapper) { - updateNotificationKeys(update); - NotificationListenerService.this.onListenerConnected(mNotificationKeys); + public void onListenerConnected(NotificationRankingUpdate update) { + // protect subclass from concurrent modifications of (@link mNotificationKeys}. + synchronized (mWrapper) { + applyUpdate(update); + try { + NotificationListenerService.this.onListenerConnected( + mRanking.getOrderedKeys()); + } catch (Throwable t) { + Log.w(TAG, "Error running onListenerConnected", t); } - } catch (Throwable t) { - Log.w(TAG, "Error running onListenerConnected", t); } } @Override - public void onNotificationOrderUpdate(NotificationOrderUpdate update) + public void onNotificationRankingUpdate(NotificationRankingUpdate update) throws RemoteException { - try { - // protect subclass from concurrent modifications of (@link mNotificationKeys}. - synchronized (mWrapper) { - updateNotificationKeys(update); - NotificationListenerService.this.onNotificationOrderUpdate(); + // protect subclass from concurrent modifications of (@link mNotificationKeys}. + synchronized (mWrapper) { + applyUpdate(update); + try { + NotificationListenerService.this.onNotificationRankingUpdate(); + } catch (Throwable t) { + Log.w(TAG, "Error running onNotificationRankingUpdate", t); } - } catch (Throwable t) { - Log.w(TAG, "Error running onNotificationOrderUpdate", t); } } } - private void updateNotificationKeys(NotificationOrderUpdate update) { - // TODO: avoid garbage by comparing the lists - mNotificationKeys = update.getOrderedKeys(); + private void applyUpdate(NotificationRankingUpdate update) { + mRanking = new Ranking(update); + } + + /** + * Provides access to ranking information on currently active + * notifications. + * + * <p> + * Note that this object represents a ranking snapshot that only applies to + * notifications active at the time of retrieval. + */ + public static class Ranking implements Parcelable { + private final NotificationRankingUpdate mRankingUpdate; + + private Ranking(NotificationRankingUpdate rankingUpdate) { + mRankingUpdate = rankingUpdate; + } + + /** + * Request the list of notification keys in their current ranking + * order. + * + * @return An array of active notification keys, in their ranking order. + */ + public String[] getOrderedKeys() { + return mRankingUpdate.getOrderedKeys(); + } + + /** + * Returns the rank of the notification with the given key, that is the + * index of <code>key</code> in the array of keys returned by + * {@link #getOrderedKeys()}. + * + * @return The rank of the notification with the given key; -1 when the + * given key is unknown. + */ + public int getIndexOfKey(String key) { + // TODO: Optimize. + String[] orderedKeys = mRankingUpdate.getOrderedKeys(); + for (int i = 0; i < orderedKeys.length; i++) { + if (orderedKeys[i].equals(key)) { + return i; + } + } + return -1; + } + + /** + * Returns whether the notification with the given key was intercepted + * by "Do not disturb". + */ + public boolean isInterceptedByDoNotDisturb(String key) { + // TODO: Optimize. + for (String interceptedKey : mRankingUpdate.getDndInterceptedKeys()) { + if (interceptedKey.equals(key)) { + return true; + } + } + return false; + } + + /** + * Returns whether the notification with the given key is an ambient + * notification, that is a notification that doesn't require the user's + * immediate attention. + */ + public boolean isAmbient(String key) { + // TODO: Optimize. + int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); + if (firstAmbientIndex < 0) { + return false; + } + String[] orderedKeys = mRankingUpdate.getOrderedKeys(); + for (int i = firstAmbientIndex; i < orderedKeys.length; i++) { + if (orderedKeys[i].equals(key)) { + return true; + } + } + return false; + } + + // ----------- Parcelable + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mRankingUpdate, flags); + } + + public static final Creator<Ranking> CREATOR = new Creator<Ranking>() { + @Override + public Ranking createFromParcel(Parcel source) { + NotificationRankingUpdate rankingUpdate = source.readParcelable(null); + return new Ranking(rankingUpdate); + } + + @Override + public Ranking[] newArray(int size) { + return new Ranking[size]; + } + }; } } diff --git a/core/java/android/service/notification/NotificationOrderUpdate.java b/core/java/android/service/notification/NotificationOrderUpdate.java deleted file mode 100644 index 20e19a3..0000000 --- a/core/java/android/service/notification/NotificationOrderUpdate.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.service.notification; - -import android.os.Parcel; -import android.os.Parcelable; - -public class NotificationOrderUpdate implements Parcelable { - // TODO replace this with an update instead of the whole array - private final String[] mKeys; - - /** @hide */ - public NotificationOrderUpdate(String[] keys) { - this.mKeys = keys; - } - - public NotificationOrderUpdate(Parcel in) { - this.mKeys = in.readStringArray(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeStringArray(this.mKeys); - } - - public static final Parcelable.Creator<NotificationOrderUpdate> CREATOR - = new Parcelable.Creator<NotificationOrderUpdate>() { - public NotificationOrderUpdate createFromParcel(Parcel parcel) { - return new NotificationOrderUpdate(parcel); - } - - public NotificationOrderUpdate[] newArray(int size) { - return new NotificationOrderUpdate[size]; - } - }; - - /** - * @hide - * @return ordered list of keys - */ - String[] getOrderedKeys() { - return mKeys; - } -} diff --git a/core/java/android/service/notification/NotificationOrderUpdate.aidl b/core/java/android/service/notification/NotificationRankingUpdate.aidl index 5d50641..1393cb9 100644 --- a/core/java/android/service/notification/NotificationOrderUpdate.aidl +++ b/core/java/android/service/notification/NotificationRankingUpdate.aidl @@ -16,4 +16,4 @@ package android.service.notification; -parcelable NotificationOrderUpdate; +parcelable NotificationRankingUpdate; diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java new file mode 100644 index 0000000..4b13d95 --- /dev/null +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.notification; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class NotificationRankingUpdate implements Parcelable { + // TODO: Support incremental updates. + private final String[] mKeys; + private final String[] mDndInterceptedKeys; + private final int mFirstAmbientIndex; + + public NotificationRankingUpdate(String[] keys, String[] dndInterceptedKeys, + int firstAmbientIndex) { + mKeys = keys; + mFirstAmbientIndex = firstAmbientIndex; + mDndInterceptedKeys = dndInterceptedKeys; + } + + public NotificationRankingUpdate(Parcel in) { + mKeys = in.readStringArray(); + mFirstAmbientIndex = in.readInt(); + mDndInterceptedKeys = in.readStringArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStringArray(mKeys); + out.writeInt(mFirstAmbientIndex); + out.writeStringArray(mDndInterceptedKeys); + } + + public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR + = new Parcelable.Creator<NotificationRankingUpdate>() { + public NotificationRankingUpdate createFromParcel(Parcel parcel) { + return new NotificationRankingUpdate(parcel); + } + + public NotificationRankingUpdate[] newArray(int size) { + return new NotificationRankingUpdate[size]; + } + }; + + public String[] getOrderedKeys() { + return mKeys; + } + + public int getFirstAmbientIndex() { + return mFirstAmbientIndex; + } + + public String[] getDndInterceptedKeys() { + return mDndInterceptedKeys; + } +} diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java index 0bd46bc..b17f502 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -115,7 +115,7 @@ public class QwertyKeyListener extends BaseKeyListener { if (count > 0 && selStart == selEnd && selStart > 0) { char c = content.charAt(selStart - 1); - if (c == i || c == Character.toUpperCase(i) && view != null) { + if ((c == i || c == Character.toUpperCase(i)) && view != null) { if (showCharacterPicker(view, content, c, false, count)) { resetMetaState(content); return true; diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java index 7721575..59b6386 100644 --- a/core/java/android/tv/TvView.java +++ b/core/java/android/tv/TvView.java @@ -149,6 +149,7 @@ public class TvView extends SurfaceView { if (mSession != null) { release(); } + mSessionCallback = null; } /** diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index ae5f37e..358ae8a 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -234,6 +234,14 @@ public final class InputDevice implements Parcelable { public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK; /** + * The input source is a device connected through HDMI-based bus. + * + * The key comes in through HDMI-CEC or MHL signal line, and is treated as if it were + * generated by a locally connected DPAD or keyboard. + */ + public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON; + + /** * A special input source constant that is used when filtering input devices * to match devices that provide any type of input source. */ diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index b8e1b89..8a996d2 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -644,14 +644,26 @@ public class KeyEvent extends InputEvent implements Parcelable { * devices or game controllers, especially if no other input mode is * available. */ public static final int KEYCODE_PAIRING = 225; - - private static final int LAST_KEYCODE = KEYCODE_PAIRING; + /** Key code constant: Media Top Menu key. + * Goes to the top of media menu. */ + public static final int KEYCODE_MEDIA_TOP_MENU = 226; + /** Key code constant: '11' key. */ + public static final int KEYCODE_11 = 227; + /** Key code constant: '12' key. */ + public static final int KEYCODE_12 = 228; + /** Key code constant: Last Channel key. + * Goes to the last viewed channel. */ + public static final int KEYCODE_LAST_CHANNEL = 229; + /** Key code constant: TV data service key. + * Displays data services like weather, sports. */ + public static final int KEYCODE_TV_DATA_SERVICE = 230; + + private static final int LAST_KEYCODE = KEYCODE_TV_DATA_SERVICE; // NOTE: If you add a new keycode here you must also add it to: // isSystem() // frameworks/native/include/android/keycodes.h - // frameworks/base/include/androidfw/InputEventAttributes.h - // external/webkit/WebKit/android/plugins/ANPKeyCodes.h + // frameworks/native/include/input/InputEventLabels.h // frameworks/base/core/res/res/values/attrs.xml // emulator? // LAST_KEYCODE diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index c1a4fee..e918119 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -48,6 +48,8 @@ public final class RenderNodeAnimator extends Animator { public static final int Y = 9; public static final int Z = 10; public static final int ALPHA = 11; + // The last value in the enum, used for array size initialization + public static final int LAST_VALUE = ALPHA; // Keep in sync with enum PaintFields in Animator.h public static final int PAINT_STROKE_WIDTH = 0; @@ -86,7 +88,7 @@ public final class RenderNodeAnimator extends Animator { private boolean mStarted = false; private boolean mFinished = false; - public int mapViewPropertyToRenderProperty(int viewProperty) { + public static int mapViewPropertyToRenderProperty(int viewProperty) { return sViewPropertyAnimatorMap.get(viewProperty); } @@ -125,11 +127,15 @@ public final class RenderNodeAnimator extends Animator { } } + static boolean isNativeInterpolator(TimeInterpolator interpolator) { + return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class); + } + private void applyInterpolator() { if (mInterpolator == null) return; long ni; - if (mInterpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class)) { + if (isNativeInterpolator(mInterpolator)) { ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator(); } else { long duration = nGetDuration(mNativePtr.get()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index fb7d57d..6dc7286 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4789,25 +4789,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param v previous or the next focus holder, or null if none */ private void manageFocusHotspot(boolean focused, View v) { - if (mBackground != null && mBackground.supportsHotspots()) { - final Rect r = new Rect(); - if (!focused && v != null) { - v.getBoundsOnScreen(r); - final int[] location = new int[2]; - getLocationOnScreen(location); - r.offset(-location[0], -location[1]); - } else { - r.set(0, 0, mRight - mLeft, mBottom - mTop); - } - - final float x = r.exactCenterX(); - final float y = r.exactCenterY(); - mBackground.setHotspot(R.attr.state_focused, x, y); + if (mBackground == null) { + return; + } - if (!focused) { - mBackground.removeHotspot(R.attr.state_focused); - } + final Rect r = new Rect(); + if (!focused && v != null) { + v.getBoundsOnScreen(r); + final int[] location = new int[2]; + getLocationOnScreen(location); + r.offset(-location[0], -location[1]); + } else { + r.set(0, 0, mRight - mLeft, mBottom - mTop); } + + final float x = r.exactCenterX(); + final float y = r.exactCenterY(); + mBackground.setHotspot(x, y); } /** @@ -6763,7 +6761,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private void setPressed(boolean pressed, float x, float y) { if (pressed) { - setHotspot(R.attr.state_pressed, x, y); + setHotspot(x, y); } setPressed(pressed); @@ -6787,10 +6785,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_PRESSED; } - if (!pressed) { - clearHotspot(R.attr.state_pressed); - } - if (needsRefresh) { refreshDrawableState(); } @@ -9106,21 +9100,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away - setHotspot(R.attr.state_pressed, x, y); + setHotspot(x, y); setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: - clearHotspot(R.attr.state_pressed); setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: - setHotspot(R.attr.state_pressed, x, y); + setHotspot(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { @@ -9142,17 +9135,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } - private void setHotspot(int id, float x, float y) { - final Drawable bg = mBackground; - if (bg != null && bg.supportsHotspots()) { - bg.setHotspot(id, x, y); - } - } - - private void clearHotspot(int id) { - final Drawable bg = mBackground; - if (bg != null && bg.supportsHotspots()) { - bg.removeHotspot(id); + private void setHotspot(float x, float y) { + if (mBackground != null) { + mBackground.setHotspot(x, y); } } @@ -12903,10 +12888,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; - if (mBackground != null) { - mBackground.clearHotspots(); - } - removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 11d2622..3104862 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -19,6 +19,7 @@ package android.view; import android.animation.Animator; import android.animation.ValueAnimator; import android.animation.TimeInterpolator; +import android.os.Build; import java.util.ArrayList; import java.util.HashMap; @@ -109,6 +110,11 @@ public class ViewPropertyAnimator { private ValueAnimator mTempValueAnimator; /** + * A RenderThread-driven backend that may intercept startAnimation + */ + private ViewPropertyAnimatorRT mRTBackend; + + /** * This listener is the mechanism by which the underlying Animator causes changes to the * properties currently being animated, as well as the cleanup after an animation is * complete. @@ -227,7 +233,7 @@ public class ViewPropertyAnimator { * values are used to calculate the animated value for a given animation fraction * during the animation. */ - private static class NameValuesHolder { + static class NameValuesHolder { int mNameConstant; float mFromValue; float mDeltaValue; @@ -247,6 +253,9 @@ public class ViewPropertyAnimator { ViewPropertyAnimator(View view) { mView = view; view.ensureTransformationInfo(); + if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { + mRTBackend = new ViewPropertyAnimatorRT(view); + } } /** @@ -371,6 +380,10 @@ public class ViewPropertyAnimator { return this; } + Animator.AnimatorListener getListener() { + return mListener; + } + /** * Sets a listener for update events in the underlying ValueAnimator that runs * the property animations. Note that the underlying animator is animating between @@ -390,6 +403,10 @@ public class ViewPropertyAnimator { return this; } + ValueAnimator.AnimatorUpdateListener getUpdateListener() { + return mUpdateListener; + } + /** * Starts the currently pending property animations immediately. Calling <code>start()</code> * is optional because all animations start automatically at the next opportunity. However, @@ -825,12 +842,22 @@ public class ViewPropertyAnimator { return this; } + boolean hasActions() { + return mPendingSetupAction != null + || mPendingCleanupAction != null + || mPendingOnStartAction != null + || mPendingOnEndAction != null; + } + /** * Starts the underlying Animator for a set of properties. We use a single animator that * simply runs from 0 to 1, and then use that fractional value to set each property * value accordingly. */ private void startAnimation() { + if (mRTBackend != null && mRTBackend.startAnimation(this)) { + return; + } mView.setHasTransientState(true); ValueAnimator animator = ValueAnimator.ofFloat(1.0f); ArrayList<NameValuesHolder> nameValueList = diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java new file mode 100644 index 0000000..709efdb --- /dev/null +++ b/core/java/android/view/ViewPropertyAnimatorRT.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.animation.TimeInterpolator; +import android.view.ViewPropertyAnimator.NameValuesHolder; + +import com.android.internal.view.animation.FallbackLUTInterpolator; + +import java.util.ArrayList; + + +/** + * This is a RenderThread driven backend for ViewPropertyAnimator. + */ +class ViewPropertyAnimatorRT { + + private final View mView; + + private RenderNodeAnimator mAnimators[] = new RenderNodeAnimator[RenderNodeAnimator.LAST_VALUE + 1]; + + ViewPropertyAnimatorRT(View view) { + mView = view; + } + + /** + * @return true if ViewPropertyAnimatorRT handled the animation, + * false if ViewPropertyAnimator needs to handle it + */ + public boolean startAnimation(ViewPropertyAnimator parent) { + cancelAnimators(parent.mPendingAnimations); + if (!canHandleAnimator(parent)) { + return false; + } + doStartAnimation(parent); + return true; + } + + private void doStartAnimation(ViewPropertyAnimator parent) { + int size = parent.mPendingAnimations.size(); + + long startDelay = parent.getStartDelay(); + long duration = parent.getDuration(); + TimeInterpolator interpolator = parent.getInterpolator(); + if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) { + interpolator = new FallbackLUTInterpolator(interpolator, duration); + } + for (int i = 0; i < size; i++) { + NameValuesHolder holder = parent.mPendingAnimations.get(i); + int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant); + + RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue); + animator.setStartDelay(startDelay); + animator.setDuration(duration); + animator.setInterpolator(interpolator); + animator.setTarget(mView); + animator.start(); + } + + parent.mPendingAnimations.clear(); + } + + private boolean canHandleAnimator(ViewPropertyAnimator parent) { + // TODO: Can we eliminate this entirely? + // If RenderNode.animatorProperties() can be toggled to point at staging + // instead then RNA can be used as the animators for software as well + // as the updateListener fallback paths. If this can be toggled + // at the top level somehow, combined with requiresUiRedraw, we could + // ensure that RT does not self-animate, allowing for safe driving of + // the animators from the UI thread using the same mechanisms + // ViewPropertyAnimator does, just with everything sitting on a single + // animator subsystem instead of multiple. + + if (parent.getUpdateListener() != null) { + return false; + } + if (parent.getListener() != null) { + // TODO support + return false; + } + if (!mView.isHardwareAccelerated()) { + // TODO handle this maybe? + return false; + } + if (parent.hasActions()) { + return false; + } + // Here goes nothing... + return true; + } + + private void cancelAnimators(ArrayList<NameValuesHolder> mPendingAnimations) { + int size = mPendingAnimations.size(); + for (int i = 0; i < size; i++) { + NameValuesHolder holder = mPendingAnimations.get(i); + int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant); + if (mAnimators[property] != null) { + mAnimators[property].cancel(); + mAnimators[property] = null; + } + } + } + +} diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index f91ef1a..c9eb130 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2502,22 +2502,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te positionSelector(position, sel); final Drawable selector = mSelector; - if (selector != null && selector.supportsHotspots() && position != INVALID_POSITION) { + if (selector != null && position != INVALID_POSITION) { final Rect bounds = mSelectorRect; final float x = bounds.exactCenterX(); final float y = bounds.exactCenterY(); - selector.setHotspot(R.attr.state_focused, x, y); + selector.setHotspot(x, y); } } void positionSelector(int position, View sel) { if (position != INVALID_POSITION) { - if (mSelectorPosition != position) { - final Drawable selector = mSelector; - if (selector != null && selector.supportsHotspots()) { - selector.clearHotspots(); - } - } mSelectorPosition = position; } @@ -3245,9 +3239,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ((TransitionDrawable) d).resetTransition(); } } - if (d.supportsHotspots()) { - d.setHotspot(R.attr.state_pressed, x, y); - } + d.setHotspot(x, y); } if (longClickable) { @@ -3783,9 +3775,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } - if (mSelector.supportsHotspots()) { - mSelector.setHotspot(R.attr.state_pressed, x, ev.getY()); - } + mSelector.setHotspot(x, ev.getY()); } if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); @@ -3797,9 +3787,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); - if (mSelector != null && mSelector.supportsHotspots()) { - mSelector.removeHotspot(R.attr.state_pressed); - } if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { performClick.run(); } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 4f2d9c6..1152e17 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -31,8 +31,6 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import com.android.internal.R; - public abstract class AbsSeekBar extends ProgressBar { private final Rect mTempRect = new Rect(); @@ -348,7 +346,7 @@ public abstract class AbsSeekBar extends ProgressBar { final int right = left + thumbWidth; final Drawable background = getBackground(); - if (background != null && background.supportsHotspots()) { + if (background != null) { final Rect bounds = mThumb.getBounds(); final int offsetX = mPaddingLeft - mThumbOffset; final int offsetY = mPaddingTop; @@ -499,17 +497,10 @@ public abstract class AbsSeekBar extends ProgressBar { return true; } - private void setHotspot(int id, float x, float y) { - final Drawable bg = getBackground(); - if (bg != null && bg.supportsHotspots()) { - bg.setHotspot(id, x, y); - } - } - - private void clearHotspot(int id) { + private void setHotspot(float x, float y) { final Drawable bg = getBackground(); - if (bg != null && bg.supportsHotspots()) { - bg.removeHotspot(id); + if (bg != null) { + bg.setHotspot(x, y); } } @@ -541,7 +532,7 @@ public abstract class AbsSeekBar extends ProgressBar { final int max = getMax(); progress += scale * max; - setHotspot(R.attr.state_pressed, x, (int) event.getY()); + setHotspot(x, (int) event.getY()); setProgress((int) progress, true); } @@ -567,7 +558,6 @@ public abstract class AbsSeekBar extends ProgressBar { * canceled. */ void onStopTrackingTouch() { - clearHotspot(R.attr.state_pressed); mIsDragging = false; } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 9e17cca..6aff4f4 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -285,7 +285,7 @@ public abstract class CompoundButton extends Button implements Checkable { buttonDrawable.setBounds(left, top, right, bottom); final Drawable background = getBackground(); - if (background != null && background.supportsHotspots()) { + if (background != null) { background.setHotspotBounds(left, top, right, bottom); } } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 74a3eec..ad1a023 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -828,7 +828,7 @@ public class Switch extends CompoundButton { thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); final Drawable background = getBackground(); - if (background != null && background.supportsHotspots()) { + if (background != null) { background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8f073de..a4a9680 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8194,7 +8194,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod(); info.setPassword(isPassword); - if (!isPassword) { + if (!isPassword || shouldSpeakPasswordsForAccessibility()) { info.setText(getTextForAccessibility()); } diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java index 1feb943..06838c9 100644 --- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java +++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java @@ -24,10 +24,13 @@ import android.view.Choreographer; * Interpolator that builds a lookup table to use. This is a fallback for * building a native interpolator from a TimeInterpolator that is not marked * with {@link HasNativeInterpolator} + * + * This implements TimeInterpolator to allow for easier interop with Animators */ @HasNativeInterpolator -public class FallbackLUTInterpolator implements NativeInterpolatorFactory { +public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator { + private TimeInterpolator mSourceInterpolator; private final float mLut[]; /** @@ -35,6 +38,7 @@ public class FallbackLUTInterpolator implements NativeInterpolatorFactory { * interpolator creation */ public FallbackLUTInterpolator(TimeInterpolator interpolator, long duration) { + mSourceInterpolator = interpolator; mLut = createLUT(interpolator, duration); } @@ -63,4 +67,9 @@ public class FallbackLUTInterpolator implements NativeInterpolatorFactory { float[] lut = createLUT(interpolator, duration); return NativeInterpolatorFactoryHelper.createLutInterpolator(lut); } + + @Override + public float getInterpolation(float input) { + return mSourceInterpolator.getInterpolation(input); + } } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 43e80dc..8418162 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -853,7 +853,7 @@ public: } else { SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius); - paint->setLooper(new SkBlurDrawLooper((SkColor)color, sigma, dx, dy))->unref(); + paint->setLooper(SkBlurDrawLooper::Create((SkColor)color, sigma, dx, dy))->unref(); } } diff --git a/core/jni/android_hardware_UsbRequest.cpp b/core/jni/android_hardware_UsbRequest.cpp index 01eaec4..a3c7b0a 100644 --- a/core/jni/android_hardware_UsbRequest.cpp +++ b/core/jni/android_hardware_UsbRequest.cpp @@ -100,18 +100,19 @@ android_hardware_UsbRequest_queue_array(JNIEnv *env, jobject thiz, } request->buffer_length = length; + // save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us + request->client_data = (void *)env->NewGlobalRef(thiz); + if (usb_request_queue(request)) { if (request->buffer) { // free our buffer if usb_request_queue fails free(request->buffer); request->buffer = NULL; } + env->DeleteGlobalRef((jobject)request->client_data); return false; - } else { - // save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us - request->client_data = (void *)env->NewGlobalRef(thiz); - return true; } + return true; } static jint @@ -152,16 +153,17 @@ android_hardware_UsbRequest_queue_direct(JNIEnv *env, jobject thiz, } request->buffer_length = length; + // save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us + // we also need this to make sure our native buffer is not deallocated + // while IO is active + request->client_data = (void *)env->NewGlobalRef(thiz); + if (usb_request_queue(request)) { request->buffer = NULL; + env->DeleteGlobalRef((jobject)request->client_data); return false; - } else { - // save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us - // we also need this to make sure our native buffer is not deallocated - // while IO is active - request->client_data = (void *)env->NewGlobalRef(thiz); - return true; } + return true; } static jint diff --git a/core/res/res/drawable/btn_borderless_quantum.xml b/core/res/res/drawable/btn_borderless_quantum.xml index 2e3c515..eaf2550 100644 --- a/core/res/res/drawable/btn_borderless_quantum.xml +++ b/core/res/res/drawable/btn_borderless_quantum.xml @@ -14,8 +14,8 @@ limitations under the License. --> -<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android" +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorButtonPressed"> <item android:id="@id/mask" android:drawable="@drawable/btn_qntm_alpha" /> -</touch-feedback> +</ripple> diff --git a/core/res/res/drawable/btn_default_quantum.xml b/core/res/res/drawable/btn_default_quantum.xml index c6a3a33..d8ab667 100644 --- a/core/res/res/drawable/btn_default_quantum.xml +++ b/core/res/res/drawable/btn_default_quantum.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android" +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorButtonPressed"> <item> <selector> @@ -31,4 +31,4 @@ </item> </selector> </item> -</touch-feedback> +</ripple> diff --git a/core/res/res/drawable/edit_text_quantum.xml b/core/res/res/drawable/edit_text_quantum.xml index c42c7b7..67339e8 100644 --- a/core/res/res/drawable/edit_text_quantum.xml +++ b/core/res/res/drawable/edit_text_quantum.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android" +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorControlActivated"> <item> <selector> @@ -36,4 +36,4 @@ </item> </selector> </item> -</touch-feedback> +</ripple> diff --git a/core/res/res/drawable/item_background_quantum.xml b/core/res/res/drawable/item_background_quantum.xml index 11e1f67..631d3e4 100644 --- a/core/res/res/drawable/item_background_quantum.xml +++ b/core/res/res/drawable/item_background_quantum.xml @@ -14,6 +14,6 @@ limitations under the License. --> -<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android" +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorButtonPressed" android:pinned="true" /> diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/list_selector_quantum.xml index c007117..0e185aa 100644 --- a/core/res/res/drawable/list_selector_quantum.xml +++ b/core/res/res/drawable/list_selector_quantum.xml @@ -14,9 +14,9 @@ limitations under the License. --> -<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android" +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorButtonPressed"> <item android:id="@id/mask"> <color android:color="@color/white" /> </item> -</touch-feedback> +</ripple> diff --git a/core/res/res/drawable/notification_bg_dim.xml b/core/res/res/drawable/notification_bg_dim.xml index ec20368..ae03d82 100644 --- a/core/res/res/drawable/notification_bg_dim.xml +++ b/core/res/res/drawable/notification_bg_dim.xml @@ -15,9 +15,9 @@ ~ limitations under the License --> -<touch-feedback +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="#ff444444" > <item android:drawable="@drawable/notification_bg_normal" /> -</touch-feedback>
\ No newline at end of file +</ripple>
\ No newline at end of file diff --git a/core/res/res/drawable/notification_quantum_bg_dim.xml b/core/res/res/drawable/notification_quantum_bg_dim.xml index ab0e049..eb9a4ab 100644 --- a/core/res/res/drawable/notification_quantum_bg_dim.xml +++ b/core/res/res/drawable/notification_quantum_bg_dim.xml @@ -15,7 +15,7 @@ ~ limitations under the License --> -<touch-feedback +<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="#ffffffff" android:tintMode="src_over" @@ -26,4 +26,4 @@ <corners android:radius="@dimen/notification_quantum_rounded_rect_radius" /> </shape> </item> -</touch-feedback>
\ No newline at end of file +</ripple>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 51dff74..4b03f31 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1669,6 +1669,11 @@ <enum name="KEYCODE_MEDIA_SLEEP" value="223" /> <enum name="KEYCODE_MEDIA_WAKEUP" value="224" /> <enum name="KEYCODE_PAIRING" value="225" /> + <enum name="KEYCODE_MEDIA_TOP_MENU" value="226" /> + <enum name="KEYCODE_11" value="227" /> + <enum name="KEYCODE_12" value="228" /> + <enum name="KEYCODE_LAST_CHANNEL" value="229" /> + <enum name="KEYCODE_TV_DATA_SERVICE" value="230" /> </attr> <!-- ***************************************************************** --> @@ -4492,7 +4497,7 @@ RTL (right-to-left). --> <attr name="autoMirrored" /> <!-- Indicates how layer padding should affect the bounds of subsequent layers. - The default value is nest. --> + The default padding mode value is nest. --> <attr name="paddingMode"> <!-- Nest each layer inside the padding of the previous layer. --> <enum name="nest" value="0" /> @@ -4647,7 +4652,7 @@ </declare-styleable> <!-- Drawable used to show animated touch feedback. --> - <declare-styleable name="TouchFeedbackDrawable"> + <declare-styleable name="RippleDrawable"> <!-- The tint to use for feedback ripples. This attribute is required. --> <attr name="tint" /> <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. --> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 6ea0fae..c966a12 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -83,5 +83,4 @@ <item type="id" name="current_scene" /> <item type="id" name="scene_layoutid_cache" /> <item type="id" name="mask" /> - <item type="id" name="shared_element" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 44b25f2b..32ce568 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2177,6 +2177,7 @@ <public type="attr" name="contentInsetEnd" /> <public type="attr" name="contentInsetLeft" /> <public type="attr" name="contentInsetRight" /> + <public type="attr" name="paddingMode" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2186,7 +2187,6 @@ <public-padding type="id" name="l_resource_pad" end="0x01020040" /> <public type="id" name="mask" /> - <public type="id" name="shared_element" /> <public-padding type="style" name="l_resource_pad" end="0x01030200" /> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java index 67203b2..22dce39 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java @@ -20,6 +20,8 @@ import android.app.Instrumentation; import android.content.Context; import android.os.Bundle; +import java.util.Set; + public class BluetoothInstrumentation extends Instrumentation { private BluetoothTestUtils mUtils = null; @@ -66,6 +68,8 @@ public class BluetoothInstrumentation extends Instrumentation { getName(); } else if ("getAddress".equals(command)) { getAddress(); + } else if ("getBondedDevices".equals(command)) { + getBondedDevices(); } else { finish(null); } @@ -98,6 +102,16 @@ public class BluetoothInstrumentation extends Instrumentation { finish(mSuccessResult); } + public void getBondedDevices() { + Set<BluetoothDevice> devices = getBluetoothAdapter().getBondedDevices(); + int i = 0; + for (BluetoothDevice device : devices) { + mSuccessResult.putString(String.format("device-%02d", i), device.getAddress()); + i++; + } + finish(mSuccessResult); + } + public void finish(Bundle result) { if (result == null) { result = new Bundle(); diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java new file mode 100644 index 0000000..eb6c419 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.os.ParcelUuid; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +import java.util.Arrays; + +/** + * Unit test cases for {@link BluetoothLeAdvertiseScanData}. + * <p> + * To run this test, use adb shell am instrument -e class + * 'android.bluetooth.BluetoothLeAdvertiseScanDataTest' -w + * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' + */ +public class BluetoothLeAdvertiseScanDataTest extends TestCase { + + @SmallTest + public void testParser() { + byte[] scanRecord = new byte[] { + 0x02, 0x01, 0x1a, // advertising flags + 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids + 0x04, 0x09, 0x50, 0x65, 0x64, // name + 0x02, 0x0A, (byte) 0xec, // tx power level + 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data + 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data + 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble + }; + BluetoothLeAdvertiseScanData.ScanRecord data = BluetoothLeAdvertiseScanData.ScanRecord + .getParser().parseFromScanRecord(scanRecord); + assertEquals(0x1a, data.getAdvertiseFlags()); + ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); + ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); + assertTrue(data.getServiceUuids().contains(uuid1)); + assertTrue(data.getServiceUuids().contains(uuid2)); + + assertEquals("Ped", data.getLocalName()); + assertEquals(-20, data.getTxPowerLevel()); + + assertEquals(224, data.getManufacturerId()); + assertArrayEquals(new byte[] { + (byte) 0xe0, 0x00, 0x02, 0x15 }, data.getManufacturerSpecificData()); + + assertEquals(uuid2, data.getServiceDataUuid()); + assertArrayEquals(new byte[] { + 0x0b, 0x11, 0x50, 0x64 }, data.getServiceData()); + } + + // Assert two byte arrays are equal. + private static void assertArrayEquals(byte[] expected, byte[] actual) { + if (!Arrays.equals(expected, actual)) { + fail("expected:<" + Arrays.toString(expected) + + "> but was:<" + Arrays.toString(actual) + ">"); + } + + } +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java new file mode 100644 index 0000000..ec35d85 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +/** + * Unit test cases for Bluetooth LE scan filters. + * <p> + * To run this test, use adb shell am instrument -e class + * 'android.bluetooth.BluetoothLeScanFilterTest' -w + * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' + */ +public class BluetoothLeScanFilterTest extends TestCase { + + private static final String DEVICE_MAC = "01:02:03:04:05:AB"; + private ScanResult mScanResult; + private BluetoothLeScanFilter.Builder mFilterBuilder; + + @Override + protected void setUp() throws Exception { + byte[] scanRecord = new byte[] { + 0x02, 0x01, 0x1a, // advertising flags + 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids + 0x04, 0x09, 0x50, 0x65, 0x64, // name + 0x02, 0x0A, (byte) 0xec, // tx power level + 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data + 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data + 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble + }; + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); + mScanResult = new ScanResult(device, scanRecord, -10, 1397545200000000L); + mFilterBuilder = BluetoothLeScanFilter.newBuilder(); + } + + @SmallTest + public void testNameFilter() { + BluetoothLeScanFilter filter = mFilterBuilder.name("Ped").build(); + assertTrue("name filter fails", filter.matches(mScanResult)); + + filter = mFilterBuilder.name("Pem").build(); + assertFalse("name filter fails", filter.matches(mScanResult)); + + } + + @SmallTest + public void testDeviceFilter() { + BluetoothLeScanFilter filter = mFilterBuilder.macAddress(DEVICE_MAC).build(); + assertTrue("device filter fails", filter.matches(mScanResult)); + + filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + assertFalse("device filter fails", filter.matches(mScanResult)); + } + + @SmallTest + public void testServiceUuidFilter() { + BluetoothLeScanFilter filter = mFilterBuilder.serviceUuid( + ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); + assertTrue("uuid filter fails", filter.matches(mScanResult)); + + filter = mFilterBuilder.serviceUuid( + ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); + assertFalse("uuid filter fails", filter.matches(mScanResult)); + + filter = mFilterBuilder + .serviceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")) + .serviceUuidMask(ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + assertTrue("uuid filter fails", filter.matches(mScanResult)); + } + + @SmallTest + public void testServiceDataFilter() { + byte[] serviceData = new byte[] { + 0x0b, 0x11, 0x50, 0x64 }; + BluetoothLeScanFilter filter = mFilterBuilder.serviceData(serviceData).build(); + assertTrue("service data filter fails", filter.matches(mScanResult)); + + byte[] nonMatchData = new byte[] { + 0x0b, 0x01, 0x50, 0x64 }; + filter = mFilterBuilder.serviceData(nonMatchData).build(); + assertFalse("service data filter fails", filter.matches(mScanResult)); + + byte[] mask = new byte[] { + (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; + filter = mFilterBuilder.serviceData(nonMatchData).serviceDataMask(mask).build(); + assertTrue("partial service data filter fails", filter.matches(mScanResult)); + } + + @SmallTest + public void testManufacturerSpecificData() { + byte[] manufacturerData = new byte[] { + (byte) 0xE0, 0x00, 0x02, 0x15 }; + int manufacturerId = 224; + BluetoothLeScanFilter filter = + mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); + assertTrue("manufacturerData filter fails", filter.matches(mScanResult)); + + byte[] nonMatchData = new byte[] { + (byte) 0xF0, 0x00, 0x02, 0x15 }; + filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData).build(); + assertFalse("manufacturerData filter fails", filter.matches(mScanResult)); + + byte[] mask = new byte[] { + (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF + }; + filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData) + .manufacturerDataMask(mask).build(); + assertTrue("partial manufacturerData filter fails", filter.matches(mScanResult)); + } + + @SmallTest + public void testReadWriteParcel() { + BluetoothLeScanFilter filter = mFilterBuilder.build(); + testReadWriteParcelForFilter(filter); + + filter = mFilterBuilder.name("Ped").build(); + testReadWriteParcelForFilter(filter); + + filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + testReadWriteParcelForFilter(filter); + + filter = mFilterBuilder.serviceUuid( + ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); + testReadWriteParcelForFilter(filter); + + filter = mFilterBuilder.serviceUuidMask( + ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); + testReadWriteParcelForFilter(filter); + + byte[] serviceData = new byte[] { + 0x0b, 0x11, 0x50, 0x64 }; + + filter = mFilterBuilder.serviceData(serviceData).build(); + testReadWriteParcelForFilter(filter); + + byte[] serviceDataMask = new byte[] { + (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; + filter = mFilterBuilder.serviceDataMask(serviceDataMask).build(); + testReadWriteParcelForFilter(filter); + + byte[] manufacturerData = new byte[] { + (byte) 0xE0, 0x00, 0x02, 0x15 }; + int manufacturerId = 224; + filter = mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); + testReadWriteParcelForFilter(filter); + + byte[] manufacturerDataMask = new byte[] { + (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF + }; + filter = mFilterBuilder.manufacturerDataMask(manufacturerDataMask).build(); + testReadWriteParcelForFilter(filter); + } + + private void testReadWriteParcelForFilter(BluetoothLeScanFilter filter) { + Parcel parcel = Parcel.obtain(); + filter.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + BluetoothLeScanFilter filterFromParcel = + BluetoothLeScanFilter.CREATOR.createFromParcel(parcel); + System.out.println(filterFromParcel); + assertEquals(filter, filterFromParcel); + } +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java new file mode 100644 index 0000000..8064ba8 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +/** + * Unit test cases for Bluetooth LE scans. + * <p> + * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothLeScannerTest' + * -w 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' + */ +public class BluetoothLeScannerTest extends TestCase { + + /** + * Test read and write parcel of ScanResult + */ + @SmallTest + public void testScanResultParceling() { + BluetoothDevice device = new BluetoothDevice("01:02:03:04:05:06"); + byte[] scanRecord = new byte[] { + 1, 2, 3 }; + int rssi = -10; + long timestampMicros = 10000L; + + ScanResult result = new ScanResult(device, scanRecord, rssi, timestampMicros); + Parcel parcel = Parcel.obtain(); + result.writeToParcel(parcel, 0); + // Need to reset parcel data position to the beginning. + parcel.setDataPosition(0); + ScanResult resultFromParcel = ScanResult.CREATOR.createFromParcel(parcel); + assertEquals(result, resultFromParcel); + } + +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java index 0f3ea0e..536d7226 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java @@ -47,4 +47,20 @@ public class BluetoothUuidTest extends TestCase { assertEquals(ParcelUuid.fromString("FF0F0E0D-0C0B-0A09-0807-0060504030201"), BluetoothUuid.parseUuidFrom(uuid128)); } + + @SmallTest + public void testUuidType() { + assertTrue(BluetoothUuid.is16BitUuid( + ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); + assertFalse(BluetoothUuid.is32BitUuid( + ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); + + assertFalse(BluetoothUuid.is16BitUuid( + ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); + assertTrue(BluetoothUuid.is32BitUuid( + ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); + assertFalse(BluetoothUuid.is32BitUuid( + ParcelUuid.fromString("FE33110B-1000-1000-8000-00805F9B34FB"))); + + } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b939636..911fb96 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -484,68 +484,40 @@ public abstract class Drawable { } /** - * Indicates whether the drawable supports hotspots. Hotspots are uniquely - * identifiable coordinates the may be added, updated and removed within a - * drawable. - * - * @return true if hotspots are supported - * @see #setHotspot(int, float, float) - * @see #removeHotspot(int) - * @see #clearHotspots() - */ - public boolean supportsHotspots() { - return false; - } - - /** - * Specifies a hotspot's location within the drawable. - * <p> - * The specified key should be an id declared in the resources of the - * application to ensure it is unique (see the <a - * href={@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>). + * Specifies the hotspot's location within the drawable. * - * @param key The key identifying the hotspot * @param x The X coordinate of the center of the hotspot * @param y The Y coordinate of the center of the hotspot */ - public void setHotspot(int key, float x, float y) {} - - /** - * Removes the hotspot with the specified key from the drawable. - * - * @param key The key identifying the hotspot - */ - public void removeHotspot(int key) {} + public void setHotspot(float x, float y) {} /** - * Immediately removes all hotspots from the drawable. - */ - public void clearHotspots() {} - - /** - * Sets the bounds to which hotspots are constrained. - * - * @hide until we finalize these APIs + * Sets the bounds to which the hotspot is constrained, if they should be + * different from the drawable bounds. + * + * @param left + * @param top + * @param right + * @param bottom */ public void setHotspotBounds(int left, int top, int right, int bottom) {} /** * Whether this drawable requests projection. * - * @hide until we finalize these APIs + * @hide magic! */ public boolean isProjected() { return false; } /** - * Indicates whether this view will change its appearance based on state. - * Clients can use this to determine whether it is necessary to calculate - * their state and call setState. - * - * @return True if this view changes its appearance based on state, false - * otherwise. + * Indicates whether this drawable will change its appearance based on + * state. Clients can use this to determine whether it is necessary to + * calculate their state and call setState. * + * @return True if this drawable changes its appearance based on state, + * false otherwise. * @see #setState(int[]) */ public boolean isStateful() { @@ -1032,6 +1004,11 @@ public abstract class Drawable { return createFromXmlInnerThemed(r, parser, attrs, null); } + /** + * Create a themed drawable from inside an XML document. Called on a parser + * positioned at a tag in an XML document, tries to create a Drawable from + * that tag. Returns null if the tag is not a valid drawable. + */ public static Drawable createFromXmlInnerThemed(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final Drawable drawable; @@ -1047,8 +1024,8 @@ public abstract class Drawable { drawable = new LayerDrawable(); } else if (name.equals("transition")) { drawable = new TransitionDrawable(); - } else if (name.equals("touch-feedback")) { - drawable = new TouchFeedbackDrawable(); + } else if (name.equals("ripple")) { + drawable = new RippleDrawable(); } else if (name.equals("color")) { drawable = new ColorDrawable(); } else if (name.equals("shape")) { diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 08fc99d..ec5c6c6 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -238,35 +238,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override - public void setHotspot(int key, float x, float y) { + public void setHotspot(float x, float y) { if (mCurrDrawable != null) { - mCurrDrawable.setHotspot(key, x, y); + mCurrDrawable.setHotspot(x, y); } } @Override - public void removeHotspot(int key) { - if (mCurrDrawable != null) { - mCurrDrawable.removeHotspot(key); - } - } - - @Override - public void clearHotspots() { - if (mCurrDrawable != null) { - mCurrDrawable.clearHotspots(); - } - } - - @Override - public boolean supportsHotspots() { - if (mCurrDrawable != null) { - return mCurrDrawable.supportsHotspots(); - } - return false; - } - - @Override protected boolean onStateChange(int[] state) { if (mLastDrawable != null) { return mLastDrawable.setState(state); diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java deleted file mode 100644 index 6ab33f8..0000000 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics.drawable; - -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.graphics.Xfermode; - -/** - * A Drawable that wraps another Drawable. - */ -public class DrawableWrapper extends Drawable implements Drawable.Callback { - private WrapperState mWrapperState; - - /** Local drawable backed by its own constant state. */ - private Drawable mWrappedDrawable; - - private boolean mMutated; - - /** @hide */ - @Override - public boolean isProjected() { - return mWrappedDrawable.isProjected(); - } - - @Override - public void setAutoMirrored(boolean mirrored) { - mWrappedDrawable.setAutoMirrored(mirrored); - } - - @Override - public boolean isAutoMirrored() { - return mWrappedDrawable.isAutoMirrored(); - } - - @Override - public int getMinimumWidth() { - return mWrappedDrawable.getMinimumWidth(); - } - - @Override - public int getMinimumHeight() { - return mWrappedDrawable.getMinimumHeight(); - } - - @Override - public int getIntrinsicWidth() { - return mWrappedDrawable.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mWrappedDrawable.getIntrinsicHeight(); - } - - @Override - public Drawable getCurrent() { - return mWrappedDrawable.getCurrent(); - } - - @Override - public void invalidateDrawable(Drawable who) { - final Callback callback = getCallback(); - if (callback != null) { - callback.invalidateDrawable(this); - } - } - - @Override - public void scheduleDrawable(Drawable who, Runnable what, long when) { - final Callback callback = getCallback(); - if (callback != null) { - callback.scheduleDrawable(this, what, when); - } - } - - @Override - public void unscheduleDrawable(Drawable who, Runnable what) { - final Callback callback = getCallback(); - if (callback != null) { - callback.unscheduleDrawable(this, what); - } - } - - @Override - public void draw(Canvas canvas) { - mWrappedDrawable.draw(canvas); - } - - @Override - public int getChangingConfigurations() { - return mWrappedDrawable.getChangingConfigurations(); - } - - @Override - public boolean getPadding(Rect padding) { - return mWrappedDrawable.getPadding(padding); - } - - @Override - public Rect getDirtyBounds() { - return mWrappedDrawable.getDirtyBounds(); - } - - @Override - public boolean supportsHotspots() { - return mWrappedDrawable.supportsHotspots(); - } - - @Override - public void setHotspot(int id, float x, float y) { - mWrappedDrawable.setHotspot(id, x, y); - } - - @Override - public void removeHotspot(int id) { - mWrappedDrawable.removeHotspot(id); - } - - @Override - public void clearHotspots() { - mWrappedDrawable.clearHotspots(); - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - // Must call through to super(). - super.setVisible(visible, restart); - return mWrappedDrawable.setVisible(visible, restart); - } - - @Override - public void setAlpha(int alpha) { - mWrappedDrawable.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mWrappedDrawable.getAlpha(); - } - - /** {@hide} */ - @Override - public void setLayoutDirection(int layoutDirection) { - mWrappedDrawable.setLayoutDirection(layoutDirection); - } - - /** {@hide} */ - @Override - public int getLayoutDirection() { - return mWrappedDrawable.getLayoutDirection(); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mWrappedDrawable.setColorFilter(cf); - } - - @Override - public ColorFilter getColorFilter() { - return mWrappedDrawable.getColorFilter(); - } - - @Override - public void setFilterBitmap(boolean filter) { - mWrappedDrawable.setFilterBitmap(filter); - } - - @Override - public void setXfermode(Xfermode mode) { - mWrappedDrawable.setXfermode(mode); - } - - @Override - public int getOpacity() { - return mWrappedDrawable.getOpacity(); - } - - @Override - public boolean isStateful() { - return mWrappedDrawable.isStateful(); - } - - @Override - public final boolean setState(int[] stateSet) { - return super.setState(stateSet); - } - - @Override - public final int[] getState() { - return super.getState(); - } - - @Override - protected boolean onStateChange(int[] state) { - // Don't override setState(), getState(). - return mWrappedDrawable.setState(state); - } - - @Override - protected boolean onLevelChange(int level) { - // Don't override setLevel(), getLevel(). - return mWrappedDrawable.setLevel(level); - } - - @Override - public final void setBounds(int left, int top, int right, int bottom) { - super.setBounds(left, top, right, bottom); - } - - @Override - public final void setBounds(Rect bounds) { - super.setBounds(bounds); - } - - @Override - protected void onBoundsChange(Rect bounds) { - // Don't override setBounds(), getBounds(). - mWrappedDrawable.setBounds(bounds); - } - - protected void setConstantState(WrapperState wrapperState, Resources res) { - mWrapperState = wrapperState; - - // Load a new drawable from the constant state. - if (wrapperState == null || wrapperState.mWrappedConstantState == null) { - mWrappedDrawable = null; - } else if (res != null) { - mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable(res); - } else { - mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable(); - } - } - - @Override - public ConstantState getConstantState() { - return mWrapperState; - } - - @Override - public Drawable mutate() { - if (!mMutated) { - mWrappedDrawable = mWrappedDrawable.mutate(); - mMutated = true; - } - return this; - } - - /** - * Sets the wrapped drawable and update the constant state. - * - * @param drawable - * @param res - */ - protected final void setDrawable(Drawable drawable, Resources res) { - if (mWrappedDrawable != null) { - mWrappedDrawable.setCallback(null); - } - - mWrappedDrawable = drawable; - - if (drawable != null) { - drawable.setCallback(this); - - mWrapperState.mWrappedConstantState = drawable.getConstantState(); - } else { - mWrapperState.mWrappedConstantState = null; - } - } - - protected final Drawable getDrawable() { - return mWrappedDrawable; - } - - public static abstract class WrapperState extends ConstantState { - ConstantState mWrappedConstantState; - - WrapperState(WrapperState orig) { - if (orig != null) { - mWrappedConstantState = orig.mWrappedConstantState; - } - } - - @Override - public int getChangingConfigurations() { - return mWrappedConstantState.getChangingConfigurations(); - } - } -} diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 9384caf..3749339 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -131,6 +131,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback // overrides from Drawable.Callback + @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { @@ -138,6 +139,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback } } + @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { @@ -145,6 +147,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback } } + @Override public void unscheduleDrawable(Drawable who, Runnable what) { final Callback callback = getCallback(); if (callback != null) { @@ -184,23 +187,8 @@ public class InsetDrawable extends Drawable implements Drawable.Callback } @Override - public boolean supportsHotspots() { - return mInsetState.mDrawable.supportsHotspots(); - } - - @Override - public void setHotspot(int id, float x, float y) { - mInsetState.mDrawable.setHotspot(id, x, y); - } - - @Override - public void removeHotspot(int id) { - mInsetState.mDrawable.removeHotspot(id); - } - - @Override - public void clearHotspots() { - mInsetState.mDrawable.clearHotspots(); + public void setHotspot(float x, float y) { + mInsetState.mDrawable.setHotspot(x, y); } @Override diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 7847aad..f446000 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -38,10 +38,12 @@ import java.io.IOException; * order, so the element with the largest index will be drawn on top. * <p> * It can be defined in an XML file with the <code><layer-list></code> element. - * Each Drawable in the layer is defined in a nested <code><item></code>. For more - * information, see the guide to <a - * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> + * Each Drawable in the layer is defined in a nested <code><item></code>. + * <p> + * For more information, see the guide to + * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. * + * @attr ref android.R.styleable#LayerDrawable_paddingMode * @attr ref android.R.styleable#LayerDrawableItem_left * @attr ref android.R.styleable#LayerDrawableItem_top * @attr ref android.R.styleable#LayerDrawableItem_right @@ -53,10 +55,16 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { /** * Padding mode used to nest each layer inside the padding of the previous * layer. + * + * @see #setPaddingMode(int) */ public static final int PADDING_MODE_NEST = 0; - /** Padding mode used to stack each layer directly atop the previous layer. */ + /** + * Padding mode used to stack each layer directly atop the previous layer. + * + * @see #setPaddingMode(int) + */ public static final int PADDING_MODE_STACK = 1; LayerState mLayerState; @@ -127,9 +135,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes( - r, theme, attrs, R.styleable.LayerDrawable); - inflateStateFromTypedArray(a); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable); + updateStateFromTypedArray(a); a.recycle(); inflateLayers(r, parser, attrs, theme); @@ -141,25 +148,19 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { /** * Initializes the constant state from the values in the typed array. */ - private void inflateStateFromTypedArray(TypedArray a) { + private void updateStateFromTypedArray(TypedArray a) { final LayerState state = mLayerState; // Extract the theme attributes, if any. final int[] themeAttrs = a.extractThemeAttrs(); state.mThemeAttrs = themeAttrs; - if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_opacity] == 0) { - mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); - } - - if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_autoMirrored] == 0) { - state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); - } + mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride); - if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawableItem_drawable] == 0) { - state.mPaddingMode = a.getInteger( - R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); - } + state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, + state.mAutoMirrored); + state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode, + state.mPaddingMode); } /** @@ -181,9 +182,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { continue; } - a = obtainAttributes( - r, theme, attrs, R.styleable.LayerDrawableItem); + a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem); + final int[] themeAttrs = a.extractThemeAttrs(); final int left = a.getDimensionPixelOffset( R.styleable.LayerDrawableItem_left, 0); final int top = a.getDimensionPixelOffset( @@ -197,7 +198,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final int id = a.getResourceId( R.styleable.LayerDrawableItem_id, View.NO_ID); - // TODO: Cache typed array, if necessary. a.recycle(); final Drawable dr; @@ -214,7 +214,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } - addLayer(dr, id, left, top, right, bottom); + addLayer(dr, themeAttrs, id, left, top, right, bottom); } } @@ -224,7 +224,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final LayerState state = mLayerState; if (state == null) { - throw new RuntimeException("Can't apply theme to <layer-list> with no constant state"); + return; } final int[] themeAttrs = state.mThemeAttrs; @@ -239,9 +239,10 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { - final Drawable layer = array[i].mDrawable; - if (layer.canApplyTheme()) { - layer.applyTheme(t); + final ChildDrawable layer = array[i]; + final Drawable d = layer.mDrawable; + if (d.canApplyTheme()) { + d.applyTheme(t); } } @@ -249,26 +250,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { onStateChange(getState()); } - /** - * Updates the constant state from the values in the typed array. - */ - private void updateStateFromTypedArray(TypedArray a) { - final LayerState state = mLayerState; - - if (a.hasValue(R.styleable.LayerDrawable_opacity)) { - mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); - } - - if (a.hasValue(R.styleable.LayerDrawable_autoMirrored)) { - state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); - } - - if (a.hasValue(R.styleable.LayerDrawableItem_drawable)) { - state.mPaddingMode = a.getInteger( - R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); - } - } - @Override public boolean canApplyTheme() { final LayerState state = mLayerState; @@ -283,14 +264,15 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable[] array = state.mChildren; final int N = state.mNum; for (int i = 0; i < N; i++) { - if (array[i].mDrawable.canApplyTheme()) { + final ChildDrawable layer = array[i]; + if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { return true; } } return false; } - + /** * @hide */ @@ -315,13 +297,15 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * Add a new layer to this drawable. The new layer is identified by an id. * * @param layer The drawable to add as a layer. + * @param themeAttrs Theme attributes extracted from the layer. * @param id The id of the new layer. * @param left The left padding of the new layer. * @param top The top padding of the new layer. * @param right The right padding of the new layer. * @param bottom The bottom padding of the new layer. */ - private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) { + private void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right, + int bottom) { final LayerState st = mLayerState; final int N = st.mChildren != null ? st.mChildren.length : 0; final int i = st.mNum; @@ -339,6 +323,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable childDrawable = new ChildDrawable(); st.mChildren[i] = childDrawable; childDrawable.mId = id; + childDrawable.mThemeAttrs = themeAttrs; childDrawable.mDrawable = layer; childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); childDrawable.mInsetL = left; @@ -471,8 +456,14 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * * @param mode padding mode, one of: * <ul> - * <li>{@link #PADDING_MODE_NEST} <li>{@link #PADDING_MODE_STACK} + * <li>{@link #PADDING_MODE_NEST} to nest each layer inside the + * padding of the previous layer + * <li>{@link #PADDING_MODE_STACK} to stack each layer directly + * atop the previous layer * </ul> + * + * @see #getPaddingMode() + * @attr ref android.R.styleable#LayerDrawable_paddingMode */ public void setPaddingMode(int mode) { if (mLayerState.mPaddingMode != mode) { @@ -482,7 +473,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { /** * @return the current padding mode + * * @see #setPaddingMode(int) + * @attr ref android.R.styleable#LayerDrawable_paddingMode */ public int getPaddingMode() { return mLayerState.mPaddingMode; @@ -569,42 +562,11 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public boolean supportsHotspots() { - final ChildDrawable[] array = mLayerState.mChildren; - final int N = mLayerState.mNum; - for (int i = 0; i < N; i++) { - if (array[i].mDrawable.supportsHotspots()) { - return true; - } - } - - return false; - } - - @Override - public void setHotspot(int id, float x, float y) { - final ChildDrawable[] array = mLayerState.mChildren; - final int N = mLayerState.mNum; - for (int i = 0; i < N; i++) { - array[i].mDrawable.setHotspot(id, x, y); - } - } - - @Override - public void removeHotspot(int id) { - final ChildDrawable[] array = mLayerState.mChildren; - final int N = mLayerState.mNum; - for (int i = 0; i < N; i++) { - array[i].mDrawable.removeHotspot(id); - } - } - - @Override - public void clearHotspots() { + public void setHotspot(float x, float y) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { - array[i].mDrawable.clearHotspots(); + array[i].mDrawable.setHotspot(x, y); } } @@ -936,7 +898,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { private boolean mHaveIsStateful; private boolean mIsStateful; - private boolean mAutoMirrored; + private boolean mAutoMirrored = false; private int mPaddingMode = PADDING_MODE_NEST; diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index a55a4b2..6776e66 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -22,6 +22,7 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PointF; @@ -44,7 +45,7 @@ import java.io.IOException; /** * Drawable that shows a ripple effect in response to state changes. The * anchoring position of the ripple for a given state may be specified by - * calling {@link #setHotspot(int, float, float)} with the corresponding state + * calling {@link #setHotspot(float, float)} with the corresponding state * attribute identifier. * <p> * A touch feedback drawable may contain multiple child layers, including a @@ -56,19 +57,19 @@ import java.io.IOException; * <p> * If no mask layer is set, the ripple effect is simply blended onto the * composite of the child layers using the specified - * {@link android.R.styleable#TouchFeedbackDrawable_tintMode}. + * {@link android.R.styleable#RippleDrawable_tintMode}. * <p> * If no child layers or mask is specified and the ripple is set as a View * background, the ripple will be blended onto the first available parent * background within the View's hierarchy using the specified - * {@link android.R.styleable#TouchFeedbackDrawable_tintMode}. In this case, the + * {@link android.R.styleable#RippleDrawable_tintMode}. In this case, the * drawing region may extend outside of the Drawable bounds. * * @attr ref android.R.styleable#DrawableStates_state_focused * @attr ref android.R.styleable#DrawableStates_state_pressed */ -public class TouchFeedbackDrawable extends LayerDrawable { - private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName(); +public class RippleDrawable extends LayerDrawable { + private static final String LOG_TAG = RippleDrawable.class.getSimpleName(); private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN); private static final PorterDuffXfermode DST_ATOP = new PorterDuffXfermode(Mode.DST_ATOP); private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP); @@ -88,17 +89,17 @@ public class TouchFeedbackDrawable extends LayerDrawable { /** Current dirty bounds, union of current and previous drawing bounds. */ private final Rect mDirtyBounds = new Rect(); - private final TouchFeedbackState mState; + private final RippleState mState; /** * Lazily-created map of pending hotspot locations. These may be modified by - * calls to {@link #setHotspot(int, float, float)}. + * calls to {@link #setHotspot(float, float)}. */ private SparseArray<PointF> mPendingHotspots; /** * Lazily-created map of active hotspot locations. These may be modified by - * calls to {@link #setHotspot(int, float, float)}. + * calls to {@link #setHotspot(float, float)}. */ private SparseArray<Ripple> mActiveHotspots; @@ -121,8 +122,18 @@ public class TouchFeedbackDrawable extends LayerDrawable { /** Whether bounds are being overridden. */ private boolean mOverrideBounds; - TouchFeedbackDrawable() { - this(new TouchFeedbackState(null, null, null), null, null); + RippleDrawable() { + this(new RippleState(null, null, null), null, null); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter cf) { + } @Override @@ -233,7 +244,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes( - r, theme, attrs, R.styleable.TouchFeedbackDrawable); + r, theme, attrs, R.styleable.RippleDrawable); updateStateFromTypedArray(a); a.recycle(); @@ -267,22 +278,22 @@ public class TouchFeedbackDrawable extends LayerDrawable { * Initializes the constant state from the values in the typed array. */ private void updateStateFromTypedArray(TypedArray a) { - final TouchFeedbackState state = mState; + final RippleState state = mState; // Extract the theme attributes, if any. state.mTouchThemeAttrs = a.extractThemeAttrs(); - final ColorStateList tint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + final ColorStateList tint = a.getColorStateList(R.styleable.RippleDrawable_tint); if (tint != null) { mState.mTint = tint; } - final int tintMode = a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1); + final int tintMode = a.getInt(R.styleable.RippleDrawable_tintMode, -1); if (tintMode != -1) { mState.setTintMode(Drawable.parseTintMode(tintMode, Mode.SRC_ATOP)); } - mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, mState.mPinned); + mState.mPinned = a.getBoolean(R.styleable.RippleDrawable_pinned, mState.mPinned); } /** @@ -301,13 +312,13 @@ public class TouchFeedbackDrawable extends LayerDrawable { public void applyTheme(Theme t) { super.applyTheme(t); - final TouchFeedbackState state = mState; + final RippleState state = mState; if (state == null || state.mTouchThemeAttrs == null) { return; } final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs, - R.styleable.TouchFeedbackDrawable); + R.styleable.RippleDrawable); updateStateFromTypedArray(a); a.recycle(); } @@ -318,17 +329,14 @@ public class TouchFeedbackDrawable extends LayerDrawable { } @Override - public boolean supportsHotspots() { - return true; - } - - @Override - public void setHotspot(int id, float x, float y) { + public void setHotspot(float x, float y) { if (mState.mPinned && !circleContains(mHotspotBounds, x, y)) { x = mHotspotBounds.exactCenterX(); y = mHotspotBounds.exactCenterY(); } + // TODO: We should only have a single pending/active hotspot. + final int id = R.attr.state_pressed; final int[] stateSet = getState(); if (!Arrays.contains(stateSet, id)) { // The hotspot is not active, so just modify the pending location. @@ -423,8 +431,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { mActiveHotspots.put(id, newRipple); } - @Override - public void removeHotspot(int id) { + private void removeHotspot(int id) { if (mActiveHotspots == null) { return; } @@ -437,8 +444,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { } } - @Override - public void clearHotspots() { + private void clearHotspots() { if (mActiveHotspots != null) { mActiveHotspots.clear(); } @@ -632,7 +638,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { return mState; } - static class TouchFeedbackState extends LayerState { + static class RippleState extends LayerState { int[] mTouchThemeAttrs; ColorStateList mTint = null; PorterDuffXfermode mTintXfermode = SRC_ATOP; @@ -640,8 +646,8 @@ public class TouchFeedbackDrawable extends LayerDrawable { Drawable mMask; boolean mPinned = false; - public TouchFeedbackState( - TouchFeedbackState orig, TouchFeedbackDrawable owner, Resources res) { + public RippleState( + RippleState orig, RippleDrawable owner, Resources res) { super(orig, owner, res); if (orig != null) { @@ -655,7 +661,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { } public void setTintMode(Mode mode) { - final Mode invertedMode = TouchFeedbackState.invertPorterDuffMode(mode); + final Mode invertedMode = RippleState.invertPorterDuffMode(mode); mTintXfermodeInverse = new PorterDuffXfermode(invertedMode); mTintXfermode = new PorterDuffXfermode(mode); } @@ -675,17 +681,17 @@ public class TouchFeedbackDrawable extends LayerDrawable { @Override public Drawable newDrawable() { - return new TouchFeedbackDrawable(this, null, null); + return new RippleDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new TouchFeedbackDrawable(this, res, null); + return new RippleDrawable(this, res, null); } @Override public Drawable newDrawable(Resources res, Theme theme) { - return new TouchFeedbackDrawable(this, res, theme); + return new RippleDrawable(this, res, theme); } /** @@ -716,20 +722,20 @@ public class TouchFeedbackDrawable extends LayerDrawable { } } - private TouchFeedbackDrawable(TouchFeedbackState state, Resources res, Theme theme) { + private RippleDrawable(RippleState state, Resources res, Theme theme) { boolean needsTheme = false; - final TouchFeedbackState ns; + final RippleState ns; if (theme != null && state != null && state.canApplyTheme()) { - ns = new TouchFeedbackState(state, this, res); + ns = new RippleState(state, this, res); needsTheme = true; } else if (state == null) { - ns = new TouchFeedbackState(null, this, res); + ns = new RippleState(null, this, res); } else { // We always need a new state since child drawables contain local // state but live within the parent's constant state. // TODO: Move child drawables into local state. - ns = new TouchFeedbackState(state, this, res); + ns = new RippleState(state, this, res); } if (res != null) { diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index edf1091..5f9d1cd 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -145,6 +145,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * Sets the start angle for rotation. * * @param fromDegrees Starting angle in degrees + * + * @see #getFromDegrees() + * @attr ref android.R.styleable#RotateDrawable_fromDegrees */ public void setFromDegrees(float fromDegrees) { if (mState.mFromDegrees != fromDegrees) { @@ -155,6 +158,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return The starting angle for rotation in degrees + * + * @see #setFromDegrees(float) + * @attr ref android.R.styleable#RotateDrawable_fromDegrees */ public float getFromDegrees() { return mState.mFromDegrees; @@ -164,6 +170,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * Sets the end angle for rotation. * * @param toDegrees Ending angle in degrees + * + * @see #getToDegrees() + * @attr ref android.R.styleable#RotateDrawable_toDegrees */ public void setToDegrees(float toDegrees) { if (mState.mToDegrees != toDegrees) { @@ -174,6 +183,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return The ending angle for rotation in degrees + * + * @see #setToDegrees(float) + * @attr ref android.R.styleable#RotateDrawable_toDegrees */ public float getToDegrees() { return mState.mToDegrees; @@ -186,7 +198,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * relative, the position represents a fraction of the drawable * width. Otherwise, the position represents an absolute value in * pixels. + * * @see #setPivotXRelative(boolean) + * @attr ref android.R.styleable#RotateDrawable_pivotX */ public void setPivotX(float pivotX) { if (mState.mPivotX == pivotX) { @@ -197,7 +211,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return X position around which to rotate + * * @see #setPivotX(float) + * @attr ref android.R.styleable#RotateDrawable_pivotX */ public float getPivotX() { return mState.mPivotX; @@ -209,6 +225,8 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * * @param relative True if the X pivot represents a fraction of the drawable * width, or false if it represents an absolute value in pixels + * + * @see #isPivotXRelative() */ public void setPivotXRelative(boolean relative) { if (mState.mPivotXRel == relative) { @@ -220,6 +238,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return True if the X pivot represents a fraction of the drawable width, * or false if it represents an absolute value in pixels + * * @see #setPivotXRelative(boolean) */ public boolean isPivotXRelative() { @@ -233,7 +252,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * relative, the position represents a fraction of the drawable * height. Otherwise, the position represents an absolute value * in pixels. - * @see #setPivotYRelative(boolean) + * + * @see #getPivotY() + * @attr ref android.R.styleable#RotateDrawable_pivotY */ public void setPivotY(float pivotY) { if (mState.mPivotY == pivotY) { @@ -244,7 +265,9 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return Y position around which to rotate + * * @see #setPivotY(float) + * @attr ref android.R.styleable#RotateDrawable_pivotY */ public float getPivotY() { return mState.mPivotY; @@ -256,6 +279,8 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * * @param relative True if the Y pivot represents a fraction of the drawable * height, or false if it represents an absolute value in pixels + * + * @see #isPivotYRelative() */ public void setPivotYRelative(boolean relative) { if (mState.mPivotYRel == relative) { @@ -267,6 +292,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * @return True if the Y pivot represents a fraction of the drawable height, * or false if it represents an absolute value in pixels + * * @see #setPivotYRelative(boolean) */ public boolean isPivotYRelative() { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 3a3f76d..575667d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2557,60 +2557,191 @@ public class AudioManager { // class is not used by other parts of the framework, which instead use definitions and methods // from AudioManager. AudioSystem is an internal class used by AudioManager and AudioService. - /** {@hide} The audio output device code for the small speaker at the front of the device used + /** @hide + * The audio output device code for the small speaker at the front of the device used * when placing calls. Does not refer to an in-ear headphone without attached microphone, * such as earbuds, earphones, or in-ear monitors (IEM). Those would be handled as a * {@link #DEVICE_OUT_WIRED_HEADPHONE}. */ public static final int DEVICE_OUT_EARPIECE = AudioSystem.DEVICE_OUT_EARPIECE; - /** {@hide} The audio output device code for the built-in speaker */ + /** @hide + * The audio output device code for the built-in speaker */ public static final int DEVICE_OUT_SPEAKER = AudioSystem.DEVICE_OUT_SPEAKER; - /** {@hide} The audio output device code for a wired headset with attached microphone */ + /** @hide + * The audio output device code for a wired headset with attached microphone */ public static final int DEVICE_OUT_WIRED_HEADSET = AudioSystem.DEVICE_OUT_WIRED_HEADSET; - /** {@hide} The audio output device code for a wired headphone without attached microphone */ + /** @hide + * The audio output device code for a wired headphone without attached microphone */ public static final int DEVICE_OUT_WIRED_HEADPHONE = AudioSystem.DEVICE_OUT_WIRED_HEADPHONE; - /** {@hide} The audio output device code for generic Bluetooth SCO, for voice */ + /** @hide + * The audio output device code for generic Bluetooth SCO, for voice */ public static final int DEVICE_OUT_BLUETOOTH_SCO = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; - /** {@hide} The audio output device code for Bluetooth SCO Headset Profile (HSP) and - * Hands-Free Profile (HFP), for voice + /** @hide + * The audio output device code for Bluetooth SCO Headset Profile (HSP) and + * Hands-Free Profile (HFP), for voice */ public static final int DEVICE_OUT_BLUETOOTH_SCO_HEADSET = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; - /** {@hide} The audio output device code for Bluetooth SCO car audio, for voice */ + /** @hide + * The audio output device code for Bluetooth SCO car audio, for voice */ public static final int DEVICE_OUT_BLUETOOTH_SCO_CARKIT = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; - /** {@hide} The audio output device code for generic Bluetooth A2DP, for music */ + /** @hide + * The audio output device code for generic Bluetooth A2DP, for music */ public static final int DEVICE_OUT_BLUETOOTH_A2DP = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - /** {@hide} The audio output device code for Bluetooth A2DP headphones, for music */ + /** @hide + * The audio output device code for Bluetooth A2DP headphones, for music */ public static final int DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; - /** {@hide} The audio output device code for Bluetooth A2DP external speaker, for music */ + /** @hide + * The audio output device code for Bluetooth A2DP external speaker, for music */ public static final int DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; - /** {@hide} The audio output device code for S/PDIF or HDMI */ + /** @hide + * The audio output device code for S/PDIF (legacy) or HDMI + * Deprecated: replaced by {@link #DEVICE_OUT_HDMI} */ public static final int DEVICE_OUT_AUX_DIGITAL = AudioSystem.DEVICE_OUT_AUX_DIGITAL; - /** {@hide} The audio output device code for an analog wired headset attached via a + /** @hide + * The audio output device code for HDMI */ + public static final int DEVICE_OUT_HDMI = AudioSystem.DEVICE_OUT_HDMI; + /** @hide + * The audio output device code for an analog wired headset attached via a * docking station */ public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET; - /** {@hide} The audio output device code for a digital wired headset attached via a + /** @hide + * The audio output device code for a digital wired headset attached via a * docking station */ public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET; - /** {@hide} The audio output device code for a USB audio accessory. The accessory is in USB host + /** @hide + * The audio output device code for a USB audio accessory. The accessory is in USB host * mode and the Android device in USB device mode */ public static final int DEVICE_OUT_USB_ACCESSORY = AudioSystem.DEVICE_OUT_USB_ACCESSORY; - /** {@hide} The audio output device code for a USB audio device. The device is in USB device + /** @hide + * The audio output device code for a USB audio device. The device is in USB device * mode and the Android device in USB host mode */ public static final int DEVICE_OUT_USB_DEVICE = AudioSystem.DEVICE_OUT_USB_DEVICE; - /** {@hide} This is not used as a returned value from {@link #getDevicesForStream}, but could be + /** @hide + * The audio output device code for projection output. + */ + public static final int DEVICE_OUT_REMOTE_SUBMIX = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + /** @hide + * The audio output device code the telephony voice TX path. + */ + public static final int DEVICE_OUT_TELEPHONY_TX = AudioSystem.DEVICE_OUT_TELEPHONY_TX; + /** @hide + * The audio output device code for an analog jack with line impedance detected. + */ + public static final int DEVICE_OUT_LINE = AudioSystem.DEVICE_OUT_LINE; + /** @hide + * The audio output device code for HDMI Audio Return Channel. + */ + public static final int DEVICE_OUT_HDMI_ARC = AudioSystem.DEVICE_OUT_HDMI_ARC; + /** @hide + * The audio output device code for S/PDIF digital connection. + */ + public static final int DEVICE_OUT_SPDIF = AudioSystem.DEVICE_OUT_SPDIF; + /** @hide + * The audio output device code for built-in FM transmitter. + */ + public static final int DEVICE_OUT_FM = AudioSystem.DEVICE_OUT_FM; + /** @hide + * This is not used as a returned value from {@link #getDevicesForStream}, but could be * used in the future in a set method to select whatever default device is chosen by the * platform-specific implementation. */ public static final int DEVICE_OUT_DEFAULT = AudioSystem.DEVICE_OUT_DEFAULT; + /** @hide + * The audio input device code for default built-in microphone + */ + public static final int DEVICE_IN_BUILTIN_MIC = AudioSystem.DEVICE_IN_BUILTIN_MIC; + /** @hide + * The audio input device code for a Bluetooth SCO headset + */ + public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = + AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + /** @hide + * The audio input device code for wired headset microphone + */ + public static final int DEVICE_IN_WIRED_HEADSET = + AudioSystem.DEVICE_IN_WIRED_HEADSET; + /** @hide + * The audio input device code for HDMI + */ + public static final int DEVICE_IN_HDMI = + AudioSystem.DEVICE_IN_HDMI; + /** @hide + * The audio input device code for telephony voice RX path + */ + public static final int DEVICE_IN_TELEPHONY_RX = + AudioSystem.DEVICE_IN_TELEPHONY_RX; + /** @hide + * The audio input device code for built-in microphone pointing to the back + */ + public static final int DEVICE_IN_BACK_MIC = + AudioSystem.DEVICE_IN_BACK_MIC; + /** @hide + * The audio input device code for analog from a docking station + */ + public static final int DEVICE_IN_ANLG_DOCK_HEADSET = + AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET; + /** @hide + * The audio input device code for digital from a docking station + */ + public static final int DEVICE_IN_DGTL_DOCK_HEADSET = + AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET; + /** @hide + * The audio input device code for a USB audio accessory. The accessory is in USB host + * mode and the Android device in USB device mode + */ + public static final int DEVICE_IN_USB_ACCESSORY = + AudioSystem.DEVICE_IN_USB_ACCESSORY; + /** @hide + * The audio input device code for a USB audio device. The device is in USB device + * mode and the Android device in USB host mode + */ + public static final int DEVICE_IN_USB_DEVICE = + AudioSystem.DEVICE_IN_USB_DEVICE; + /** @hide + * The audio input device code for a FM radio tuner + */ + public static final int DEVICE_IN_FM_TUNER = AudioSystem.DEVICE_IN_FM_TUNER; + /** @hide + * The audio input device code for a TV tuner + */ + public static final int DEVICE_IN_TV_TUNER = AudioSystem.DEVICE_IN_TV_TUNER; + /** @hide + * The audio input device code for an analog jack with line impedance detected + */ + public static final int DEVICE_IN_LINE = AudioSystem.DEVICE_IN_LINE; + /** @hide + * The audio input device code for a S/PDIF digital connection + */ + public static final int DEVICE_IN_SPDIF = AudioSystem.DEVICE_IN_SPDIF; + + /** + * Return true if the device code corresponds to an output device. + * @hide + */ + public static boolean isOutputDevice(int device) + { + return (device & AudioSystem.DEVICE_BIT_IN) == 0; + } + + /** + * Return true if the device code corresponds to an input device. + * @hide + */ + public static boolean isInputDevice(int device) + { + return (device & AudioSystem.DEVICE_BIT_IN) == AudioSystem.DEVICE_BIT_IN; + } + + /** * Return the enabled devices for the specified output stream type. * @@ -2635,9 +2766,17 @@ public class AudioManager { * {@link #DEVICE_OUT_BLUETOOTH_A2DP}, * {@link #DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES}, * {@link #DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER}, - * {@link #DEVICE_OUT_AUX_DIGITAL}, + * {@link #DEVICE_OUT_HDMI}, * {@link #DEVICE_OUT_ANLG_DOCK_HEADSET}, * {@link #DEVICE_OUT_DGTL_DOCK_HEADSET}. + * {@link #DEVICE_OUT_USB_ACCESSORY}. + * {@link #DEVICE_OUT_USB_DEVICE}. + * {@link #DEVICE_OUT_REMOTE_SUBMIX}. + * {@link #DEVICE_OUT_TELEPHONY_TX}. + * {@link #DEVICE_OUT_LINE}. + * {@link #DEVICE_OUT_HDMI_ARC}. + * {@link #DEVICE_OUT_SPDIF}. + * {@link #DEVICE_OUT_FM}. * {@link #DEVICE_OUT_DEFAULT} is not used here. * * The implementation may support additional device codes beyond those listed, so diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index bb8cfa6..6e623a5 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -423,7 +423,7 @@ public class AudioService extends IAudioService.Stub { public final static int STREAM_REMOTE_MUSIC = -200; // Devices for which the volume is fixed and VolumePanel slider should be disabled - final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_AUX_DIGITAL | + final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_ALL_USB; @@ -2895,7 +2895,7 @@ public class AudioService extends IAudioService.Stub { public String getSettingNameForDevice(int device) { String name = mVolumeIndexSettingName; - String suffix = AudioSystem.getDeviceName(device); + String suffix = AudioSystem.getOutputDeviceName(device); if (suffix.isEmpty()) { return name; } @@ -3935,7 +3935,7 @@ public class AudioService extends IAudioService.Stub { // sent if none of these devices is connected. int mBecomingNoisyIntentDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | AudioSystem.DEVICE_OUT_ALL_USB; @@ -3992,7 +3992,7 @@ public class AudioService extends IAudioService.Stub { } else if (device == AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET) { connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; intent.setAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG); - } else if (device == AudioSystem.DEVICE_OUT_AUX_DIGITAL) { + } else if (device == AudioSystem.DEVICE_OUT_HDMI) { connType = AudioRoutesInfo.MAIN_HDMI; intent.setAction(Intent.ACTION_HDMI_AUDIO_PLUG); } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 5ddb198..0c45443 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -234,11 +234,17 @@ public class AudioSystem public static final int DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100; public static final int DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200; public static final int DEVICE_OUT_AUX_DIGITAL = 0x400; + public static final int DEVICE_OUT_HDMI = DEVICE_OUT_AUX_DIGITAL; public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800; public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000; public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000; public static final int DEVICE_OUT_USB_DEVICE = 0x4000; public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000; + public static final int DEVICE_OUT_TELEPHONY_TX = 0x10000; + public static final int DEVICE_OUT_LINE = 0x20000; + public static final int DEVICE_OUT_HDMI_ARC = 0x40000; + public static final int DEVICE_OUT_SPDIF = 0x80000; + public static final int DEVICE_OUT_FM = 0x100000; public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; @@ -252,12 +258,17 @@ public class AudioSystem DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | - DEVICE_OUT_AUX_DIGITAL | + DEVICE_OUT_HDMI | DEVICE_OUT_ANLG_DOCK_HEADSET | DEVICE_OUT_DGTL_DOCK_HEADSET | DEVICE_OUT_USB_ACCESSORY | DEVICE_OUT_USB_DEVICE | DEVICE_OUT_REMOTE_SUBMIX | + DEVICE_OUT_TELEPHONY_TX | + DEVICE_OUT_LINE | + DEVICE_OUT_HDMI_ARC | + DEVICE_OUT_SPDIF | + DEVICE_OUT_FM | DEVICE_OUT_DEFAULT); public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | @@ -275,13 +286,20 @@ public class AudioSystem public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = DEVICE_BIT_IN | 0x8; public static final int DEVICE_IN_WIRED_HEADSET = DEVICE_BIT_IN | 0x10; public static final int DEVICE_IN_AUX_DIGITAL = DEVICE_BIT_IN | 0x20; + public static final int DEVICE_IN_HDMI = DEVICE_IN_AUX_DIGITAL; public static final int DEVICE_IN_VOICE_CALL = DEVICE_BIT_IN | 0x40; + public static final int DEVICE_IN_TELEPHONY_RX = DEVICE_IN_VOICE_CALL; public static final int DEVICE_IN_BACK_MIC = DEVICE_BIT_IN | 0x80; public static final int DEVICE_IN_REMOTE_SUBMIX = DEVICE_BIT_IN | 0x100; public static final int DEVICE_IN_ANLG_DOCK_HEADSET = DEVICE_BIT_IN | 0x200; public static final int DEVICE_IN_DGTL_DOCK_HEADSET = DEVICE_BIT_IN | 0x400; public static final int DEVICE_IN_USB_ACCESSORY = DEVICE_BIT_IN | 0x800; public static final int DEVICE_IN_USB_DEVICE = DEVICE_BIT_IN | 0x1000; + public static final int DEVICE_IN_FM_TUNER = DEVICE_BIT_IN | 0x2000; + public static final int DEVICE_IN_TV_TUNER = DEVICE_BIT_IN | 0x4000; + public static final int DEVICE_IN_LINE = DEVICE_BIT_IN | 0x8000; + public static final int DEVICE_IN_SPDIF = DEVICE_BIT_IN | 0x10000; + public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT; public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | @@ -289,14 +307,18 @@ public class AudioSystem DEVICE_IN_BUILTIN_MIC | DEVICE_IN_BLUETOOTH_SCO_HEADSET | DEVICE_IN_WIRED_HEADSET | - DEVICE_IN_AUX_DIGITAL | - DEVICE_IN_VOICE_CALL | + DEVICE_IN_HDMI | + DEVICE_IN_TELEPHONY_RX | DEVICE_IN_BACK_MIC | DEVICE_IN_REMOTE_SUBMIX | DEVICE_IN_ANLG_DOCK_HEADSET | DEVICE_IN_DGTL_DOCK_HEADSET | DEVICE_IN_USB_ACCESSORY | DEVICE_IN_USB_DEVICE | + DEVICE_IN_FM_TUNER | + DEVICE_IN_TV_TUNER | + DEVICE_IN_LINE | + DEVICE_IN_SPDIF | DEVICE_IN_DEFAULT); public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET; public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY | @@ -318,13 +340,19 @@ public class AudioSystem public static final String DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME = "bt_a2dp_hp"; public static final String DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME = "bt_a2dp_spk"; public static final String DEVICE_OUT_AUX_DIGITAL_NAME = "aux_digital"; + public static final String DEVICE_OUT_HDMI_NAME = "hdmi"; public static final String DEVICE_OUT_ANLG_DOCK_HEADSET_NAME = "analog_dock"; public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory"; public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device"; public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; + public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx"; + public static final String DEVICE_OUT_LINE_NAME = "line"; + public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; + public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; + public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; - public static String getDeviceName(int device) + public static String getOutputDeviceName(int device) { switch(device) { case DEVICE_OUT_EARPIECE: @@ -347,8 +375,8 @@ public class AudioSystem return DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES_NAME; case DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: return DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER_NAME; - case DEVICE_OUT_AUX_DIGITAL: - return DEVICE_OUT_AUX_DIGITAL_NAME; + case DEVICE_OUT_HDMI: + return DEVICE_OUT_HDMI_NAME; case DEVICE_OUT_ANLG_DOCK_HEADSET: return DEVICE_OUT_ANLG_DOCK_HEADSET_NAME; case DEVICE_OUT_DGTL_DOCK_HEADSET: @@ -359,12 +387,23 @@ public class AudioSystem return DEVICE_OUT_USB_DEVICE_NAME; case DEVICE_OUT_REMOTE_SUBMIX: return DEVICE_OUT_REMOTE_SUBMIX_NAME; + case DEVICE_OUT_TELEPHONY_TX: + return DEVICE_OUT_TELEPHONY_TX_NAME; + case DEVICE_OUT_LINE: + return DEVICE_OUT_LINE_NAME; + case DEVICE_OUT_HDMI_ARC: + return DEVICE_OUT_HDMI_ARC_NAME; + case DEVICE_OUT_SPDIF: + return DEVICE_OUT_SPDIF_NAME; + case DEVICE_OUT_FM: + return DEVICE_OUT_FM_NAME; case DEVICE_OUT_DEFAULT: default: return ""; } } + // phone state, match audio_mode??? public static final int PHONE_STATE_OFFCALL = 0; public static final int PHONE_STATE_RINGING = 1; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 34c5520..c7b3fc9 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -212,6 +212,7 @@ final public class MediaCodec { * <li>"video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm) * <li>"video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm) * <li>"video/avc" - H.264/AVC video + * <li>"video/hevc" - H.265/HEVC video * <li>"video/mp4v-es" - MPEG4 video * <li>"video/3gpp" - H.263 video * <li>"audio/3gpp" - AMR narrowband audio diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 90c12c6..b5d0a57 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -264,6 +264,37 @@ public final class MediaCodecInfo { // from OMX_VIDEO_VP8PROFILETYPE public static final int VP8ProfileMain = 0x01; + // from OMX_VIDEO_HEVCPROFILETYPE + public static final int HEVCProfileMain = 0x01; + public static final int HEVCProfileMain10 = 0x02; + + // from OMX_VIDEO_HEVCLEVELTYPE + public static final int HEVCMainTierLevel1 = 0x1; + public static final int HEVCHighTierLevel1 = 0x2; + public static final int HEVCMainTierLevel2 = 0x4; + public static final int HEVCHighTierLevel2 = 0x8; + public static final int HEVCMainTierLevel21 = 0x10; + public static final int HEVCHighTierLevel21 = 0x20; + public static final int HEVCMainTierLevel3 = 0x40; + public static final int HEVCHighTierLevel3 = 0x80; + public static final int HEVCMainTierLevel31 = 0x100; + public static final int HEVCHighTierLevel31 = 0x200; + public static final int HEVCMainTierLevel4 = 0x400; + public static final int HEVCHighTierLevel4 = 0x800; + public static final int HEVCMainTierLevel41 = 0x1000; + public static final int HEVCHighTierLevel41 = 0x2000; + public static final int HEVCMainTierLevel5 = 0x4000; + public static final int HEVCHighTierLevel5 = 0x8000; + public static final int HEVCMainTierLevel51 = 0x10000; + public static final int HEVCHighTierLevel51 = 0x20000; + public static final int HEVCMainTierLevel52 = 0x40000; + public static final int HEVCHighTierLevel52 = 0x80000; + public static final int HEVCMainTierLevel6 = 0x100000; + public static final int HEVCHighTierLevel6 = 0x200000; + public static final int HEVCMainTierLevel61 = 0x400000; + public static final int HEVCHighTierLevel61 = 0x800000; + public static final int HEVCMainTierLevel62 = 0x1000000; + public static final int HEVCHighTierLevel62 = 0x2000000; /** * Defined in the OpenMAX IL specs, depending on the type of media diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 37f45c2..26ae3cc 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -407,6 +407,19 @@ public class RemoteControlClient } /** + * Get a {@link MediaSession} associated with this RCC. It will only have a + * session while it is registered with + * {@link AudioManager#registerRemoteControlClient}. The session returned + * should not be modified directly by the application but may be used with + * other APIs that require a session. + * + * @return A media session object or null. + */ + public MediaSession getMediaSession() { + return mSession; + } + + /** * Class used to modify metadata in a {@link RemoteControlClient} object. * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, * on which you set the metadata for the RemoteControlClient instance. Once all the information diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp index 84028b7..d21b442 100644 --- a/media/jni/android_media_MediaScanner.cpp +++ b/media/jni/android_media_MediaScanner.cpp @@ -351,7 +351,7 @@ android_media_MediaScanner_extractAlbumArt( if (!data) { return NULL; } - long len = *((long*)data); + jsize len = *((uint32_t*)data); jbyteArray array = env->NewByteArray(len); if (array != NULL) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 2d17b7b..aae92e8 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -149,14 +149,16 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout if (mLockPatternUtils.checkPassword(entry)) { mCallback.reportUnlockAttempt(true); mCallback.dismiss(true); - } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { - // to avoid accidental lockout, only count attempts that are long enough to be a - // real password. This may require some tweaking. - mCallback.reportUnlockAttempt(false); - int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); - if (0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); - handleAttemptLockout(deadline); + } else { + if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + mCallback.reportUnlockAttempt(false); + int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); + if (0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } } mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 909c32e..286921e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -70,7 +70,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 102; + private static final int DATABASE_VERSION = 103; private Context mContext; private int mUserHandle; @@ -1632,6 +1632,34 @@ public class DatabaseHelper extends SQLiteOpenHelper { upgradeVersion = 102; } + if (upgradeVersion == 102) { + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + // The INSTALL_NON_MARKET_APPS setting is becoming per-user rather + // than device-global. + if (mUserHandle == UserHandle.USER_OWNER) { + // In the owner user, the global table exists so we can migrate the + // entry from there to the secure table, preserving its value. + String[] globalToSecure = { + Settings.Secure.INSTALL_NON_MARKET_APPS + }; + moveSettingsToNewTable(db, TABLE_GLOBAL, TABLE_SECURE, globalToSecure, true); + } else { + // Secondary users' dbs don't have the global table, so institute the + // default. + stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)" + + " VALUES(?,?);"); + loadBooleanSetting(stmt, Settings.Secure.INSTALL_NON_MARKET_APPS, + R.bool.def_install_non_market_apps); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) stmt.close(); + } + upgradeVersion = 103; + } // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -2191,6 +2219,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadStringSetting(stmt, Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, R.string.def_immersive_mode_confirmations); + loadBooleanSetting(stmt, Settings.Secure.INSTALL_NON_MARKET_APPS, + R.bool.def_install_non_market_apps); + } finally { if (stmt != null) stmt.close(); } @@ -2289,9 +2320,6 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Global.NETSTATS_ENABLED, R.bool.def_netstats_enabled); - loadBooleanSetting(stmt, Settings.Global.INSTALL_NON_MARKET_APPS, - R.bool.def_install_non_market_apps); - loadBooleanSetting(stmt, Settings.Global.USB_MASS_STORAGE_ENABLED, R.bool.def_usb_mass_storage_enabled); diff --git a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml index 744795e..9c1165d 100644 --- a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml +++ b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml @@ -13,8 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:versionCode="1" > +<vector xmlns:android="http://schemas.android.com/apk/res/android"> <size android:height="16dp" diff --git a/packages/SystemUI/res/drawable/recents_dismiss_light.xml b/packages/SystemUI/res/drawable/recents_dismiss_light.xml index 96bfbe1..a8afeb3 100644 --- a/packages/SystemUI/res/drawable/recents_dismiss_light.xml +++ b/packages/SystemUI/res/drawable/recents_dismiss_light.xml @@ -13,8 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:versionCode="1" > +<vector xmlns:android="http://schemas.android.com/apk/res/android"> <size android:height="16dp" diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index b24d4ad..85de645 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -23,6 +23,7 @@ android:background="@drawable/qs_panel_background" > <com.android.systemui.qs.QSPanel android:id="@+id/quick_settings_panel" + android:background="#0000" android:layout_width="match_parent" android:layout_height="wrap_content" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index a68ad2f..97051ff 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -22,21 +22,6 @@ android:id="@+id/task_view_thumbnail" android:layout_width="match_parent" android:layout_height="match_parent" /> - <com.android.systemui.recents.views.TaskInfoView - android:id="@+id/task_view_info_pane" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="invisible" - android:background="@color/recents_task_bar_default_background_color"> - <Button - android:id="@+id/task_view_app_info_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="20dp" - android:layout_marginEnd="20dp" - android:layout_gravity="top|center_horizontal" - android:text="@string/recents_app_info_button_label" /> - </com.android.systemui.recents.views.TaskInfoView> <com.android.systemui.recents.views.TaskBarView android:id="@+id/task_view_bar" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 2837cd6..5fabd3e 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -27,7 +27,7 @@ <Button android:id="@+id/veto" android:layout_width="48dp" - android:layout_height="match_parent" + android:layout_height="0dp" android:gravity="end" android:layout_marginEnd="-80dp" android:background="@null" diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 79545b3..fcbd0f4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -31,8 +31,6 @@ public class Constants { public static final boolean EnableTaskStackClipping = false; // Enables the use of theme colors as the task bar background public static final boolean EnableTaskBarThemeColors = true; - // Enables the info pane on long-pressing the task - public static final boolean EnableInfoPane = false; // Enables app-info pane on long-pressing the icon public static final boolean EnableDevAppInfoOnLongPress = true; // Enables the search bar layout diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index bae8a99..de696db 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -394,16 +394,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView public void onBackPressed() { boolean interceptedByInfoPanelClose = false; - // Try and return from any open info panes - if (Constants.DebugFlags.App.EnableInfoPane) { - interceptedByInfoPanelClose = mRecentsView.closeOpenInfoPanes(); - } - - // If we haven't been intercepted already, then unfilter any stacks - if (!interceptedByInfoPanelClose) { - if (!mRecentsView.unfilterFilteredStacks()) { - super.onBackPressed(); - } + // Unfilter any stacks + if (!mRecentsView.unfilterFilteredStacks()) { + super.onBackPressed(); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index c63e688..cad54fa 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -301,24 +301,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return insets.consumeSystemWindowInsets(false, false, false, true); } - /** Closes any open info panes */ - public boolean closeOpenInfoPanes() { - if (mBSP != null) { - // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child instanceof TaskStackView) { - TaskStackView stackView = (TaskStackView) child; - if (stackView.closeOpenInfoPanes()) { - return true; - } - } - } - } - return false; - } - /** Unfilters any filtered stacks */ public boolean unfilterFilteredStacks() { if (mBSP != null) { @@ -346,9 +328,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mCb.onTaskLaunching(); } - // Close any open info panes - closeOpenInfoPanes(); - final Runnable launchRunnable = new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java deleted file mode 100644 index f1c362a..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.TouchFeedbackDrawable; -import android.util.AttributeSet; -import android.widget.Button; -import android.widget.FrameLayout; -import com.android.systemui.R; -import com.android.systemui.recents.Constants; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.Utilities; -import com.android.systemui.recents.model.Task; - - -/* The task info view */ -class TaskInfoView extends FrameLayout { - - Button mAppInfoButton; - - // Circular clip animation - boolean mCircularClipEnabled; - Path mClipPath = new Path(); - float mClipRadius; - float mMaxClipRadius; - Point mClipOrigin = new Point(); - ObjectAnimator mCircularClipAnimator; - - public TaskInfoView(Context context) { - this(context, null); - } - - public TaskInfoView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - // Initialize the buttons on the info panel - mAppInfoButton = (Button) findViewById(R.id.task_view_app_info_button); - } - - /** Updates the positions of each of the items to fit in the rect specified */ - void updateContents(Rect visibleRect) { - // Offset the app info button - mAppInfoButton.setTranslationY(visibleRect.top + - (visibleRect.height() - mAppInfoButton.getMeasuredHeight()) / 2); - } - - /** Sets the circular clip radius on this panel */ - public void setCircularClipRadius(float r) { - mClipRadius = r; - invalidate(); - } - - /** Gets the circular clip radius on this panel */ - public float getCircularClipRadius() { - return mClipRadius; - } - - /** Animates the circular clip radius on the icon */ - void animateCircularClip(Point o, float fromRadius, float toRadius, - final Runnable postRunnable, boolean animateInContent) { - if (mCircularClipAnimator != null) { - mCircularClipAnimator.cancel(); - } - - // Calculate the max clip radius to each of the corners - int w = getMeasuredWidth() - o.x; - int h = getMeasuredHeight() - o.y; - // origin to tl, tr, br, bl - mMaxClipRadius = (int) Math.ceil(Math.sqrt(o.x * o.x + o.y * o.y)); - mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + o.y * o.y))); - mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + h * h))); - mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(o.x * o.x + h * h))); - - mClipOrigin.set(o.x, o.y); - mClipRadius = fromRadius; - int duration = Utilities.calculateTranslationAnimationDuration((int) mMaxClipRadius); - mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", toRadius); - mCircularClipAnimator.setDuration(duration); - mCircularClipAnimator.setInterpolator( - RecentsConfiguration.getInstance().defaultBezierInterpolator); - mCircularClipAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCircularClipEnabled = false; - if (postRunnable != null) { - postRunnable.run(); - } - } - }); - mCircularClipAnimator.start(); - mCircularClipEnabled = true; - - if (animateInContent) { - animateAppInfoButtonIn(duration); - } - } - - /** Cancels the circular clip animation. */ - void cancelCircularClipAnimation() { - if (mCircularClipAnimator != null) { - mCircularClipAnimator.cancel(); - } - } - - void animateAppInfoButtonIn(int duration) { - mAppInfoButton.setScaleX(0.75f); - mAppInfoButton.setScaleY(0.75f); - mAppInfoButton.animate() - .scaleX(1f) - .scaleY(1f) - .setDuration(duration) - .setInterpolator(RecentsConfiguration.getInstance().defaultBezierInterpolator) - .withLayer() - .start(); - } - - /** Binds the info view to the task */ - void rebindToTask(Task t, boolean animate) { - RecentsConfiguration configuration = RecentsConfiguration.getInstance(); - if (Constants.DebugFlags.App.EnableTaskBarThemeColors && t.colorPrimary != 0) { - setBackgroundColor(t.colorPrimary); - // Workaround: The button currently doesn't support setting a custom background tint - // not defined in the theme. Just lower the alpha on the button to make it blend more - // into the background. - if (mAppInfoButton.getBackground() instanceof TouchFeedbackDrawable) { - TouchFeedbackDrawable d = (TouchFeedbackDrawable) mAppInfoButton.getBackground(); - if (d != null) { - d.setAlpha(96); - } - } - } else { - setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor); - } - } - - @Override - public void draw(Canvas canvas) { - int saveCount = 0; - if (mCircularClipEnabled) { - saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); - mClipPath.reset(); - mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * mMaxClipRadius, - Path.Direction.CW); - canvas.clipPath(mClipPath); - } - super.draw(canvas); - if (mCircularClipEnabled) { - canvas.restoreToCount(saveCount); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 37c3c35..0687222 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -54,7 +54,7 @@ import java.util.Set; /* The visual representation of a task stack view */ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, - View.OnClickListener, View.OnLongClickListener, RecentsPackageMonitor.PackageCallbacks { + View.OnClickListener, RecentsPackageMonitor.PackageCallbacks { /** The TaskView callbacks */ interface TaskStackViewCallbacks { @@ -79,7 +79,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int mMinScroll; int mMaxScroll; int mStashedScroll; - int mLastInfoPaneStackScroll; int mFocusedTaskIndex = -1; OverScroller mScroller; ObjectAnimator mScrollAnimator; @@ -290,17 +289,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void setStackScroll(int value) { mStackScroll = value; requestSynchronizeStackViewsWithModel(); - - // Close any open info panes if the user has scrolled away from them - boolean isAnimatingScroll = (mScrollAnimator != null && mScrollAnimator.isRunning()); - if (mLastInfoPaneStackScroll > -1 && !isAnimatingScroll) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (Math.abs(mStackScroll - mLastInfoPaneStackScroll) > - config.taskStackScrollDismissInfoPaneDistance) { - // Close any open info panes - closeOpenInfoPanes(); - } - } } /** Sets the current stack scroll without synchronizing the stack view with the model */ public void setStackScrollRaw(int value) { @@ -455,21 +443,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - /** Closes any open info panes. */ - boolean closeOpenInfoPanes() { - if (!Constants.DebugFlags.App.EnableInfoPane) return false; - - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); - if (tv.isInfoPaneVisible()) { - tv.hideInfoPane(); - return true; - } - } - return false; - } - /** Focuses the task at the specified index in the stack */ void focusTask(int taskIndex, boolean scrollToNewPosition) { Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex); @@ -949,9 +922,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, Task filteredTask) { - // Close any open info panes - closeOpenInfoPanes(); - // Stash the scroll and filtered task for us to restore to when we unfilter mStashedScroll = getStackScroll(); @@ -976,9 +946,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { - // Close any open info panes - closeOpenInfoPanes(); - // Calculate the current task transforms final ArrayList<TaskViewTransform> curTaskTransforms = getStackTransforms(curTasks, getStackScroll(), null, true); @@ -1058,9 +1025,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Set the callbacks and listeners for this new view tv.setOnClickListener(this); - if (Constants.DebugFlags.App.EnableInfoPane) { - tv.setOnLongClickListener(this); - } tv.setCallbacks(this); } else { attachViewToParent(tv, insertIndex, tv.getLayoutParams()); @@ -1094,17 +1058,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override - public void onTaskInfoPanelShown(TaskView tv) { - // Do nothing - } - - @Override - public void onTaskInfoPanelHidden(TaskView tv) { - // Unset the saved scroll - mLastInfoPaneStackScroll = -1; - } - - @Override public void onTaskAppInfoClicked(TaskView tv) { if (mCb != null) { mCb.onTaskAppInfoLaunched(tv.getTask()); @@ -1129,52 +1082,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", task + " cb: " + mCb); - // Close any open info panes if the user taps on another task - if (closeOpenInfoPanes()) { - return; - } - if (mCb != null) { mCb.onTaskLaunched(this, tv, mStack, task); } } - @Override - public boolean onLongClick(View v) { - if (!Constants.DebugFlags.App.EnableInfoPane) return false; - - TaskView tv = (TaskView) v; - - // Close any other task info panels if we launch another info pane - closeOpenInfoPanes(); - - // Scroll the task view so that it is maximally visible - float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); - int taskIndex = mStack.indexOfTask(tv.getTask()); - int curScroll = getStackScroll(); - int newScroll = (int) Math.max(mMinScroll, Math.min(mMaxScroll, taskIndex * overlapHeight)); - TaskViewTransform transform = getStackTransform(taskIndex, curScroll); - Rect nonOverlapRect = new Rect(transform.rect); - if (taskIndex < (mStack.getTaskCount() - 1)) { - nonOverlapRect.bottom = nonOverlapRect.top + (int) overlapHeight; - } - - // XXX: Use HW Layers - if (transform.t < 0f) { - animateScroll(curScroll, newScroll, null); - } else if (nonOverlapRect.bottom > mStackRectSansPeek.bottom) { - // Check if we are out of bounds, if so, just scroll it in such that the bottom of the - // task view is visible - newScroll = curScroll - (mStackRectSansPeek.bottom - nonOverlapRect.bottom); - animateScroll(curScroll, newScroll, null); - } - mLastInfoPaneStackScroll = newScroll; - - // Show the info pane for this task view - tv.showInfoPane(new Rect(0, 0, 0, (int) overlapHeight)); - return true; - } - /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ @Override @@ -1550,13 +1462,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } - // If the info panel is currently showing on this view, then we need to dismiss it - if (Constants.DebugFlags.App.EnableInfoPane) { - TaskView tv = (TaskView) v; - if (tv.isInfoPaneVisible()) { - tv.hideInfoPane(); - } - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 46af4c1..780f274 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -44,8 +44,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On /** The TaskView callbacks */ interface TaskViewCallbacks { public void onTaskIconClicked(TaskView tv); - public void onTaskInfoPanelShown(TaskView tv); - public void onTaskInfoPanelHidden(TaskView tv); public void onTaskAppInfoClicked(TaskView tv); public void onTaskDismissed(TaskView tv); @@ -58,14 +56,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On Task mTask; boolean mTaskDataLoaded; - boolean mTaskInfoPaneVisible; boolean mIsFocused; Point mLastTouchDown = new Point(); Path mRoundedRectClipPath = new Path(); TaskThumbnailView mThumbnailView; TaskBarView mBarView; - TaskInfoView mInfoView; TaskViewCallbacks mCb; @@ -94,7 +90,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On // Bind the views mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail); mBarView = (TaskBarView) findViewById(R.id.task_view_bar); - mInfoView = (TaskInfoView) findViewById(R.id.task_view_info_pane); if (mTaskDataLoaded) { onTaskDataLoaded(false); @@ -280,63 +275,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On return outRect; } - /** Returns whether this task has an info pane visible */ - boolean isInfoPaneVisible() { - return mTaskInfoPaneVisible; - } - - /** Shows the info pane if it is not visible. */ - void showInfoPane(Rect taskVisibleRect) { - if (mTaskInfoPaneVisible) return; - - // Remove the bar view from the visible rect and update the info pane contents - taskVisibleRect.top += mBarView.getMeasuredHeight(); - mInfoView.updateContents(taskVisibleRect); - - // Show the info pane and animate it into view - mInfoView.setVisibility(View.VISIBLE); - mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true); - mInfoView.setOnClickListener(this); - mTaskInfoPaneVisible = true; - - // Notify any callbacks - if (mCb != null) { - mCb.onTaskInfoPanelShown(this); - } - } - - /** Hides the info pane if it is visible. */ - void hideInfoPane() { - if (!mTaskInfoPaneVisible) return; - RecentsConfiguration config = RecentsConfiguration.getInstance(); - - // Cancel any circular clip animation - mInfoView.cancelCircularClipAnimation(); - - // Animate the info pane out - mInfoView.animate() - .alpha(0f) - .setDuration(config.taskViewInfoPaneAnimDuration) - .setInterpolator(config.defaultBezierInterpolator) - .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - mInfoView.setVisibility(View.INVISIBLE); - mInfoView.setOnClickListener(null); - - mInfoView.setAlpha(1f); - } - }) - .start(); - mTaskInfoPaneVisible = false; - - // Notify any callbacks - if (mCb != null) { - mCb.onTaskInfoPanelHidden(this); - } - } - /** Enable the hw layers on this task view */ void enableHwLayers() { mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); @@ -408,11 +346,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On @Override public void onTaskDataLoaded(boolean reloadingTaskData) { - if (mThumbnailView != null && mBarView != null && mInfoView != null) { + if (mThumbnailView != null && mBarView != null) { // Bind each of the views to the new task data mThumbnailView.rebindToTask(mTask, reloadingTaskData); mBarView.rebindToTask(mTask, reloadingTaskData); - mInfoView.rebindToTask(mTask, reloadingTaskData); // Rebind any listeners mBarView.mApplicationIcon.setOnClickListener(this); mBarView.mDismissButton.setOnClickListener(this); @@ -424,16 +361,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On mBarView.mApplicationIcon.setOnLongClickListener(this); } } - if (Constants.DebugFlags.App.EnableInfoPane) { - mInfoView.mAppInfoButton.setOnClickListener(this); - } } mTaskDataLoaded = true; } @Override public void onTaskDataUnloaded() { - if (mThumbnailView != null && mBarView != null && mInfoView != null) { + if (mThumbnailView != null && mBarView != null) { // Unbind each of the views from the task data and remove the task callback mTask.setCallbacks(null); mThumbnailView.unbindFromTask(); @@ -444,18 +378,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { mBarView.mApplicationIcon.setOnLongClickListener(null); } - if (Constants.DebugFlags.App.EnableInfoPane) { - mInfoView.mAppInfoButton.setOnClickListener(null); - } } mTaskDataLoaded = false; } @Override public void onClick(View v) { - if (v == mInfoView) { - hideInfoPane(); - } else if (v == mBarView.mApplicationIcon) { + if (v == mBarView.mApplicationIcon) { mCb.onTaskIconClicked(this); } else if (v == mBarView.mDismissButton) { // Animate out the view and call the callback @@ -466,8 +395,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On mCb.onTaskDismissed(tv); } }); - } else if (v == mInfoView.mAppInfoButton) { - mCb.onTaskAppInfoClicked(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 724b6a4..ac16164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -89,7 +89,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView super.onFinishInflate(); mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); - updateBackgroundResource(); + updateBackgroundResources(); } private final Runnable mTapTimeoutRunnable = new Runnable() { @@ -215,9 +215,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mDimmed != dimmed) { mDimmed = dimmed; if (fade) { - fadeBackgroundResource(); + fadeBackground(); } else { - updateBackgroundResource(); + updateBackground(); } } } @@ -233,14 +233,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBgTint = bgTint; mDimmedBgResId = dimmedBgResId; mDimmedBgTint = dimmedTint; - updateBackgroundResource(); + updateBackgroundResources(); } public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) { setBackgroundResourceIds(bgResId, 0, dimmedBgResId, 0); } - private void fadeBackgroundResource() { + private void fadeBackground() { if (mDimmed) { mBackgroundDimmed.setVisibility(View.VISIBLE); } else { @@ -256,7 +256,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundAnimator.removeAllListeners(); mBackgroundAnimator.cancel(); if (duration <= 0) { - updateBackgroundResource(); + updateBackground(); return; } } @@ -279,19 +279,22 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundAnimator.start(); } - private void updateBackgroundResource() { + private void updateBackground() { if (mDimmed) { mBackgroundDimmed.setVisibility(View.VISIBLE); - mBackgroundDimmed.setCustomBackground(mDimmedBgResId, mDimmedBgTint); mBackgroundNormal.setVisibility(View.INVISIBLE); } else { mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); - mBackgroundNormal.setCustomBackground(mBgResId, mBgTint); mBackgroundNormal.setAlpha(1f); } } + private void updateBackgroundResources() { + mBackgroundDimmed.setCustomBackground(mDimmedBgResId, mDimmedBgTint); + mBackgroundNormal.setCustomBackground(mBgResId, mBgTint); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index eb4e77a..e699dd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -619,7 +619,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected void hideRecents(boolean triggeredFromAltTab) { if (mRecents != null) { - sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); mRecents.hideRecents(triggeredFromAltTab); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index f6c80fc..84005d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -53,6 +53,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; private boolean mIsBelowSpeedBump; + private View mVetoButton; public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); @@ -63,6 +64,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.onFinishInflate(); mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); + mVetoButton = findViewById(R.id.veto); } @Override @@ -224,7 +226,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public void setActualHeight(int height, boolean notifyListeners) { - mPrivateLayout.setActualHeight(height, notifyListeners); + mPrivateLayout.setActualHeight(height); invalidate(); super.setActualHeight(height, notifyListeners); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 061396d..eaaac10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -20,29 +20,78 @@ import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; +import com.android.systemui.R; + +import java.util.ArrayList; /** * An abstract view for expandable views. */ public abstract class ExpandableView extends FrameLayout { + private final int mMaxNotificationHeight; + private OnHeightChangedListener mOnHeightChangedListener; protected int mActualHeight; protected int mClipTopAmount; private boolean mActualHeightInitialized; + private ArrayList<View> mMatchParentViews = new ArrayList<View>(); public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); + mMaxNotificationHeight = getResources().getDimensionPixelSize( + R.dimen.notification_max_height); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ownMaxHeight = mMaxNotificationHeight; + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + if (hasFixedHeight || isHeightLimited) { + int size = MeasureSpec.getSize(heightMeasureSpec); + ownMaxHeight = Math.min(ownMaxHeight, size); + } + int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); + int maxChildHeight = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + int childHeightSpec = newHeightSpec; + ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); + if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) { + if (layoutParams.height >= 0) { + // An actual height is set + childHeightSpec = layoutParams.height > ownMaxHeight + ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY) + : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); + } + child.measure(widthMeasureSpec, childHeightSpec); + int childHeight = child.getMeasuredHeight(); + maxChildHeight = Math.max(maxChildHeight, childHeight); + } else { + mMatchParentViews.add(child); + } + } + int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight; + newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY); + for (View child : mMatchParentViews) { + child.measure(widthMeasureSpec, newHeightSpec); + } + mMatchParentViews.clear(); + int width = MeasureSpec.getSize(widthMeasureSpec); + setMeasuredDimension(width, ownHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (!mActualHeightInitialized && mActualHeight == 0) { - mActualHeight = getInitialHeight(); + setActualHeight(getInitialHeight()); } - mActualHeightInitialized = true; } protected int getInitialHeight() { @@ -77,6 +126,7 @@ public abstract class ExpandableView extends FrameLayout { } public void setActualHeight(int actualHeight) { + mActualHeightInitialized = true; setActualHeight(actualHeight, true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index e49ec64..3c080fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -31,7 +31,6 @@ public class NotificationBackgroundView extends View { private Drawable mBackground; private int mClipTopAmount; private int mActualHeight; - private boolean mActualHeightInitialized; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); @@ -39,15 +38,6 @@ public class NotificationBackgroundView extends View { } @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (!mActualHeightInitialized && mActualHeight == 0) { - mActualHeight = getHeight(); - } - mActualHeightInitialized = true; - } - - @Override protected void onDraw(Canvas canvas) { draw(canvas, mBackground); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 9df2701..f9baecb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -22,6 +22,7 @@ import android.util.AttributeSet; import android.view.Gravity; import android.view.View; +import android.widget.FrameLayout; import com.android.systemui.R; /** @@ -29,7 +30,7 @@ import com.android.systemui.R; * expanded layout. This class is responsible for clipping the content and and switching between the * expanded and contracted view depending on its clipped size. */ -public class NotificationContentView extends ExpandableView { +public class NotificationContentView extends FrameLayout { private final Rect mClipBounds = new Rect(); @@ -37,6 +38,8 @@ public class NotificationContentView extends ExpandableView { private View mExpandedChild; private int mSmallHeight; + private int mClipTopAmount; + private int mActualHeight; public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); @@ -69,28 +72,24 @@ public class NotificationContentView extends ExpandableView { selectLayout(); } - @Override - public void setActualHeight(int actualHeight, boolean notifyListeners) { - super.setActualHeight(actualHeight, notifyListeners); + public void setActualHeight(int actualHeight) { + mActualHeight = actualHeight; selectLayout(); updateClipping(); } - @Override public int getMaxHeight() { // The maximum height is just the laid out height. return getHeight(); } - @Override public int getMinHeight() { return mSmallHeight; } - @Override public void setClipTopAmount(int clipTopAmount) { - super.setClipTopAmount(clipTopAmount); + mClipTopAmount = clipTopAmount; updateClipping(); } @@ -127,7 +126,6 @@ public class NotificationContentView extends ExpandableView { selectLayout(); } - @Override public boolean isContentExpandable() { return mExpandedChild != null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 2bc6f9c..f5d4889 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -167,7 +168,7 @@ public class SignalClusterView } private void applyInetProblem(ImageView iv) { - iv.setColorFilter(mInetProblem ? PROBLEM_FILTER : null); + iv.setColorFilter(Build.IS_DEBUGGABLE && mInetProblem ? PROBLEM_FILTER : null); } // Run after each indicator change. diff --git a/policy/src/com/android/internal/policy/impl/BarController.java b/policy/src/com/android/internal/policy/impl/BarController.java index 49e1072..bfbd60d 100644 --- a/policy/src/com/android/internal/policy/impl/BarController.java +++ b/policy/src/com/android/internal/policy/impl/BarController.java @@ -121,7 +121,7 @@ public class BarController { } } else { vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag); - vis = (vis & View.SYSTEM_UI_TRANSPARENT) | (oldVis & View.SYSTEM_UI_TRANSPARENT); + vis = (vis & ~View.SYSTEM_UI_TRANSPARENT) | (oldVis & View.SYSTEM_UI_TRANSPARENT); } } return vis; diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 168742f..4ab8a01 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -22,7 +22,6 @@ import android.app.AlarmManager; import android.app.IAlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -40,7 +39,6 @@ import android.os.UserHandle; import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -104,13 +102,21 @@ class AlarmManagerService extends SystemService { int mBroadcastRefCount = 0; PowerManager.WakeLock mWakeLock; boolean mLastWakeLockUnimportantForLogging; + ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<Alarm>(); ArrayList<InFlight> mInFlight = new ArrayList<InFlight>(); final AlarmHandler mHandler = new AlarmHandler(); ClockReceiver mClockReceiver; + InteractiveStateReceiver mInteractiveStateReceiver; private UninstallReceiver mUninstallReceiver; final ResultReceiver mResultReceiver = new ResultReceiver(); PendingIntent mTimeTickSender; PendingIntent mDateChangeSender; + boolean mInteractive = true; + long mNonInteractiveStartTime; + long mNonInteractiveTime; + long mLastAlarmDeliveryTime; + long mStartCurrentDelayTime; + long mNextNonWakeupDeliveryTime; class WakeupEvent { public long when; @@ -318,7 +324,11 @@ class AlarmManagerService extends SystemService { final Comparator<Alarm> mAlarmDispatchComparator = new Comparator<Alarm>() { @Override public int compare(Alarm lhs, Alarm rhs) { - if (lhs.wakeup != rhs.wakeup) { + if ((!lhs.operation.getCreatorPackage().equals(rhs.operation.getCreatorPackage())) + && lhs.wakeup != rhs.wakeup) { + // We want to put wakeup alarms before non-wakeup alarms, since they are + // the things that drive most activity in the alarm manager. However, + // alarms from the same package should always be ordered strictly by time. return lhs.wakeup ? -1 : 1; } if (lhs.whenElapsed < rhs.whenElapsed) { @@ -424,24 +434,21 @@ class AlarmManagerService extends SystemService { static final class InFlight extends Intent { final PendingIntent mPendingIntent; final WorkSource mWorkSource; - final Pair<String, ComponentName> mTarget; + final String mTag; final BroadcastStats mBroadcastStats; final FilterStats mFilterStats; final int mAlarmType; InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource, - int alarmType) { + int alarmType, String tag) { mPendingIntent = pendingIntent; mWorkSource = workSource; - Intent intent = pendingIntent.getIntent(); - mTarget = intent != null - ? new Pair<String, ComponentName>(intent.getAction(), intent.getComponent()) - : null; + mTag = tag; mBroadcastStats = service.getStatsLocked(pendingIntent); - FilterStats fs = mBroadcastStats.filterStats.get(mTarget); + FilterStats fs = mBroadcastStats.filterStats.get(mTag); if (fs == null) { - fs = new FilterStats(mBroadcastStats, mTarget); - mBroadcastStats.filterStats.put(mTarget, fs); + fs = new FilterStats(mBroadcastStats, mTag); + mBroadcastStats.filterStats.put(mTag, fs); } mFilterStats = fs; mAlarmType = alarmType; @@ -450,7 +457,7 @@ class AlarmManagerService extends SystemService { static final class FilterStats { final BroadcastStats mBroadcastStats; - final Pair<String, ComponentName> mTarget; + final String mTag; long aggregateTime; int count; @@ -458,9 +465,9 @@ class AlarmManagerService extends SystemService { long startTime; int nesting; - FilterStats(BroadcastStats broadcastStats, Pair<String, ComponentName> target) { + FilterStats(BroadcastStats broadcastStats, String tag) { mBroadcastStats = broadcastStats; - mTarget = target; + mTag = tag; } } @@ -473,8 +480,7 @@ class AlarmManagerService extends SystemService { int numWakeup; long startTime; int nesting; - final ArrayMap<Pair<String, ComponentName>, FilterStats> filterStats - = new ArrayMap<Pair<String, ComponentName>, FilterStats>(); + final ArrayMap<String, FilterStats> filterStats = new ArrayMap<String, FilterStats>(); BroadcastStats(int uid, String packageName) { mUid = uid; @@ -484,7 +490,11 @@ class AlarmManagerService extends SystemService { final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats = new SparseArray<ArrayMap<String, BroadcastStats>>(); - + + int mNumDelayedAlarms = 0; + long mTotalDelayTime = 0; + long mMaxDelayTime = 0; + @Override public void onStart() { mNativeData = init(); @@ -511,6 +521,7 @@ class AlarmManagerService extends SystemService { mClockReceiver = new ClockReceiver(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); + mInteractiveStateReceiver = new InteractiveStateReceiver(); mUninstallReceiver = new UninstallReceiver(); if (mNativeData != 0) { @@ -735,13 +746,29 @@ class AlarmManagerService extends SystemService { pw.print("nowRTC="); pw.print(nowRTC); pw.print("="); pw.print(sdf.format(new Date(nowRTC))); - pw.print(" nowELAPSED="); pw.println(nowELAPSED); + pw.print(" nowELAPSED="); TimeUtils.formatDuration(nowELAPSED, pw); + pw.println(); + if (!mInteractive) { + pw.print("Time since non-interactive: "); + TimeUtils.formatDuration(nowELAPSED - mNonInteractiveStartTime, pw); + pw.println(); + pw.print("Max wakeup delay: "); + TimeUtils.formatDuration(currentNonWakeupFuzzLocked(nowELAPSED), pw); + pw.println(); + pw.print("Time since last dispatch: "); + TimeUtils.formatDuration(nowELAPSED - mLastAlarmDeliveryTime, pw); + pw.println(); + pw.print("Next non-wakeup delivery time: "); + TimeUtils.formatDuration(nowELAPSED - mNextNonWakeupDeliveryTime, pw); + pw.println(); + } long nextWakeupRTC = mNextWakeup + (nowRTC - nowELAPSED); long nextNonWakeupRTC = mNextNonWakeup + (nowRTC - nowELAPSED); - pw.print("Next alarm: "); pw.print(mNextNonWakeup); + pw.print("Next non-wakeup alarm: "); + TimeUtils.formatDuration(mNextNonWakeup, nowELAPSED, pw); pw.print(" = "); pw.println(sdf.format(new Date(nextNonWakeupRTC))); - pw.print("Next wakeup: "); pw.print(mNextWakeup); + pw.print("Next wakeup: "); TimeUtils.formatDuration(mNextWakeup, nowELAPSED, pw); pw.print(" = "); pw.println(sdf.format(new Date(nextWakeupRTC))); if (mAlarmBatches.size() > 0) { @@ -750,11 +777,27 @@ class AlarmManagerService extends SystemService { pw.println(mAlarmBatches.size()); for (Batch b : mAlarmBatches) { pw.print(b); pw.println(':'); - dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC); + dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf); } } pw.println(); + pw.print("Past-due non-wakeup alarms: "); + if (mPendingNonWakeupAlarms.size() > 0) { + pw.println(mPendingNonWakeupAlarms.size()); + dumpAlarmList(pw, mPendingNonWakeupAlarms, " ", nowELAPSED, nowRTC, sdf); + } else { + pw.println("(none)"); + } + pw.print(" Number of delayed alarms: "); pw.print(mNumDelayedAlarms); + pw.print(", total delay time: "); TimeUtils.formatDuration(mTotalDelayTime, pw); + pw.println(); + pw.print(" Max delay time: "); TimeUtils.formatDuration(mMaxDelayTime, pw); + pw.print(", max non-interactive time: "); + TimeUtils.formatDuration(mNonInteractiveTime, pw); + pw.println(); + + pw.println(); pw.print(" Broadcast ref count: "); pw.println(mBroadcastRefCount); pw.println(); @@ -811,13 +854,7 @@ class AlarmManagerService extends SystemService { pw.print(" alarms: "); UserHandle.formatUid(pw, fs.mBroadcastStats.mUid); pw.print(":"); pw.print(fs.mBroadcastStats.mPackageName); pw.println(); - pw.print(" "); - if (fs.mTarget.first != null) { - pw.print(" act="); pw.print(fs.mTarget.first); - } - if (fs.mTarget.second != null) { - pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString()); - } + pw.print(" "); pw.print(fs.mTag); pw.println(); } } @@ -849,13 +886,8 @@ class AlarmManagerService extends SystemService { TimeUtils.formatDuration(fs.aggregateTime, pw); pw.print(" "); pw.print(fs.numWakeup); pw.print(" wakes " ); pw.print(fs.count); - pw.print(" alarms:"); - if (fs.mTarget.first != null) { - pw.print(" act="); pw.print(fs.mTarget.first); - } - if (fs.mTarget.second != null) { - pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString()); - } + pw.print(" alarms: "); + pw.print(fs.mTag); pw.println(); } } @@ -883,7 +915,7 @@ class AlarmManagerService extends SystemService { } } - private void logBatchesLocked() { + private void logBatchesLocked(SimpleDateFormat sdf) { ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); PrintWriter pw = new PrintWriter(bs); final long nowRTC = System.currentTimeMillis(); @@ -892,7 +924,7 @@ class AlarmManagerService extends SystemService { for (int iz = 0; iz < NZ; iz++) { Batch bz = mAlarmBatches.get(iz); pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz); - dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC); + dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC, sdf); pw.flush(); Slog.v(TAG, bs.toString()); bs.reset(); @@ -910,7 +942,8 @@ class AlarmManagerService extends SystemService { lastTime = b.start; } else { Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order"); - logBatchesLocked(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + logBatchesLocked(sdf); return false; } } @@ -932,6 +965,7 @@ class AlarmManagerService extends SystemService { void rescheduleKernelAlarmsLocked() { // 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; if (mAlarmBatches.size() > 0) { final Batch firstWakeup = findFirstWakeupBatchLocked(); final Batch firstBatch = mAlarmBatches.get(0); @@ -939,11 +973,19 @@ class AlarmManagerService extends SystemService { mNextWakeup = firstWakeup.start; setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start); } - if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) { - mNextNonWakeup = firstBatch.start; - setLocked(ELAPSED_REALTIME, firstBatch.start); + if (firstBatch != firstWakeup) { + nextNonWakeup = firstBatch.start; + } + } + if (mPendingNonWakeupAlarms.size() > 0) { + if (nextNonWakeup == 0 || mNextNonWakeupDeliveryTime < nextNonWakeup) { + nextNonWakeup = mNextNonWakeupDeliveryTime; } } + if (nextNonWakeup != 0 && mNextNonWakeup != nextNonWakeup) { + mNextNonWakeup = nextNonWakeup; + setLocked(ELAPSED_REALTIME, nextNonWakeup); + } } private void removeLocked(PendingIntent operation) { @@ -1003,6 +1045,32 @@ class AlarmManagerService extends SystemService { } } + void interactiveStateChangedLocked(boolean interactive) { + if (mInteractive != interactive) { + mInteractive = interactive; + final long nowELAPSED = SystemClock.elapsedRealtime(); + if (interactive) { + if (mPendingNonWakeupAlarms.size() > 0) { + final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime; + mTotalDelayTime += thisDelayTime; + if (mMaxDelayTime < thisDelayTime) { + mMaxDelayTime = thisDelayTime; + } + deliverAlarmsLocked(mPendingNonWakeupAlarms, nowELAPSED); + mPendingNonWakeupAlarms.clear(); + } + if (mNonInteractiveStartTime > 0) { + long dur = nowELAPSED - mNonInteractiveStartTime; + if (dur > mNonInteractiveTime) { + mNonInteractiveTime = dur; + } + } + } else { + mNonInteractiveStartTime = nowELAPSED; + } + } + } + boolean lookForPackageLocked(String packageName) { for (int i = 0; i < mAlarmBatches.size(); i++) { Batch b = mAlarmBatches.get(i); @@ -1037,12 +1105,12 @@ class AlarmManagerService extends SystemService { } private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, - String prefix, String label, long now) { + String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) { for (int i=list.size()-1; i>=0; i--) { Alarm a = list.get(i); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", now); + a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); } } @@ -1059,14 +1127,13 @@ class AlarmManagerService extends SystemService { } private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, - String prefix, long nowELAPSED, long nowRTC) { + String prefix, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) { for (int i=list.size()-1; i>=0; i--) { Alarm a = list.get(i); final String label = labelForType(a.type); - long now = (a.type <= RTC) ? nowRTC : nowELAPSED; pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", now); + a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); } } @@ -1077,8 +1144,9 @@ class AlarmManagerService extends SystemService { private native int setKernelTime(long nativeData, long millis); private native int setKernelTimezone(long nativeData, int minuteswest); - void triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED, + boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED, final long nowRTC) { + boolean hasWakeup = 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 @@ -1113,8 +1181,14 @@ class AlarmManagerService extends SystemService { maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), alarm.repeatInterval, alarm.operation, batch.standalone, true, alarm.workSource); - } + // For now we count this as a wakeup alarm, meaning it needs to be + // delivered immediately. In the future we should change this, but + // that required delaying when we reschedule the repeat...! + hasWakeup = false; + } else if (alarm.wakeup) { + hasWakeup = true; + } } } @@ -1125,6 +1199,8 @@ class AlarmManagerService extends SystemService { Slog.v(TAG, "Triggering alarm #" + i + ": " + triggerList.get(i)); } } + + return hasWakeup; } /** @@ -1147,15 +1223,16 @@ class AlarmManagerService extends SystemService { private static class Alarm { public final int type; public final boolean wakeup; + public final PendingIntent operation; + public final String tag; + public final WorkSource workSource; 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 repeatInterval; - public PendingIntent operation; - public WorkSource workSource; - + public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, long _interval, PendingIntent _op, WorkSource _ws) { type = _type; @@ -1167,12 +1244,17 @@ class AlarmManagerService extends SystemService { maxWhen = _maxWhen; repeatInterval = _interval; operation = _op; + tag = makeTag(_op, _type); workSource = _ws; } + public static String makeTag(PendingIntent pi, int type) { + return pi.getTag(type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP + ? "*walarm*:" : "*alarm*:"); + } + @Override - public String toString() - { + public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Alarm{"); sb.append(Integer.toHexString(System.identityHashCode(this))); @@ -1186,11 +1268,20 @@ class AlarmManagerService extends SystemService { return sb.toString(); } - public void dump(PrintWriter pw, String prefix, long now) { + public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED, + SimpleDateFormat sdf) { + final boolean isRtc = (type == RTC || type == RTC_WAKEUP); + pw.print(prefix); pw.print("tag="); pw.println(tag); pw.print(prefix); pw.print("type="); pw.print(type); - pw.print(" whenElapsed="); pw.print(whenElapsed); - pw.print(" when="); TimeUtils.formatDuration(when, now, pw); - pw.print(" window="); pw.print(windowLength); + pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, + nowELAPSED, pw); + if (isRtc) { + pw.print(" when="); pw.print(sdf.format(new Date(when))); + } else { + pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw); + } + 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(prefix); pw.print("operation="); pw.println(operation); @@ -1216,6 +1307,102 @@ class AlarmManagerService extends SystemService { } } + long currentNonWakeupFuzzLocked(long nowELAPSED) { + long timeSinceOn = nowELAPSED - mNonInteractiveStartTime; + if (timeSinceOn < 5*60*1000) { + // If the screen has been off for 5 minutes, only delay by at most two minutes. + return 2*60*1000; + } else if (timeSinceOn < 30*60*1000) { + // If the screen has been off for 30 minutes, only delay by at most 15 minutes. + return 15*60*1000; + } else { + // Otherwise, we will delay by at most an hour. + return 60*60*1000; + } + } + + boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) { + if (mInteractive) { + return false; + } + if (mLastAlarmDeliveryTime <= 0) { + return false; + } + if (mPendingNonWakeupAlarms.size() > 0 && mNextNonWakeupDeliveryTime > nowELAPSED) { + // This is just a little paranoia, if somehow we have pending non-wakeup alarms + // and the next delivery time is in the past, then just deliver them all. This + // avoids bugs where we get stuck in a loop trying to poll for alarms. + return false; + } + long timeSinceLast = nowELAPSED - mLastAlarmDeliveryTime; + return timeSinceLast <= currentNonWakeupFuzzLocked(nowELAPSED); + } + + void deliverAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED) { + mLastAlarmDeliveryTime = nowELAPSED; + for (int i=0; i<triggerList.size(); i++) { + Alarm alarm = triggerList.get(i); + try { + if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); + alarm.operation.send(getContext(), 0, + mBackgroundIntent.putExtra( + Intent.EXTRA_ALARM_COUNT, alarm.count), + mResultReceiver, mHandler); + + // we have an active broadcast so stay awake. + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.operation, alarm.workSource, + alarm.type, alarm.tag, true); + mWakeLock.acquire(); + } + final InFlight inflight = new InFlight(AlarmManagerService.this, + alarm.operation, alarm.workSource, alarm.type, alarm.tag); + mInFlight.add(inflight); + mBroadcastRefCount++; + + final BroadcastStats bs = inflight.mBroadcastStats; + bs.count++; + if (bs.nesting == 0) { + bs.nesting = 1; + bs.startTime = nowELAPSED; + } else { + bs.nesting++; + } + final FilterStats fs = inflight.mFilterStats; + fs.count++; + if (fs.nesting == 0) { + fs.nesting = 1; + fs.startTime = nowELAPSED; + } else { + fs.nesting++; + } + if (alarm.type == ELAPSED_REALTIME_WAKEUP + || alarm.type == RTC_WAKEUP) { + bs.numWakeup++; + fs.numWakeup++; + if (alarm.workSource != null && alarm.workSource.size() > 0) { + for (int wi=0; wi<alarm.workSource.size(); wi++) { + ActivityManagerNative.noteWakeupAlarm( + alarm.operation, alarm.workSource.get(wi), + alarm.workSource.getName(wi)); + } + } else { + ActivityManagerNative.noteWakeupAlarm( + alarm.operation, -1, null); + } + } + } catch (PendingIntent.CanceledException e) { + if (alarm.repeatInterval > 0) { + // This IntentSender is no longer valid, but this + // is a repeating alarm, so toss the hoser. + removeImpl(alarm.operation); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Failure sending alarm.", e); + } + } + } + private class AlarmThread extends Thread { public AlarmThread() @@ -1269,70 +1456,35 @@ class AlarmManagerService extends SystemService { } } - triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); - rescheduleKernelAlarmsLocked(); - - // now deliver the alarm intents - for (int i=0; i<triggerList.size(); i++) { - Alarm alarm = triggerList.get(i); - try { - if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); - alarm.operation.send(getContext(), 0, - mBackgroundIntent.putExtra( - Intent.EXTRA_ALARM_COUNT, alarm.count), - mResultReceiver, mHandler); - - // we have an active broadcast so stay awake. - if (mBroadcastRefCount == 0) { - setWakelockWorkSource(alarm.operation, alarm.workSource, - alarm.type, true); - mWakeLock.acquire(); - } - final InFlight inflight = new InFlight(AlarmManagerService.this, - alarm.operation, alarm.workSource, alarm.type); - mInFlight.add(inflight); - mBroadcastRefCount++; - - final BroadcastStats bs = inflight.mBroadcastStats; - bs.count++; - if (bs.nesting == 0) { - bs.nesting = 1; - bs.startTime = nowELAPSED; - } else { - bs.nesting++; - } - final FilterStats fs = inflight.mFilterStats; - fs.count++; - if (fs.nesting == 0) { - fs.nesting = 1; - fs.startTime = nowELAPSED; - } else { - fs.nesting++; - } - if (alarm.type == ELAPSED_REALTIME_WAKEUP - || alarm.type == RTC_WAKEUP) { - bs.numWakeup++; - fs.numWakeup++; - if (alarm.workSource != null && alarm.workSource.size() > 0) { - for (int wi=0; wi<alarm.workSource.size(); wi++) { - ActivityManagerNative.noteWakeupAlarm( - alarm.operation, alarm.workSource.get(wi), - alarm.workSource.getName(wi)); - } - } else { - ActivityManagerNative.noteWakeupAlarm( - alarm.operation, -1, null); - } - } - } catch (PendingIntent.CanceledException e) { - if (alarm.repeatInterval > 0) { - // This IntentSender is no longer valid, but this - // is a repeating alarm, so toss the hoser. - removeImpl(alarm.operation); + boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); + if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) { + // if there are no wakeup alarms and the screen is off, we can + // delay what we have so far until the future. + if (mPendingNonWakeupAlarms.size() == 0) { + mStartCurrentDelayTime = nowELAPSED; + mNextNonWakeupDeliveryTime = nowELAPSED + + ((currentNonWakeupFuzzLocked(nowELAPSED)*3)/2); + } + mPendingNonWakeupAlarms.addAll(triggerList); + mNumDelayedAlarms += triggerList.size(); + rescheduleKernelAlarmsLocked(); + } else { + // now deliver the alarm intents; if there are pending non-wakeup + // alarms, we need to merge them in to the list. note we don't + // just deliver them first because we generally want non-wakeup + // alarms delivered after wakeup alarms. + rescheduleKernelAlarmsLocked(); + if (mPendingNonWakeupAlarms.size() > 0) { + triggerList.addAll(mPendingNonWakeupAlarms); + Collections.sort(triggerList, mAlarmDispatchComparator); + final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime; + mTotalDelayTime += thisDelayTime; + if (mMaxDelayTime < thisDelayTime) { + mMaxDelayTime = thisDelayTime; } - } catch (RuntimeException e) { - Slog.w(TAG, "Failure sending alarm.", e); + mPendingNonWakeupAlarms.clear(); } + deliverAlarmsLocked(triggerList, nowELAPSED); } } } @@ -1344,14 +1496,13 @@ class AlarmManagerService extends SystemService { * @param pi PendingIntent to attribute blame to if ws is null. * @param ws WorkSource to attribute blame. */ - void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, boolean first) { + void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag, + boolean first) { try { final boolean unimportant = pi == mTimeTickSender; mWakeLock.setUnimportantForLogging(unimportant); if (first || mLastWakeLockUnimportantForLogging) { - mWakeLock.setHistoryTag(pi.getTag( - type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP - ? "*walarm*:" : "*alarm*:")); + mWakeLock.setHistoryTag(tag); } else { mWakeLock.setHistoryTag(null); } @@ -1462,6 +1613,23 @@ class AlarmManagerService extends SystemService { } } + class InteractiveStateReceiver extends BroadcastReceiver { + public InteractiveStateReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + getContext().registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + interactiveStateChangedLocked(Intent.ACTION_SCREEN_ON.equals(intent.getAction())); + } + } + } + class UninstallReceiver extends BroadcastReceiver { public UninstallReceiver() { IntentFilter filter = new IntentFilter(); @@ -1589,7 +1757,7 @@ class AlarmManagerService extends SystemService { if (mInFlight.size() > 0) { InFlight inFlight = mInFlight.get(0); setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource, - inFlight.mAlarmType, false); + inFlight.mAlarmType, inFlight.mTag, false); } else { // should never happen mLog.w("Alarm wakelock still held but sent queue empty"); diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index be20616..bfa0402 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -1253,7 +1253,10 @@ public class AppOpsService extends IAppOpsService.Stub { public void removeUser(int userHandle) throws RemoteException { checkSystemUid("removeUser"); mOpRestrictions.remove(userHandle); - mProfileOwnerUids.removeAt(mProfileOwnerUids.indexOfKey(userHandle)); + final int index = mProfileOwnerUids.indexOfKey(userHandle); + if (index >= 0) { + mProfileOwnerUids.removeAt(index); + } } private void checkSystemUid(String function) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index cc132be..af53fef 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -398,7 +398,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** * used internally when registering NetworkFactories - * obj = Messenger + * obj = NetworkFactoryInfo */ private static final int EVENT_REGISTER_NETWORK_FACTORY = 17; @@ -434,6 +434,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_RELEASE_NETWORK_REQUEST = 22; + /** + * used internally when registering NetworkFactories + * obj = Messenger + */ + private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23; + + /** Handler used for internal events. */ final private InternalHandler mHandler; /** Handler used for incoming {@link NetworkStateTracker} events. */ @@ -2889,6 +2896,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { return; } + pw.println("NetworkFactories for:"); + pw.increaseIndent(); + for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { + pw.println(nfi.name); + } + pw.decreaseIndent(); + pw.println(); + NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId); pw.print("Active default network: "); if (defaultNai == null) { @@ -2983,6 +2998,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (nai == null) { loge("NetworkAgent not found for EVENT_NETWORK_PROPERTIES_CHANGED"); } else { + if (VDBG) log("Update of Linkproperties for " + nai.name()); LinkProperties oldLp = nai.linkProperties; nai.linkProperties = (LinkProperties)msg.obj; updateLinkProperties(nai, oldLp); @@ -3096,18 +3112,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleAsyncChannelHalfConnect(Message msg) { AsyncChannel ac = (AsyncChannel) msg.obj; - if (mNetworkFactories.contains(ac)) { + if (mNetworkFactoryInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // A network factory has connected. Send it all current NetworkRequests. for (NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.isRequest == false) continue; NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, (nai != null ? nai.currentScore : 0), 0, nri.request); } } else { loge("Error connecting NetworkFactory"); - mNetworkFactories.remove(ac); + mNetworkFactoryInfos.remove(msg.obj); } } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { @@ -3214,8 +3231,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetworkRequests.put(nri.request, nri); if (msg.what == EVENT_REGISTER_NETWORK_REQUEST) { if (DBG) log("sending new NetworkRequest to factories"); - for (AsyncChannel ac : mNetworkFactories) { - ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request); + for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { + nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request); } } } @@ -3236,8 +3253,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (nri.isRequest) { - for (AsyncChannel factory : mNetworkFactories) { - factory.sendMessage(NetworkFactoryProtocol.CMD_CANCEL_REQUEST, nri.request); + for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { + nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_CANCEL_REQUEST, nri.request); } if (affectedNetwork != null) { @@ -3356,7 +3373,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { break; } case EVENT_REGISTER_NETWORK_FACTORY: { - handleRegisterNetworkFactory((Messenger)msg.obj); + handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj); + break; + } + case EVENT_UNREGISTER_NETWORK_FACTORY: { + handleUnregisterNetworkFactory((Messenger)msg.obj); break; } case EVENT_REGISTER_NETWORK_AGENT: { @@ -5222,10 +5243,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent); } - private final ArrayList<AsyncChannel> mNetworkFactories = new ArrayList<AsyncChannel>(); + private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = + new HashMap<Messenger, NetworkFactoryInfo>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<NetworkRequest, NetworkRequestInfo>(); + private static class NetworkFactoryInfo { + public final String name; + public final Messenger messenger; + public final AsyncChannel asyncChannel; + + public NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel) { + this.name = name; + this.messenger = messenger; + this.asyncChannel = asyncChannel; + } + } private class NetworkRequestInfo implements IBinder.DeathRecipient { static final boolean REQUEST = true; @@ -5263,6 +5296,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { request + ", " + mBinder + ")"); releaseNetworkRequest(request); } + + public String toString() { + return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" + + mPid + " for " + request; + } } @Override @@ -5326,24 +5364,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { } @Override - public void registerNetworkFactory(Messenger messenger) { + public void registerNetworkFactory(Messenger messenger, String name) { enforceConnectivityInternalPermission(); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, messenger)); + NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel()); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); } - private void handleRegisterNetworkFactory(Messenger messenger) { - if (VDBG) log("Got NetworkFactory Messenger"); - AsyncChannel ac = new AsyncChannel(); - mNetworkFactories.add(ac); - ac.connect(mContext, mTrackerHandler, messenger); - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - if (nri.isRequest) { - int score = 0; - NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId); - if (currentNetwork != null) score = currentNetwork.currentScore; - ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request); - } + private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) { + if (VDBG) log("Got NetworkFactory Messenger for " + nfi.name); + mNetworkFactoryInfos.put(nfi.messenger, nfi); + nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger); + } + + @Override + public void unregisterNetworkFactory(Messenger messenger) { + enforceConnectivityInternalPermission(); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger)); + } + + private void handleUnregisterNetworkFactory(Messenger messenger) { + NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger); + if (nfi == null) { + if (VDBG) log("Failed to find Messenger in unregisterNetworkFactory"); + return; } + if (VDBG) log("unregisterNetworkFactory for " + nfi.name); } /** @@ -5535,8 +5580,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) { if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); - for (AsyncChannel ac : mNetworkFactories) { - ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, networkRequest); + for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { + nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, networkRequest); } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 50553ee..5cb2a8a 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -3439,7 +3439,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " mShowExplicitlyRequested=" + mShowExplicitlyRequested + " mShowForced=" + mShowForced + " mInputShown=" + mInputShown); - p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); + p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mScreenOn); } p.println(" "); diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index c32beda..cd8c13f 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -264,7 +264,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } else if (headset == BIT_USB_HEADSET_DGTL) { device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; } else if (headset == BIT_HDMI_AUDIO) { - device = AudioManager.DEVICE_OUT_AUX_DIGITAL; + device = AudioManager.DEVICE_OUT_HDMI; } else { Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); return; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7abc75f..7cd4ef8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13837,7 +13837,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { - ProxyInfo proxy = intent.getParcelableExtra("proxy"); + ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index e0bc718..156bbbe 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -103,7 +103,7 @@ final class NewDeviceAction extends FeatureAction { requestVendorId(); return true; } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) { - int requestOpcode = params[1]; + int requestOpcode = params[1] & 0xff; if (requestOpcode == HdmiCec.MESSAGE_SET_OSD_NAME) { mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID; requestVendorId(); @@ -113,7 +113,8 @@ final class NewDeviceAction extends FeatureAction { } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) { if (opcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { if (params.length == 3) { - mVendorId = (params[0] << 16) + (params[1] << 8) + params[2]; + mVendorId = ((params[0] & 0xff) << 16) + ((params[1] & 0xff) << 8) + + (params[2] & 0xff); } else { Slog.e(TAG, "Failed to get device vendor ID: "); } @@ -121,7 +122,7 @@ final class NewDeviceAction extends FeatureAction { finish(); return true; } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) { - int requestOpcode = params[1]; + int requestOpcode = params[1] & 0xff; if (requestOpcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) { addDeviceInfo(); finish(); diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 007032e..dbfb1cf 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -88,6 +88,8 @@ public class ConditionProviders extends ManagedServices { for (int i = 0; i < mRecords.size(); i++) { pw.print(" "); pw.println(mRecords.get(i)); } + pw.print(" mCountdownHelper: "); + pw.println(mCountdownHelper.getCurrentConditionDescription()); } } @@ -474,6 +476,16 @@ public class ConditionProviders extends ManagedServices { } } + public String getCurrentConditionDescription() { + if (mCurrent == 0) return null; + final long time = mCurrent; + final long now = System.currentTimeMillis(); + final CharSequence span = + DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS); + return String.format("Scheduled for %s, %s in the future (%s), now=%s", + ts(time), time - now, span, ts(now)); + } + private String ts(long time) { return new Date(time) + " (" + time + ")"; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1734a33..9569c0d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -63,7 +63,7 @@ import android.service.notification.INotificationListener; import android.service.notification.IConditionListener; import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; -import android.service.notification.NotificationOrderUpdate; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.StatusBarNotification; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; @@ -1744,7 +1744,7 @@ public class NotificationManagerService extends SystemService { sendAccessibilityEvent(notification, pkg); } - mListeners.notifyPostedLocked(r.sbn); + mListeners.notifyPostedLocked(r.sbn, cloneNotificationListLocked()); } else { Slog.e(TAG, "Not posting notification with icon==0: " + notification); if (old != null && old.statusBarKey != null) { @@ -1755,7 +1755,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } - mListeners.notifyRemovedLocked(r.sbn); + mListeners.notifyRemovedLocked(r.sbn, cloneNotificationListLocked()); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid @@ -2041,14 +2041,17 @@ public class NotificationManagerService extends SystemService { private void handleSendRankingUpdate() { synchronized (mNotificationList) { - final int N = mNotificationList.size(); - ArrayList<StatusBarNotification> sbns = - new ArrayList<StatusBarNotification>(N); - for (int i = 0; i < N; i++ ) { - sbns.add(mNotificationList.get(i).sbn); - } - mListeners.notifyOrderUpdateLocked(sbns); + mListeners.notifyRankingUpdateLocked(cloneNotificationListLocked()); + } + } + + private ArrayList<StatusBarNotification> cloneNotificationListLocked() { + final int N = mNotificationList.size(); + ArrayList<StatusBarNotification> sbns = new ArrayList<StatusBarNotification>(N); + for (int i = 0; i < N; i++) { + sbns.add(mNotificationList.get(i).sbn); } + return sbns; } private final class WorkerHandler extends Handler @@ -2136,7 +2139,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } r.statusBarKey = null; - mListeners.notifyRemovedLocked(r.sbn); + mListeners.notifyRemovedLocked(r.sbn, cloneNotificationListLocked()); } // sound @@ -2442,6 +2445,33 @@ public class NotificationManagerService extends SystemService { } } + /** + * Generates a NotificationRankingUpdate from 'sbns', considering only + * notifications visible to the given listener. + */ + private static NotificationRankingUpdate makeRankingUpdateForListener(ManagedServiceInfo info, + ArrayList<StatusBarNotification> sbns) { + int speedBumpIndex = -1; + ArrayList<String> keys = new ArrayList<String>(sbns.size()); + ArrayList<String> dndKeys = new ArrayList<String>(sbns.size()); + for (StatusBarNotification sbn: sbns) { + if (!info.enabledAndUserMatches(sbn.getUserId())) { + continue; + } + keys.add(sbn.getKey()); + if (sbn.getNotification().extras.getBoolean(EXTRA_INTERCEPT)) { + dndKeys.add(sbn.getKey()); + } + if (speedBumpIndex == -1 && + sbn.getNotification().priority == Notification.PRIORITY_MIN) { + speedBumpIndex = keys.size() - 1; + } + } + String[] keysAr = keys.toArray(new String[keys.size()]); + String[] dndKeysAr = dndKeys.toArray(new String[dndKeys.size()]); + return new NotificationRankingUpdate(keysAr, dndKeysAr, speedBumpIndex); + } + public class NotificationListeners extends ManagedServices { public NotificationListeners() { @@ -2468,9 +2498,12 @@ public class NotificationManagerService extends SystemService { @Override public void onServiceAdded(ManagedServiceInfo info) { final INotificationListener listener = (INotificationListener) info.service; - final String[] keys = getActiveNotificationKeys(listener); + final ArrayList<StatusBarNotification> sbns; + synchronized (mNotificationList) { + sbns = cloneNotificationListLocked(); + } try { - listener.onListenerConnected(new NotificationOrderUpdate(keys)); + listener.onListenerConnected(makeRankingUpdateForListener(info, sbns)); } catch (RemoteException e) { // we tried } @@ -2479,44 +2512,47 @@ public class NotificationManagerService extends SystemService { /** * asynchronously notify all listeners about a new notification */ - public void notifyPostedLocked(StatusBarNotification sbn) { + public void notifyPostedLocked(StatusBarNotification sbn, + final ArrayList<StatusBarNotification> sbns) { // make a copy in case changes are made to the underlying Notification object final StatusBarNotification sbnClone = sbn.clone(); for (final ManagedServiceInfo info : mServices) { - if (info.isEnabledForCurrentProfiles()) { - final INotificationListener listener = (INotificationListener) info.service; - final String[] keys = getActiveNotificationKeys(listener); - if (keys.length > 0) { - mHandler.post(new Runnable() { - @Override - public void run() { - notifyPostedIfUserMatch(info, sbnClone, keys); - } - }); - } + if (!info.isEnabledForCurrentProfiles()) { + continue; } + final NotificationRankingUpdate update = makeRankingUpdateForListener(info, sbns); + if (update.getOrderedKeys().length == 0) { + continue; + } + mHandler.post(new Runnable() { + @Override + public void run() { + notifyPostedIfUserMatch(info, sbnClone, update); + } + }); } } /** * asynchronously notify all listeners about a removed notification */ - public void notifyRemovedLocked(StatusBarNotification sbn) { + public void notifyRemovedLocked(StatusBarNotification sbn, + final ArrayList<StatusBarNotification> sbns) { // make a copy in case changes are made to the underlying Notification object // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the // notification final StatusBarNotification sbnLight = sbn.cloneLight(); for (final ManagedServiceInfo info : mServices) { - if (info.isEnabledForCurrentProfiles()) { - final INotificationListener listener = (INotificationListener) info.service; - final String[] keys = getActiveNotificationKeys(listener); - mHandler.post(new Runnable() { - @Override - public void run() { - notifyRemovedIfUserMatch(info, sbnLight, keys); - } - }); + if (!info.isEnabledForCurrentProfiles()) { + continue; } + mHandler.post(new Runnable() { + @Override + public void run() { + notifyRemovedIfUserMatch(info, sbnLight, + makeRankingUpdateForListener(info, sbns)); + } + }); } } @@ -2526,60 +2562,52 @@ public class NotificationManagerService extends SystemService { * must not rely on mutable members of these objects, such as the * {@link Notification}. */ - public void notifyOrderUpdateLocked(final ArrayList<StatusBarNotification> sbns) { + public void notifyRankingUpdateLocked(final ArrayList<StatusBarNotification> sbns) { for (final ManagedServiceInfo serviceInfo : mServices) { + if (!serviceInfo.isEnabledForCurrentProfiles()) { + continue; + } mHandler.post(new Runnable() { @Override public void run() { - notifyOrderUpdateIfUserMatch(serviceInfo, sbns); + notifyRankingUpdate(serviceInfo, + makeRankingUpdateForListener(serviceInfo, sbns)); } }); } } private void notifyPostedIfUserMatch(final ManagedServiceInfo info, - final StatusBarNotification sbn, String[] keys) { + final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { if (!info.enabledAndUserMatches(sbn.getUserId())) { return; } final INotificationListener listener = (INotificationListener)info.service; try { - listener.onNotificationPosted(sbn, new NotificationOrderUpdate(keys)); + listener.onNotificationPosted(sbn, rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (posted): " + listener, ex); } } private void notifyRemovedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn, - String[] keys) { + NotificationRankingUpdate rankingUpdate) { if (!info.enabledAndUserMatches(sbn.getUserId())) { return; } - final INotificationListener listener = (INotificationListener)info.service; + final INotificationListener listener = (INotificationListener) info.service; try { - listener.onNotificationRemoved(sbn, new NotificationOrderUpdate(keys)); + listener.onNotificationRemoved(sbn, rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (removed): " + listener, ex); } } - /** - * @param sbns an array of {@link StatusBarNotification}s to consider. This code - * must not rely on mutable members of these objects, such as the - * {@link Notification}. - */ - public void notifyOrderUpdateIfUserMatch(ManagedServiceInfo info, - ArrayList<StatusBarNotification> sbns) { - ArrayList<String> keys = new ArrayList<String>(sbns.size()); - for (StatusBarNotification sbn: sbns) { - if (info.enabledAndUserMatches(sbn.getUserId())) { - keys.add(sbn.getKey()); - } - } - final INotificationListener listener = (INotificationListener)info.service; + private void notifyRankingUpdate(ManagedServiceInfo info, + NotificationRankingUpdate rankingUpdate) { + final INotificationListener listener = (INotificationListener) info.service; try { - listener.onNotificationOrderUpdate( - new NotificationOrderUpdate(keys.toArray(new String[keys.size()]))); + listener.onNotificationRankingUpdate(rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (ranking update): " + listener, ex); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 61b3a89..46c5482 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11876,6 +11876,7 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" cmd may be one of:"); pw.println(" l[ibraries]: list known shared libraries"); pw.println(" f[ibraries]: list device features"); + pw.println(" k[eysets]: print known keysets"); pw.println(" r[esolvers]: dump intent resolvers"); pw.println(" perm[issions]: dump permissions"); pw.println(" pref[erred]: print preferred package settings"); @@ -11886,8 +11887,8 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" m[essages]: print collected runtime messages"); pw.println(" v[erifiers]: print package verifier info"); pw.println(" version: print database version info"); + pw.println(" write: write current settings now"); pw.println(" <package.name>: info about given package"); - pw.println(" k[eysets]: print known keysets"); return; } else if ("--checkin".equals(opt)) { checkin = true; @@ -11938,6 +11939,12 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.setDump(DumpState.DUMP_VERSION); } else if ("k".equals(cmd) || "keysets".equals(cmd)) { dumpState.setDump(DumpState.DUMP_KEYSETS); + } else if ("write".equals(cmd)) { + synchronized (mPackages) { + mSettings.writeLPr(); + pw.println("Settings written."); + return; + } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 131d05b..102b2d4 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -511,8 +511,16 @@ public class UserManagerService extends IUserManager.Stub { * Check if we've hit the limit of how many users can be created. */ private boolean isUserLimitReachedLocked() { - int nUsers = mUsers.size(); - return nUsers >= UserManager.getMaxSupportedUsers(); + int aliveUserCount = 0; + final int totalUserCount = mUsers.size(); + // Skip over users being removed + for (int i = 0; i < totalUserCount; i++) { + UserInfo user = mUsers.valueAt(i); + if (!mRemovingUserIds.get(user.id)) { + aliveUserCount++; + } + } + return aliveUserCount >= UserManager.getMaxSupportedUsers(); } /** @@ -781,6 +789,7 @@ public class UserManagerService extends IUserManager.Stub { writeBoolean(serializer, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE); writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADJUST_VOLUME); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_TELEPHONY); serializer.endTag(null, TAG_RESTRICTIONS); } serializer.endTag(null, TAG_USER); @@ -933,6 +942,7 @@ public class UserManagerService extends IUserManager.Stub { UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); readBoolean(parser, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE); readBoolean(parser, restrictions, UserManager.DISALLOW_ADJUST_VOLUME); + readBoolean(parser, restrictions, UserManager.DISALLOW_TELEPHONY); } } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index c1b9a33..efaa91b 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -324,8 +324,6 @@ public class TrustManagerService extends SystemService { mTrustListeners.get(i).onTrustChanged(enabled, userId); } catch (RemoteException e) { Slog.e(TAG, "Exception while notifying TrustListener. Removing listener.", e); - mTrustListeners.get(i); - i--; } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c20e38c..e2d2ac6 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.os.IRemoteCallback; import android.os.SystemProperties; import android.util.Slog; +import android.view.View; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -34,7 +35,6 @@ import android.view.animation.AnimationUtils; import android.view.animation.ClipRectAnimation; import android.view.animation.Interpolator; import android.view.animation.ScaleAnimation; - import android.view.animation.TranslateAnimation; import com.android.internal.util.DumpUtils.Dump; import com.android.server.AttributeCache; @@ -500,7 +500,8 @@ public class AppTransition implements Dump { */ Animation createAlternateThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth, int appHeight, int orientation, int transit, - Rect containingFrame, Rect contentInsets) { + Rect containingFrame, Rect contentInsets, + boolean isFullScreen) { Animation a; final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; @@ -520,6 +521,9 @@ public class AppTransition implements Dump { scaledTopDecor = (int) (scale * contentInsets.top); int unscaledThumbHeight = (int) (thumbHeight / scale); mTmpFromClipRect.set(containingFrame); + if (isFullScreen) { + mTmpFromClipRect.top = contentInsets.top; + } mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight); mTmpToClipRect.set(containingFrame); } else { @@ -527,7 +531,12 @@ public class AppTransition implements Dump { scale = thumbHeight / (appHeight - contentInsets.top); scaledTopDecor = (int) (scale * contentInsets.top); int unscaledThumbWidth = (int) (thumbWidth / scale); + int unscaledThumbHeight = (int) (thumbHeight / scale); mTmpFromClipRect.set(containingFrame); + if (isFullScreen) { + mTmpFromClipRect.top = contentInsets.top; + mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight); + } mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth); mTmpToClipRect.set(containingFrame); } @@ -575,14 +584,22 @@ public class AppTransition implements Dump { int unscaledThumbHeight = (int) (thumbHeight / scale); mTmpFromClipRect.set(containingFrame); mTmpToClipRect.set(containingFrame); + if (isFullScreen) { + mTmpToClipRect.top = contentInsets.top; + } mTmpToClipRect.bottom = (mTmpToClipRect.top + unscaledThumbHeight); } else { // In landscape, we scale the height and clip to the top/left square scale = thumbHeight / (appHeight - contentInsets.top); scaledTopDecor = (int) (scale * contentInsets.top); int unscaledThumbWidth = (int) (thumbWidth / scale); + int unscaledThumbHeight = (int) (thumbHeight / scale); mTmpFromClipRect.set(containingFrame); mTmpToClipRect.set(containingFrame); + if (isFullScreen) { + mTmpToClipRect.top = contentInsets.top; + mTmpToClipRect.bottom = (mTmpToClipRect.top + unscaledThumbHeight); + } mTmpToClipRect.right = (mTmpToClipRect.left + unscaledThumbWidth); } @@ -679,7 +696,7 @@ public class AppTransition implements Dump { Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int appWidth, int appHeight, int orientation, - Rect containingFrame, Rect contentInsets) { + Rect containingFrame, Rect contentInsets, boolean isFullScreen) { Animation a; if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { a = loadAnimation(mNextAppTransitionPackage, enter ? @@ -702,7 +719,7 @@ public class AppTransition implements Dump { (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); a = createAlternateThumbnailEnterExitAnimationLocked( getThumbnailTransitionState(enter), appWidth, appHeight, orientation, - transit, containingFrame, contentInsets); + transit, containingFrame, contentInsets, isFullScreen); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c6fffbf..63a4f52 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -289,6 +289,11 @@ public class WindowManagerService extends IWindowManager.Stub private static final int MAX_SCREENSHOT_RETRIES = 3; + // The flag describing a full screen app window (where the app takes care of drawing under the + // SystemUI bars) + private static final int SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + final private KeyguardDisableHandler mKeyguardDisableHandler; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -3193,8 +3198,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + boolean isFullScreen = + ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) + == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN); Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height, - mCurConfiguration.orientation, containingFrame, contentInsets); + mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e2cd4e2..bc7742f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3384,6 +3384,93 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public boolean setApplicationBlocked(ComponentName who, String packageName, + boolean blocked) { + int callingUserId = UserHandle.getCallingUserId(); + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + IPackageManager pm = AppGlobals.getPackageManager(); + return pm.setApplicationBlockedSettingAsUser(packageName, blocked, callingUserId); + } catch (RemoteException re) { + // shouldn't happen + Slog.e(LOG_TAG, "Failed to setApplicationBlockedSetting", re); + } finally { + restoreCallingIdentity(id); + } + return false; + } + } + + @Override + public int setApplicationsBlocked(ComponentName who, Intent intent, boolean blocked) { + int callingUserId = UserHandle.getCallingUserId(); + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + IPackageManager pm = AppGlobals.getPackageManager(); + List<ResolveInfo> activitiesToEnable = pm.queryIntentActivities(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_UNINSTALLED_PACKAGES, + callingUserId); + + if (DBG) Slog.d(LOG_TAG, "Enabling activities: " + activitiesToEnable); + int numberOfAppsUnblocked = 0; + if (activitiesToEnable != null) { + for (ResolveInfo info : activitiesToEnable) { + if (info.activityInfo != null) { + numberOfAppsUnblocked++; + pm.setApplicationBlockedSettingAsUser(info.activityInfo.packageName, + blocked, callingUserId); + } + } + } + return numberOfAppsUnblocked; + } catch (RemoteException re) { + // shouldn't happen + Slog.e(LOG_TAG, "Failed to setApplicationsBlockedSettingsWithIntent", re); + } finally { + restoreCallingIdentity(id); + } + return 0; + } + } + + @Override + public boolean isApplicationBlocked(ComponentName who, String packageName) { + int callingUserId = UserHandle.getCallingUserId(); + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + IPackageManager pm = AppGlobals.getPackageManager(); + return pm.getApplicationBlockedSettingAsUser(packageName, callingUserId); + } catch (RemoteException re) { + // shouldn't happen + Slog.e(LOG_TAG, "Failed to getApplicationBlockedSettingAsUser", re); + } finally { + restoreCallingIdentity(id); + } + return false; + } + } + + @Override public void enableSystemApp(ComponentName who, String packageName) { synchronized (this) { if (who == null) { @@ -3595,4 +3682,43 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return false; } + + @Override + public void setGlobalSetting(ComponentName who, String setting, String value) { + final ContentResolver contentResolver = mContext.getContentResolver(); + + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + Settings.Global.putString(contentResolver, setting, value); + } finally { + restoreCallingIdentity(id); + } + } + } + + @Override + public void setSecureSetting(ComponentName who, String setting, String value) { + int callingUserId = UserHandle.getCallingUserId(); + final ContentResolver contentResolver = mContext.getContentResolver(); + + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + Settings.Secure.putStringForUser(contentResolver, setting, value, callingUserId); + } finally { + restoreCallingIdentity(id); + } + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9b3f7ac..55ae9c6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -125,7 +125,7 @@ public final class SystemServer { private static final String WIFI_SERVICE_CLASS = "com.android.server.wifi.WifiService"; private static final String WIFI_PASSPOINT_SERVICE_CLASS = - "com.android.server.wifi.passpoint.WifiPasspointService"; + "com.android.server.wifi.passpoint.PasspointService"; private static final String WIFI_P2P_SERVICE_CLASS = "com.android.server.wifi.p2p.WifiP2pService"; private static final String HDMI_CEC_SERVICE_CLASS = @@ -639,15 +639,15 @@ public final class SystemServer { } try { - mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); + mSystemServiceManager.startService(WIFI_SERVICE_CLASS); } catch (Throwable e) { - reportWtf("starting Wi-Fi PasspointService", e); + reportWtf("starting Wi-Fi Service", e); } try { - mSystemServiceManager.startService(WIFI_SERVICE_CLASS); + mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); } catch (Throwable e) { - reportWtf("starting Wi-Fi Service", e); + reportWtf("starting Wi-Fi PasspointService", e); } try { diff --git a/tests/RenderThreadTest/Android.mk b/tests/RenderThreadTest/Android.mk index bdcba2e..e07e943 100644 --- a/tests/RenderThreadTest/Android.mk +++ b/tests/RenderThreadTest/Android.mk @@ -1,7 +1,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_TAGS := tests # Only compile source java files in this apk. LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/tests/RenderThreadTest/AndroidManifest.xml b/tests/RenderThreadTest/AndroidManifest.xml index c76cfce..a7f4f6e 100644 --- a/tests/RenderThreadTest/AndroidManifest.xml +++ b/tests/RenderThreadTest/AndroidManifest.xml @@ -4,10 +4,6 @@ android:versionCode="1" android:versionName="1.0" > - <uses-sdk - android:minSdkVersion="18" - android:targetSdkVersion="18" /> - <application android:allowBackup="true" android:icon="@drawable/ic_launcher" diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index b766268..9ea7027 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -783,6 +783,9 @@ public class WifiScanner { FullScanResult result = (FullScanResult) msg.obj; ((ScanListener) listener).onFullResult(result); return; + case CMD_PERIOD_CHANGED: + ((ScanListener) listener).onPeriodChanged(msg.arg1); + return; case CMD_AP_FOUND: ((HotlistListener) listener).onFound( ((ParcelableScanResults) msg.obj).getResults()); |
