diff options
173 files changed, 7193 insertions, 2354 deletions
@@ -174,9 +174,6 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \ core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \ core/java/android/hardware/usb/IUsbManager.aidl \ - core/java/android/midi/IMidiDeviceServer.aidl \ - core/java/android/midi/IMidiListener.aidl \ - core/java/android/midi/IMidiManager.aidl \ core/java/android/net/IConnectivityManager.aidl \ core/java/android/net/IEthernetManager.aidl \ core/java/android/net/IEthernetServiceListener.aidl \ @@ -335,7 +332,10 @@ LOCAL_SRC_FILES += \ media/java/android/media/IRemoteVolumeObserver.aidl \ media/java/android/media/IRingtonePlayer.aidl \ media/java/android/media/IVolumeController.aidl \ - media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \ + media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \ + media/java/android/media/midi/IMidiDeviceServer.aidl \ + media/java/android/media/midi/IMidiListener.aidl \ + media/java/android/media/midi/IMidiManager.aidl \ media/java/android/media/projection/IMediaProjection.aidl \ media/java/android/media/projection/IMediaProjectionCallback.aidl \ media/java/android/media/projection/IMediaProjectionManager.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index c35f332..c812b6a 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -228,6 +228,11 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/SystemUI_intermediates/) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/Keyguard_intermediates/) $(call add-clean-step, rm -f $(OUT_DIR)/target/product/*/system/framework/android.policy.jar) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/inputflinger $(PRODUCT_OUT)/symbols/system/bin/inputflinger) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/system/lib64/libinputflingerhost.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/symbols/system/lib64/libinputflingerhost.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libinputflingerhost.so $(PRODUCT_OUT)/obj_arm/lib/libinputflingerhost.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinputflingerhost_intermediates $(PRODUCT_OUT)/obj_arm/SHARED_LIBRARIES/libinputflingerhost_intermediates) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER diff --git a/api/current.txt b/api/current.txt index 5ff0d74..c0ad9c6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1710,6 +1710,7 @@ package android { field public static final int message = 16908299; // 0x102000b field public static final int navigationBarBackground = 16908336; // 0x1020030 field public static final int paste = 16908322; // 0x1020022 + field public static final int pasteAsPlainText = 16908339; // 0x1020033 field public static final int primary = 16908300; // 0x102000c field public static final int progress = 16908301; // 0x102000d field public static final int redo = 16908338; // 0x1020032 @@ -11797,15 +11798,8 @@ package android.graphics.drawable { method public final void setTileModeY(android.graphics.Shader.TileMode); } - public class ClipDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class ClipDrawable extends android.graphics.drawable.DrawableWrapper { ctor public ClipDrawable(android.graphics.drawable.Drawable, int, int); - method public void draw(android.graphics.Canvas); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); field public static final int HORIZONTAL = 1; // 0x1 field public static final int VERTICAL = 2; // 0x2 } @@ -11847,8 +11841,10 @@ package android.graphics.drawable { method public android.graphics.Rect getDirtyBounds(); method public boolean getDither(); method public boolean getFilterBitmap(); + method public void getHotspotBounds(android.graphics.Rect); method public int getIntrinsicHeight(); method public int getIntrinsicWidth(); + method public int getLayoutDirection(); method public final int getLevel(); method public int getMinimumHeight(); method public int getMinimumWidth(); @@ -11866,6 +11862,7 @@ package android.graphics.drawable { method public void jumpToCurrentState(); method public android.graphics.drawable.Drawable mutate(); method protected void onBoundsChange(android.graphics.Rect); + method public boolean onLayoutDirectionChange(int); method protected boolean onLevelChange(int); method protected boolean onStateChange(int[]); method public static int resolveOpacity(int, int); @@ -11882,6 +11879,7 @@ package android.graphics.drawable { method public void setFilterBitmap(boolean); method public void setHotspot(float, float); method public void setHotspotBounds(int, int, int, int); + method public final boolean setLayoutDirection(int); method public final boolean setLevel(int); method public boolean setState(int[]); method public void setTint(int); @@ -11946,6 +11944,19 @@ package android.graphics.drawable { method public final void setVariablePadding(boolean); } + public abstract class DrawableWrapper extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + ctor public DrawableWrapper(android.graphics.drawable.Drawable); + method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getDrawable(); + method public int getOpacity(); + 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 void setColorFilter(android.graphics.ColorFilter); + method public void setDrawable(android.graphics.drawable.Drawable); + method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); + } + public class GradientDrawable extends android.graphics.drawable.Drawable { ctor public GradientDrawable(); ctor public GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation, int[]); @@ -11993,17 +12004,9 @@ package android.graphics.drawable { enum_constant public static final android.graphics.drawable.GradientDrawable.Orientation TR_BL; } - public class InsetDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class InsetDrawable extends android.graphics.drawable.DrawableWrapper { ctor public InsetDrawable(android.graphics.drawable.Drawable, int); ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } public class LayerDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { @@ -12016,6 +12019,12 @@ package android.graphics.drawable { method public int getId(int); method public int getLayerGravity(int); method public int getLayerHeight(int); + method public int getLayerInsetBottom(int); + method public int getLayerInsetEnd(int); + method public int getLayerInsetLeft(int); + method public int getLayerInsetRight(int); + method public int getLayerInsetStart(int); + method public int getLayerInsetTop(int); method public int getLayerWidth(int); method public int getNumberOfLayers(); method public int getOpacity(); @@ -12028,9 +12037,17 @@ package android.graphics.drawable { method public boolean setDrawableByLayerId(int, android.graphics.drawable.Drawable); method public void setId(int, int); method public void setLayerGravity(int, int); + method public void setLayerHeight(int, int); method public void setLayerInset(int, int, int, int, int); + method public void setLayerInsetBottom(int, int); + method public void setLayerInsetEnd(int, int); + method public void setLayerInsetLeft(int, int); method public void setLayerInsetRelative(int, int, int, int, int); + method public void setLayerInsetRight(int, int); + method public void setLayerInsetStart(int, int); + method public void setLayerInsetTop(int, int); method public void setLayerSize(int, int, int); + method public void setLayerWidth(int, int); method public void setOpacity(int); method public void setPaddingMode(int); method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); @@ -12083,41 +12100,24 @@ package android.graphics.drawable { field public static final int RADIUS_AUTO = -1; // 0xffffffff } - public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class RotateDrawable extends android.graphics.drawable.DrawableWrapper { ctor public RotateDrawable(); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); method public float getFromDegrees(); - method public int getOpacity(); method public float getPivotX(); method public float getPivotY(); method public float getToDegrees(); - method public void invalidateDrawable(android.graphics.drawable.Drawable); method public boolean isPivotXRelative(); method public boolean isPivotYRelative(); - method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); - method public void setAlpha(int); - method public void setColorFilter(android.graphics.ColorFilter); - method public void setDrawable(android.graphics.drawable.Drawable); method public void setFromDegrees(float); method public void setPivotX(float); method public void setPivotXRelative(boolean); method public void setPivotY(float); method public void setPivotYRelative(boolean); method public void setToDegrees(float); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } - public class ScaleDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class ScaleDrawable extends android.graphics.drawable.DrawableWrapper { ctor public ScaleDrawable(android.graphics.drawable.Drawable, int, float, float); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } public class ShapeDrawable extends android.graphics.drawable.Drawable { diff --git a/api/system-current.txt b/api/system-current.txt index 71d09e0..deaa5f0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1786,6 +1786,7 @@ package android { field public static final int message = 16908299; // 0x102000b field public static final int navigationBarBackground = 16908336; // 0x1020030 field public static final int paste = 16908322; // 0x1020022 + field public static final int pasteAsPlainText = 16908339; // 0x1020033 field public static final int primary = 16908300; // 0x102000c field public static final int progress = 16908301; // 0x102000d field public static final int redo = 16908338; // 0x1020032 @@ -12071,15 +12072,8 @@ package android.graphics.drawable { method public final void setTileModeY(android.graphics.Shader.TileMode); } - public class ClipDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class ClipDrawable extends android.graphics.drawable.DrawableWrapper { ctor public ClipDrawable(android.graphics.drawable.Drawable, int, int); - method public void draw(android.graphics.Canvas); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); field public static final int HORIZONTAL = 1; // 0x1 field public static final int VERTICAL = 2; // 0x2 } @@ -12121,8 +12115,10 @@ package android.graphics.drawable { method public android.graphics.Rect getDirtyBounds(); method public boolean getDither(); method public boolean getFilterBitmap(); + method public void getHotspotBounds(android.graphics.Rect); method public int getIntrinsicHeight(); method public int getIntrinsicWidth(); + method public int getLayoutDirection(); method public final int getLevel(); method public int getMinimumHeight(); method public int getMinimumWidth(); @@ -12140,6 +12136,7 @@ package android.graphics.drawable { method public void jumpToCurrentState(); method public android.graphics.drawable.Drawable mutate(); method protected void onBoundsChange(android.graphics.Rect); + method public boolean onLayoutDirectionChange(int); method protected boolean onLevelChange(int); method protected boolean onStateChange(int[]); method public static int resolveOpacity(int, int); @@ -12156,6 +12153,7 @@ package android.graphics.drawable { method public void setFilterBitmap(boolean); method public void setHotspot(float, float); method public void setHotspotBounds(int, int, int, int); + method public final boolean setLayoutDirection(int); method public final boolean setLevel(int); method public boolean setState(int[]); method public void setTint(int); @@ -12220,6 +12218,19 @@ package android.graphics.drawable { method public final void setVariablePadding(boolean); } + public abstract class DrawableWrapper extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + ctor public DrawableWrapper(android.graphics.drawable.Drawable); + method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getDrawable(); + method public int getOpacity(); + 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 void setColorFilter(android.graphics.ColorFilter); + method public void setDrawable(android.graphics.drawable.Drawable); + method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); + } + public class GradientDrawable extends android.graphics.drawable.Drawable { ctor public GradientDrawable(); ctor public GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation, int[]); @@ -12267,17 +12278,9 @@ package android.graphics.drawable { enum_constant public static final android.graphics.drawable.GradientDrawable.Orientation TR_BL; } - public class InsetDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class InsetDrawable extends android.graphics.drawable.DrawableWrapper { ctor public InsetDrawable(android.graphics.drawable.Drawable, int); ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } public class LayerDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { @@ -12290,6 +12293,12 @@ package android.graphics.drawable { method public int getId(int); method public int getLayerGravity(int); method public int getLayerHeight(int); + method public int getLayerInsetBottom(int); + method public int getLayerInsetEnd(int); + method public int getLayerInsetLeft(int); + method public int getLayerInsetRight(int); + method public int getLayerInsetStart(int); + method public int getLayerInsetTop(int); method public int getLayerWidth(int); method public int getNumberOfLayers(); method public int getOpacity(); @@ -12302,9 +12311,17 @@ package android.graphics.drawable { method public boolean setDrawableByLayerId(int, android.graphics.drawable.Drawable); method public void setId(int, int); method public void setLayerGravity(int, int); + method public void setLayerHeight(int, int); method public void setLayerInset(int, int, int, int, int); + method public void setLayerInsetBottom(int, int); + method public void setLayerInsetEnd(int, int); + method public void setLayerInsetLeft(int, int); method public void setLayerInsetRelative(int, int, int, int, int); + method public void setLayerInsetRight(int, int); + method public void setLayerInsetStart(int, int); + method public void setLayerInsetTop(int, int); method public void setLayerSize(int, int, int); + method public void setLayerWidth(int, int); method public void setOpacity(int); method public void setPaddingMode(int); method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); @@ -12357,41 +12374,24 @@ package android.graphics.drawable { field public static final int RADIUS_AUTO = -1; // 0xffffffff } - public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class RotateDrawable extends android.graphics.drawable.DrawableWrapper { ctor public RotateDrawable(); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); method public float getFromDegrees(); - method public int getOpacity(); method public float getPivotX(); method public float getPivotY(); method public float getToDegrees(); - method public void invalidateDrawable(android.graphics.drawable.Drawable); method public boolean isPivotXRelative(); method public boolean isPivotYRelative(); - method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); - method public void setAlpha(int); - method public void setColorFilter(android.graphics.ColorFilter); - method public void setDrawable(android.graphics.drawable.Drawable); method public void setFromDegrees(float); method public void setPivotX(float); method public void setPivotXRelative(boolean); method public void setPivotY(float); method public void setPivotYRelative(boolean); method public void setToDegrees(float); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } - public class ScaleDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + public class ScaleDrawable extends android.graphics.drawable.DrawableWrapper { ctor public ScaleDrawable(android.graphics.drawable.Drawable, int, float, float); - method public void draw(android.graphics.Canvas); - method public android.graphics.drawable.Drawable getDrawable(); - method public int getOpacity(); - 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 void setColorFilter(android.graphics.ColorFilter); - method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); } public class ShapeDrawable extends android.graphics.drawable.Drawable { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 62081ee..a501fa7 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -138,6 +138,7 @@ public class Am extends BaseCommand { " am task lock <TASK_ID>\n" + " am task lock stop\n" + " am task resizeable <TASK_ID> [true|false]\n" + + " am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + " am get-config\n" + "\n" + "am start: start an Activity. Options are:\n" + @@ -249,11 +250,11 @@ public class Am extends BaseCommand { "am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>" + ".\n" + "\n" + - "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally" + - " starting the new stack with [INTENT] if specified. If [INTENT] isn't" + - " specified and the current stack has more than one task, then the top task" + - " of the current task will be moved to the new stack. Command will also force" + - " all current tasks in both stacks to be resizeable." + + "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" + + " starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" + + " specified and the current stack has more than one task, then the top task\n" + + " of the current task will be moved to the new stack. Command will also force\n" + + " all current tasks in both stacks to be resizeable.\n" + "\n" + "am stack list: list all of the activity stacks and their sizes.\n" + "\n" + @@ -265,6 +266,10 @@ public class Am extends BaseCommand { "\n" + "am task resizeable: change if <TASK_ID> is resizeable (true) or not (false).\n" + "\n" + + "am task resize: makes sure <TASK_ID> is in a stack with the specified bounds.\n" + + " Forces the task to be resizeable and creates a stack if no existing stack\n" + + " has the specified bounds.\n" + + "\n" + "am get-config: retrieve the configuration and any recent configurations\n" + " of the device\n" + "\n" + @@ -1742,33 +1747,14 @@ public class Am extends BaseCommand { private void runStackResize() throws Exception { String stackIdStr = nextArgRequired(); int stackId = Integer.valueOf(stackIdStr); - String leftStr = nextArgRequired(); - int left = Integer.valueOf(leftStr); - String topStr = nextArgRequired(); - int top = Integer.valueOf(topStr); - String rightStr = nextArgRequired(); - int right = Integer.valueOf(rightStr); - String bottomStr = nextArgRequired(); - int bottom = Integer.valueOf(bottomStr); - if (left < 0) { - System.err.println("Error: bad left arg: " + leftStr); - return; - } - if (top < 0) { - System.err.println("Error: bad top arg: " + topStr); - return; - } - if (right <= 0) { - System.err.println("Error: bad right arg: " + rightStr); - return; - } - if (bottom <= 0) { - System.err.println("Error: bad bottom arg: " + bottomStr); + final Rect bounds = getBounds(); + if (bounds == null) { + System.err.println("Error: invalid input bounds"); return; } try { - mAm.resizeStack(stackId, new Rect(left, top, right, bottom)); + mAm.resizeStack(stackId, bounds); } catch (RemoteException e) { } } @@ -1857,6 +1843,8 @@ public class Am extends BaseCommand { runTaskLock(); } else if (op.equals("resizeable")) { runTaskResizeable(); + } else if (op.equals("resize")) { + runTaskResize(); } else { showError("Error: unknown command '" + op + "'"); return; @@ -1890,6 +1878,20 @@ public class Am extends BaseCommand { } } + private void runTaskResize() throws Exception { + final String taskIdStr = nextArgRequired(); + final int taskId = Integer.valueOf(taskIdStr); + final Rect bounds = getBounds(); + if (bounds == null) { + System.err.println("Error: invalid input bounds"); + return; + } + try { + mAm.resizeTask(taskId, bounds); + } catch (RemoteException e) { + } + } + private List<Configuration> getRecentConfigurations(int days) { IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( Context.USAGE_STATS_SERVICE)); @@ -1986,4 +1988,32 @@ public class Am extends BaseCommand { } return fd; } + + private Rect getBounds() { + String leftStr = nextArgRequired(); + int left = Integer.valueOf(leftStr); + String topStr = nextArgRequired(); + int top = Integer.valueOf(topStr); + String rightStr = nextArgRequired(); + int right = Integer.valueOf(rightStr); + String bottomStr = nextArgRequired(); + int bottom = Integer.valueOf(bottomStr); + if (left < 0) { + System.err.println("Error: bad left arg: " + leftStr); + return null; + } + if (top < 0) { + System.err.println("Error: bad top arg: " + topStr); + return null; + } + if (right <= 0) { + System.err.println("Error: bad right arg: " + rightStr); + return null; + } + if (bottom <= 0) { + System.err.println("Error: bad bottom arg: " + bottomStr); + return null; + } + return new Rect(left, top, right, bottom); + } } diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp index 81fe5f8..2932130 100644 --- a/cmds/bootanimation/AudioPlayer.cpp +++ b/cmds/bootanimation/AudioPlayer.cpp @@ -305,7 +305,7 @@ bool AudioPlayer::threadLoop() exit: if (pcm) pcm_close(pcm); - mCurrentFile->release(); + delete mCurrentFile; mCurrentFile = NULL; return false; } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 1d4de22..bb25ec6 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -179,7 +179,7 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) // FileMap memory is never released until application exit. // Release it now as the texture is already loaded and the memory used for // the packed resource can be released. - frame.map->release(); + delete frame.map; // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. @@ -446,7 +446,7 @@ bool BootAnimation::readFile(const char* name, String8& outString) } outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength()); - entryMap->release(); + delete entryMap; return true; } diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index bbe6eef..197e36b 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -147,20 +147,20 @@ namespace { char *buf = new char[uncompLen]; if (NULL == buf) { ALOGW("%s: failed to allocate %zd byte\n", __FUNCTION__, uncompLen); - dataMap->release(); + delete dataMap; return -1; } StreamingZipInflater inflater(dataMap, uncompLen); if (inflater.read(buf, uncompLen) < 0) { ALOGW("%s: failed to inflate %zd byte\n", __FUNCTION__, uncompLen); delete[] buf; - dataMap->release(); + delete dataMap; return -1; } int priority = parse_manifest(buf, uncompLen, target_package_name); delete[] buf; - dataMap->release(); + delete dataMap; return priority; } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 005b1d9..bb307bb 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2313,6 +2313,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case RESIZE_TASK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + Rect r = Rect.CREATOR.createFromParcel(data); + resizeTask(taskId, r); + reply.writeNoException(); + return true; + } + case GET_TASK_DESCRIPTION_ICON_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String filename = data.readString(); @@ -5438,6 +5447,20 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void resizeTask(int taskId, Rect r) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + r.writeToParcel(data, 0); + mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index db380ed..503657b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -75,12 +75,12 @@ import android.location.ILocationManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.MediaRouter; +import android.media.midi.IMidiManager; +import android.media.midi.MidiManager; import android.media.projection.MediaProjectionManager; import android.media.session.MediaSessionManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; -import android.midi.IMidiManager; -import android.midi.MidiManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.EthernetManager; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index c5f534c..bdcc312 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1244,7 +1244,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { mCalled = true; } @@ -1310,7 +1310,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ - public void onActivityCreated(Bundle savedInstanceState) { + public void onActivityCreated(@Nullable Bundle savedInstanceState) { mCalled = true; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 1f5a1a0..a7e9413 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -464,6 +464,7 @@ public interface IActivityManager extends IInterface { public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values) throws RemoteException; public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException; + public void resizeTask(int taskId, Rect bounds) throws RemoteException; public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException; public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) @@ -808,4 +809,5 @@ public interface IActivityManager extends IInterface { int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282; int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283; int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284; + int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index eabe297..46ccc95 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2971,7 +2971,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.midi.MidiManager} for accessing the MIDI service. + * {@link android.media.midi.MidiManager} for accessing the MIDI service. * * @see #getSystemService * @hide diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index cd32dae..1d16516 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -73,9 +73,7 @@ public interface SharedPreferences { * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. - * @param value The new value for the preference. Supplying {@code null} - * as the value is equivalent to calling {@link #remove(String)} with - * this key. + * @param value The new value for the preference. * * @return Returns a reference to the same Editor object, so you can * chain put calls together. diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java index e9ec5a4..e3bc238 100644 --- a/core/java/android/content/UndoManager.java +++ b/core/java/android/content/UndoManager.java @@ -20,9 +20,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelableParcel; import android.text.TextUtils; +import android.util.ArrayMap; import java.util.ArrayList; -import java.util.HashMap; /** * Top-level class for managing and interacting with the global undo state for @@ -54,7 +54,9 @@ import java.util.HashMap; * @hide */ public class UndoManager { - private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>(); + // The common case is a single undo owner (e.g. for a TextView), so default to that capacity. + private final ArrayMap<String, UndoOwner> mOwners = + new ArrayMap<String, UndoOwner>(1 /* capacity */); private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>(); private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>(); private int mUpdateCount; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index d52dd30..e303f61 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -205,6 +205,20 @@ public final class PowerManager { public static final int DOZE_WAKE_LOCK = 0x00000040; /** + * Wake lock level: Keep the device awake enough to allow drawing to occur. + * <p> + * This is used by the window manager to allow applications to draw while the + * system is dozing. It currently has no effect unless the power manager is in + * the dozing state. + * </p><p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * {@hide} + */ + public static final int DRAW_WAKE_LOCK = 0x00000080; + + /** * Mask for the wake lock level component of a combined wake lock level and flags integer. * * @hide @@ -489,6 +503,7 @@ public final class PowerManager { case FULL_WAKE_LOCK: case PROXIMITY_SCREEN_OFF_WAKE_LOCK: case DOZE_WAKE_LOCK: + case DRAW_WAKE_LOCK: break; default: throw new IllegalArgumentException("Must specify a valid wake lock level."); diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 0d80c0d..06666f4 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -16,6 +16,7 @@ package android.preference; +import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.XmlRes; import android.app.Fragment; @@ -531,7 +532,7 @@ public abstract class PreferenceActivity extends ListActivity implements } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Theming for the PreferenceActivity layout and for the Preference Header(s) layout diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index e95e6e2..29f9ca1 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -16,6 +16,7 @@ package android.preference; +import android.annotation.Nullable; import android.app.Activity; import android.app.Fragment; import android.content.Intent; @@ -153,15 +154,15 @@ public abstract class PreferenceFragment extends Fragment implements } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE); mPreferenceManager.setFragment(this); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { TypedArray a = getActivity().obtainStyledAttributes(null, com.android.internal.R.styleable.PreferenceFragment, @@ -177,7 +178,7 @@ public abstract class PreferenceFragment extends Fragment implements } @Override - public void onActivityCreated(Bundle savedInstanceState) { + public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (mHavePrefs) { diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index bf51ed1..ac6bbb7 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -16,6 +16,10 @@ package android.security; +import android.security.keymaster.ExportResult; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.OperationResult; import android.security.KeystoreArguments; /** @@ -52,4 +56,19 @@ interface IKeystoreService { int reset_uid(int uid); int sync_uid(int sourceUid, int targetUid); int password_uid(String password, int uid); + + // Keymaster 0.4 methods + int addRngEntropy(in byte[] data); + int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags, + out KeyCharacteristics characteristics); + int getKeyCharacteristics(String alias, in byte[] clientId, + in byte[] appId, out KeyCharacteristics characteristics); + int importKey(String alias, in KeymasterArguments arguments, int format, + in byte[] keyData, int uid, int flags, out KeyCharacteristics characteristics); + ExportResult exportKey(String alias, int format, in byte[] clientId, in byte[] appId); + OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, + in KeymasterArguments params, out KeymasterArguments operationParams); + OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); + OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature); + int abort(IBinder handle); } diff --git a/core/java/android/security/keymaster/ExportResult.aidl b/core/java/android/security/keymaster/ExportResult.aidl new file mode 100644 index 0000000..f522355 --- /dev/null +++ b/core/java/android/security/keymaster/ExportResult.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/* @hide */ +parcelable ExportResult; diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java new file mode 100644 index 0000000..bb44c03 --- /dev/null +++ b/core/java/android/security/keymaster/ExportResult.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class for handling parceling the return values from keymaster's export operation. + * @hide + */ +public class ExportResult implements Parcelable { + public final int resultCode; + public final byte[] exportData; + + public static final Parcelable.Creator<ExportResult> CREATOR = new + Parcelable.Creator<ExportResult>() { + public ExportResult createFromParcel(Parcel in) { + return new ExportResult(in); + } + + public ExportResult[] newArray(int length) { + return new ExportResult[length]; + } + }; + + protected ExportResult(Parcel in) { + resultCode = in.readInt(); + exportData = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(resultCode); + out.writeByteArray(exportData); + } +}; diff --git a/core/java/android/security/keymaster/KeyCharacteristics.aidl b/core/java/android/security/keymaster/KeyCharacteristics.aidl new file mode 100644 index 0000000..15014b1 --- /dev/null +++ b/core/java/android/security/keymaster/KeyCharacteristics.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/* @hide */ +parcelable KeyCharacteristics; diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java new file mode 100644 index 0000000..b803a1b --- /dev/null +++ b/core/java/android/security/keymaster/KeyCharacteristics.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * @hide + */ +public class KeyCharacteristics implements Parcelable { + public KeymasterArguments swEnforced; + public KeymasterArguments hwEnforced; + + public static final Parcelable.Creator<KeyCharacteristics> CREATOR = new + Parcelable.Creator<KeyCharacteristics>() { + public KeyCharacteristics createFromParcel(Parcel in) { + return new KeyCharacteristics(in); + } + + public KeyCharacteristics[] newArray(int length) { + return new KeyCharacteristics[length]; + } + }; + + public KeyCharacteristics() {} + + protected KeyCharacteristics(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + swEnforced.writeToParcel(out, flags); + hwEnforced.writeToParcel(out, flags); + } + + public void readFromParcel(Parcel in) { + swEnforced = KeymasterArguments.CREATOR.createFromParcel(in); + hwEnforced = KeymasterArguments.CREATOR.createFromParcel(in); + } +} + diff --git a/core/java/android/security/keymaster/KeymasterArgument.java b/core/java/android/security/keymaster/KeymasterArgument.java new file mode 100644 index 0000000..9a1c894 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterArgument.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelFormatException; + +/** + * Base class for the Java side of a Keymaster tagged argument. + * <p> + * Serialization code for this and subclasses must be kept in sync with system/security/keystore + * and with hardware/libhardware/include/hardware/keymaster_defs.h + * @hide + */ +abstract class KeymasterArgument implements Parcelable { + public final int tag; + + public static final Parcelable.Creator<KeymasterArgument> CREATOR = new + Parcelable.Creator<KeymasterArgument>() { + public KeymasterArgument createFromParcel(Parcel in) { + final int pos = in.dataPosition(); + final int tag = in.readInt(); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_ENUM: + case KeymasterDefs.KM_ENUM_REP: + case KeymasterDefs.KM_INT: + case KeymasterDefs.KM_INT_REP: + return new KeymasterIntArgument(tag, in); + case KeymasterDefs.KM_LONG: + return new KeymasterLongArgument(tag, in); + case KeymasterDefs.KM_DATE: + return new KeymasterDateArgument(tag, in); + case KeymasterDefs.KM_BYTES: + case KeymasterDefs.KM_BIGNUM: + return new KeymasterBlobArgument(tag, in); + case KeymasterDefs.KM_BOOL: + return new KeymasterBooleanArgument(tag, in); + default: + throw new ParcelFormatException("Bad tag: " + tag + " at " + pos); + } + } + public KeymasterArgument[] newArray(int size) { + return new KeymasterArgument[size]; + } + }; + + protected KeymasterArgument(int tag) { + this.tag = tag; + } + + /** + * Writes the value of this argument, if any, to the provided parcel. + */ + public abstract void writeValue(Parcel out); + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(tag); + writeValue(out); + } +} diff --git a/core/java/android/security/keymaster/KeymasterArguments.aidl b/core/java/android/security/keymaster/KeymasterArguments.aidl new file mode 100644 index 0000000..7aef5a6 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterArguments.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/* @hide */ +parcelable KeymasterArguments; diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java new file mode 100644 index 0000000..b5fd4bd --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterArguments.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Utility class for the java side of user specified Keymaster arguments. + * <p> + * Serialization code for this and subclasses must be kept in sync with system/security/keystore + * @hide + */ +public class KeymasterArguments implements Parcelable { + List<KeymasterArgument> mArguments; + + public static final Parcelable.Creator<KeymasterArguments> CREATOR = new + Parcelable.Creator<KeymasterArguments>() { + public KeymasterArguments createFromParcel(Parcel in) { + return new KeymasterArguments(in); + } + public KeymasterArguments[] newArray(int size) { + return new KeymasterArguments[size]; + } + }; + + public KeymasterArguments() { + mArguments = new ArrayList<KeymasterArgument>(); + } + + private KeymasterArguments(Parcel in) { + mArguments = in.createTypedArrayList(KeymasterArgument.CREATOR); + } + + public void addInt(int tag, int value) { + mArguments.add(new KeymasterIntArgument(tag, value)); + } + + public void addBoolean(int tag) { + mArguments.add(new KeymasterBooleanArgument(tag)); + } + + public void addLong(int tag, long value) { + mArguments.add(new KeymasterLongArgument(tag, value)); + } + + public void addBlob(int tag, byte[] value) { + mArguments.add(new KeymasterBlobArgument(tag, value)); + } + + public void addDate(int tag, Date value) { + mArguments.add(new KeymasterDateArgument(tag, value)); + } + + private KeymasterArgument getArgumentByTag(int tag) { + for (KeymasterArgument arg : mArguments) { + if (arg.tag == tag) { + return arg; + } + } + return null; + } + + public boolean containsTag(int tag) { + return getArgumentByTag(tag) != null; + } + + public int getInt(int tag, int defaultValue) { + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_ENUM: + case KeymasterDefs.KM_INT: + break; // Accepted types + case KeymasterDefs.KM_INT_REP: + case KeymasterDefs.KM_ENUM_REP: + throw new IllegalArgumentException("Repeatable tags must use getInts: " + tag); + default: + throw new IllegalArgumentException("Tag is not an int type: " + tag); + } + KeymasterArgument arg = getArgumentByTag(tag); + if (arg == null) { + return defaultValue; + } + return ((KeymasterIntArgument) arg).value; + } + + public long getLong(int tag, long defaultValue) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_LONG) { + throw new IllegalArgumentException("Tag is not a long type: " + tag); + } + KeymasterArgument arg = getArgumentByTag(tag); + if (arg == null) { + return defaultValue; + } + return ((KeymasterLongArgument) arg).value; + } + + public Date getDate(int tag, Date defaultValue) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Tag is not a date type: " + tag); + } + KeymasterArgument arg = getArgumentByTag(tag); + if (arg == null) { + return defaultValue; + } + return ((KeymasterDateArgument) arg).date; + } + + public boolean getBoolean(int tag, boolean defaultValue) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BOOL) { + throw new IllegalArgumentException("Tag is not a boolean type: " + tag); + } + KeymasterArgument arg = getArgumentByTag(tag); + if (arg == null) { + return defaultValue; + } + return true; + } + + public byte[] getBlob(int tag, byte[] defaultValue) { + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_BYTES: + case KeymasterDefs.KM_BIGNUM: + break; // Allowed types. + default: + throw new IllegalArgumentException("Tag is not a blob type: " + tag); + } + KeymasterArgument arg = getArgumentByTag(tag); + if (arg == null) { + return defaultValue; + } + return ((KeymasterBlobArgument) arg).blob; + } + + public List<Integer> getInts(int tag) { + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_INT_REP: + case KeymasterDefs.KM_ENUM_REP: + break; // Allowed types. + default: + throw new IllegalArgumentException("Tag is not a repeating type: " + tag); + } + List<Integer> values = new ArrayList<Integer>(); + for (KeymasterArgument arg : mArguments) { + if (arg.tag == tag) { + values.add(((KeymasterIntArgument) arg).value); + } + } + return values; + } + + public int size() { + return mArguments.size(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeTypedList(mArguments); + } + + public void readFromParcel(Parcel in) { + in.readTypedList(mArguments, KeymasterArgument.CREATOR); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java new file mode 100644 index 0000000..27f1153 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +class KeymasterBlobArgument extends KeymasterArgument { + public final byte[] blob; + + public KeymasterBlobArgument(int tag, byte[] blob) { + super(tag); + this.blob = blob; + } + + public KeymasterBlobArgument(int tag, Parcel in) { + super(tag); + blob = in.createByteArray(); + } + + @Override + public void writeValue(Parcel out) { + out.writeByteArray(blob); + } +} diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java new file mode 100644 index 0000000..8e17db4 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +class KeymasterBooleanArgument extends KeymasterArgument { + + // Boolean arguments are always true if they exist and false if they don't. + public final boolean value = true; + + public KeymasterBooleanArgument(int tag) { + super(tag); + } + + public KeymasterBooleanArgument(int tag, Parcel in) { + super(tag); + } + + @Override + public void writeValue(Parcel out) { + // Do nothing, value is implicit. + } +} diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java new file mode 100644 index 0000000..e8f4055 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterDateArgument.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; + +/** + * @hide + */ +class KeymasterDateArgument extends KeymasterArgument { + public final Date date; + + public KeymasterDateArgument(int tag, Date date) { + super(tag); + this.date = date; + } + + public KeymasterDateArgument(int tag, Parcel in) { + super(tag); + date = new Date(in.readLong()); + } + + @Override + public void writeValue(Parcel out) { + out.writeLong(date.getTime()); + } +} diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java new file mode 100644 index 0000000..88cad79 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -0,0 +1,227 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/** + * Class tracking all the keymaster enum values needed for the binder API to keystore. + * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h + * See keymaster_defs.h for detailed descriptions of each constant. + * @hide + */ +public final class KeymasterDefs { + + private KeymasterDefs() {} + + // Tag types. + public static final int KM_INVALID = 0 << 28; + public static final int KM_ENUM = 1 << 28; + public static final int KM_ENUM_REP = 2 << 28; + public static final int KM_INT = 3 << 28; + public static final int KM_INT_REP = 4 << 28; + public static final int KM_LONG = 5 << 28; + public static final int KM_DATE = 6 << 28; + public static final int KM_BOOL = 7 << 28; + public static final int KM_BIGNUM = 8 << 28; + public static final int KM_BYTES = 9 << 28; + + // Tag values. + public static final int KM_TAG_INVALID = KM_INVALID | 0; + public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1; + public static final int KM_TAG_ALGORITHM = KM_ENUM | 2; + public static final int KM_TAG_KEY_SIZE = KM_INT | 3; + public static final int KM_TAG_BLOCK_MODE = KM_ENUM | 4; + public static final int KM_TAG_DIGEST = KM_ENUM | 5; + public static final int KM_TAG_MAC_LENGTH = KM_INT | 6; + public static final int KM_TAG_PADDING = KM_ENUM | 7; + public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 8; + public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 9; + + public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101; + public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102; + public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705; + + public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200; + public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201; + public static final int KM_TAG_DSA_P = KM_BIGNUM | 202; + public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203; + public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400; + public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401; + public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402; + public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_INT | 403; + public static final int KM_TAG_MAX_USES_PER_BOOT = KM_INT | 404; + + public static final int KM_TAG_ALL_USERS = KM_BOOL | 500; + public static final int KM_TAG_USER_ID = KM_INT | 501; + public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 502; + public static final int KM_TAG_USER_AUTH_ID = KM_INT_REP | 503; + public static final int KM_TAG_AUTH_TIMEOUT = KM_INT | 504; + + public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; + public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; + + public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700; + public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701; + public static final int KM_TAG_ORIGIN = KM_ENUM | 702; + public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703; + public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704; + + public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000; + public static final int KM_TAG_NONCE = KM_BYTES | 1001; + public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002; + + // Algorithm values. + public static final int KM_ALGORITHM_RSA = 1; + public static final int KM_ALGORITHM_DSA = 2; + public static final int KM_ALGORITHM_ECDSA = 3; + public static final int KM_ALGORITHM_ECIES = 4; + public static final int KM_ALGORITHM_AES = 32; + public static final int KM_ALGORITHM_3DES = 33; + public static final int KM_ALGORITHM_SKIPJACK = 34; + public static final int KM_ALGORITHM_MARS = 48; + public static final int KM_ALGORITHM_RC6 = 49; + public static final int KM_ALGORITHM_SERPENT = 50; + public static final int KM_ALGORITHM_TWOFISH = 51; + public static final int KM_ALGORITHM_IDEA = 52; + public static final int KM_ALGORITHM_RC5 = 53; + public static final int KM_ALGORITHM_CAST5 = 54; + public static final int KM_ALGORITHM_BLOWFISH = 55; + public static final int KM_ALGORITHM_RC4 = 64; + public static final int KM_ALGORITHM_CHACHA20 = 65; + public static final int KM_ALGORITHM_HMAC = 128; + + // Block modes. + public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1; + public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED; + public static final int KM_MODE_CBC = 2; + public static final int KM_MODE_CBC_CTS = 3; + public static final int KM_MODE_CTR = 4; + public static final int KM_MODE_OFB = 5; + public static final int KM_MODE_CFB = 6; + public static final int KM_MODE_XTS = 7; + public static final int KM_MODE_FIRST_AUTHENTICATED = 32; + public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED; + public static final int KM_MODE_OCB = 33; + public static final int KM_MODE_CCM = 34; + public static final int KM_MODE_FIRST_MAC = 128; + public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC; + public static final int KM_MODE_POLY1305 = 129; + + // Padding modes. + public static final int KM_PAD_NONE = 1; + public static final int KM_PAD_RSA_OAEP = 2; + public static final int KM_PAD_RSA_PSS = 3; + public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4; + public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5; + public static final int KM_PAD_ANSI_X923 = 32; + public static final int KM_PAD_ISO_10126 = 33; + public static final int KM_PAD_ZERO = 64; + public static final int KM_PAD_PKCS7 = 65; + public static final int KM_PAD_ISO_7816_4 = 66; + + // Digest modes. + public static final int KM_DIGEST_NONE = 0; + public static final int KM_DIGEST_MD5 = 1; + public static final int KM_DIGEST_SHA1 = 2; + public static final int KM_DIGEST_SHA_2_224 = 3; + public static final int KM_DIGEST_SHA_2_256 = 4; + public static final int KM_DIGEST_SHA_2_384 = 5; + public static final int KM_DIGEST_SHA_2_512 = 6; + public static final int KM_DIGEST_SHA_3_256 = 7; + public static final int KM_DIGEST_SHA_3_384 = 8; + public static final int KM_DIGEST_SHA_3_512 = 9; + + // Key origins. + public static final int KM_ORIGIN_HARDWARE = 0; + public static final int KM_ORIGIN_SOFTWARE = 1; + public static final int KM_ORIGIN_IMPORTED = 2; + + // Key usability requirements. + public static final int KM_BLOB_STANDALONE = 0; + public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1; + + // Operation Purposes. + public static final int KM_PURPOSE_ENCRYPT = 0; + public static final int KM_PURPOSE_DECRYPT = 1; + public static final int KM_PURPOSE_SIGN = 2; + public static final int KM_PURPOSE_VERIFY = 3; + + // Key formats. + public static final int KM_KEY_FORMAT_X509 = 0; + public static final int KM_KEY_FORMAT_PKCS8 = 1; + public static final int KM_KEY_FORMAT_PKCS12 = 2; + public static final int KM_KEY_FORMAT_RAW = 3; + + // Error codes. + public static final int KM_ERROR_OK = 0; + public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1; + public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2; + public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3; + public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4; + public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5; + public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6; + public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7; + public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8; + public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9; + public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10; + public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11; + public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12; + public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13; + public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14; + public static final int KM_ERROR_INVALID_USER_ID = -15; + public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16; + public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17; + public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18; + public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19; + public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20; + public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21; + public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22; + public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23; + public static final int KM_ERROR_KEY_NOT_YET_VALID = -24; + public static final int KM_ERROR_KEY_EXPIRED = -25; + public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26; + public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27; + public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28; + public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29; + public static final int KM_ERROR_VERIFICATION_FAILED = -30; + public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31; + public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32; + public static final int KM_ERROR_INVALID_KEY_BLOB = -33; + public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34; + public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35; + public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36; + public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37; + public static final int KM_ERROR_INVALID_ARGUMENT = -38; + public static final int KM_ERROR_UNSUPPORTED_TAG = -39; + public static final int KM_ERROR_INVALID_TAG = -40; + public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41; + public static final int KM_ERROR_INVALID_RESCOPING = -42; + public static final int KM_ERROR_INVALID_DSA_PARAMS = -43; + public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44; + public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45; + public static final int KM_ERROR_OPERATION_CANCELLED = -46; + public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47; + public static final int KM_ERROR_SECURE_HW_BUSY = -48; + public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49; + public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50; + public static final int KM_ERROR_UNIMPLEMENTED = -100; + public static final int KM_ERROR_VERSION_MISMATCH = -101; + public static final int KM_ERROR_UNKNOWN_ERROR = -1000; + + public static int getTagType(int tag) { + return tag & (0xF << 28); + } +} diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java new file mode 100644 index 0000000..71797ae --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterIntArgument.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +class KeymasterIntArgument extends KeymasterArgument { + public final int value; + + public KeymasterIntArgument(int tag, int value) { + super(tag); + this.value = value; + } + + public KeymasterIntArgument(int tag, Parcel in) { + super(tag); + value = in.readInt(); + } + + @Override + public void writeValue(Parcel out) { + out.writeInt(value); + } +} diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java new file mode 100644 index 0000000..781b1ab --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterLongArgument.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +class KeymasterLongArgument extends KeymasterArgument { + public final long value; + + public KeymasterLongArgument(int tag, long value) { + super(tag); + this.value = value; + } + + public KeymasterLongArgument(int tag, Parcel in) { + super(tag); + value = in.readLong(); + } + + @Override + public void writeValue(Parcel out) { + out.writeLong(value); + } +} diff --git a/core/java/android/security/keymaster/OperationResult.aidl b/core/java/android/security/keymaster/OperationResult.aidl new file mode 100644 index 0000000..699e8d0 --- /dev/null +++ b/core/java/android/security/keymaster/OperationResult.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/* @hide */ +parcelable OperationResult; diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java new file mode 100644 index 0000000..ad54c96 --- /dev/null +++ b/core/java/android/security/keymaster/OperationResult.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Class for handling the parceling of return values from keymaster crypto operations + * (begin/update/finish). + * @hide + */ +public class OperationResult implements Parcelable { + public final int resultCode; + public final IBinder token; + public final int inputConsumed; + public final byte[] output; + + public static final Parcelable.Creator<OperationResult> CREATOR = new + Parcelable.Creator<OperationResult>() { + public OperationResult createFromParcel(Parcel in) { + return new OperationResult(in); + } + + public OperationResult[] newArray(int length) { + return new OperationResult[length]; + } + }; + + protected OperationResult(Parcel in) { + resultCode = in.readInt(); + token = in.readStrongBinder(); + inputConsumed = in.readInt(); + output = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(resultCode); + out.writeStrongBinder(token); + out.writeInt(inputConsumed); + out.writeByteArray(output); + } +} diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index d08ab46..fc0148f 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -194,4 +194,19 @@ interface IWindowSession { void onRectangleOnScreenRequested(IBinder token, in Rect rectangle); IWindowId getWindowId(IBinder window); + + /** + * When the system is dozing in a low-power partially suspended state, pokes a short + * lived wake lock and ensures that the display is ready to accept the next frame + * of content drawn in the window. + * + * This mechanism is bound to the window rather than to the display manager or the + * power manager so that the system can ensure that the window is actually visible + * and prevent runaway applications from draining the battery. This is similar to how + * FLAG_KEEP_SCREEN_ON works. + * + * This method is synchronous because it may need to acquire a wake lock before returning. + * The assumption is that this method will be called rather infrequently. + */ + void pokeDrawLock(IBinder window); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f392682..a5fa5ed 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -47,6 +47,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -838,6 +839,7 @@ public final class ViewRootImpl implements ViewParent, final int newDisplayState = mDisplay.getState(); if (oldDisplayState != newDisplayState) { mAttachInfo.mDisplayState = newDisplayState; + pokeDrawLockIfNeeded(); if (oldDisplayState != Display.STATE_UNKNOWN) { final int oldScreenState = toViewScreenState(oldDisplayState); final int newScreenState = toViewScreenState(newDisplayState); @@ -868,6 +870,19 @@ public final class ViewRootImpl implements ViewParent, } }; + void pokeDrawLockIfNeeded() { + final int displayState = mAttachInfo.mDisplayState; + if (mView != null && mAdded && mTraversalScheduled + && (displayState == Display.STATE_DOZE + || displayState == Display.STATE_DOZE_SUSPEND)) { + try { + mWindowSession.pokeDrawLock(mWindow); + } catch (RemoteException ex) { + // System server died, oh well. + } + } + } + @Override public void requestFitSystemWindows() { checkThread(); @@ -1042,6 +1057,7 @@ public final class ViewRootImpl implements ViewParent, scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); + pokeDrawLockIfNeeded(); } } @@ -3084,17 +3100,6 @@ public final class ViewRootImpl implements ViewParent, return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); } - private static void forceLayout(View view) { - view.forceLayout(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - forceLayout(group.getChildAt(i)); - } - } - } - private final static int MSG_INVALIDATE = 1; private final static int MSG_INVALIDATE_RECT = 2; private final static int MSG_DIE = 3; @@ -3228,10 +3233,6 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = true; } - if (mView != null) { - forceLayout(mView); - } - requestLayout(); } break; @@ -3246,9 +3247,6 @@ public final class ViewRootImpl implements ViewParent, mWinFrame.top = t; mWinFrame.bottom = t + h; - if (mView != null) { - forceLayout(mView); - } requestLayout(); } break; diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 78344ac..79ad6e3 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -423,8 +423,8 @@ public abstract class AbsSeekBar extends ProgressBar { } if (track != null) { - track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft, - h - mPaddingBottom - trackOffset - mPaddingTop); + final int trackWidth = w - mPaddingRight - mPaddingLeft; + track.setBounds(0, trackOffset, trackWidth, trackOffset + trackHeight); } if (thumb != null) { @@ -472,7 +472,6 @@ public abstract class AbsSeekBar extends ProgressBar { final Drawable background = getBackground(); if (background != null) { - final Rect bounds = thumb.getBounds(); final int offsetX = mPaddingLeft - mThumbOffset; final int offsetY = mPaddingTop; background.setHotspotBounds(left + offsetX, top + offsetY, diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 8601d2b..1ba11da 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4199,6 +4199,13 @@ public class Editor { } /** + * @return True iff (start, end) is a valid range within the text. + */ + private static boolean isValidRange(CharSequence text, int start, int end) { + return 0 <= start && start <= end && end <= text.length(); + } + + /** * An InputFilter that monitors text input to maintain undo history. It does not modify the * text being typed (and hence always returns null from the filter() method). */ @@ -4213,97 +4220,123 @@ public class Editor { public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (DEBUG_UNDO) { - Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ")"); - Log.d(TAG, "filter: dest=" + dest + " (" + dstart + "-" + dend + ")"); + Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ") " + + "dest=" + dest + " (" + dstart + "-" + dend + ")"); } final UndoManager um = mEditor.mUndoManager; if (um.isInUndo()) { - if (DEBUG_UNDO) Log.d(TAG, "*** skipping, currently performing undo/redo"); + if (DEBUG_UNDO) Log.d(TAG, "filter: skipping, currently performing undo/redo"); return null; } - um.beginUpdate("Edit text"); - TextModifyOperation op = um.getLastOperation( - TextModifyOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); - if (op != null) { - if (DEBUG_UNDO) Log.d(TAG, "Last op: range=(" + op.mRangeStart + "-" + op.mRangeEnd - + "), oldText=" + op.mOldText); - // See if we can continue modifying this operation. - if (op.mOldText == null) { - // The current operation is an add... are we adding more? We are adding - // more if we are either appending new text to the end of the last edit or - // completely replacing some or all of the last edit. - // TODO: This sequence doesn't work right: a, left-arrow, b, undo, undo. - // The two edits are incorrectly merged, so there is only one undo available. - if (start < end && ((dstart >= op.mRangeStart && dend <= op.mRangeEnd) - || (dstart == op.mRangeEnd && dend == op.mRangeEnd))) { - op.mRangeEnd = dstart + (end-start); - um.endUpdate(); - if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, mRangeEnd=" - + op.mRangeEnd); - return null; - } - } else { - // The current operation is a delete... can we delete more? - if (start == end && dend == op.mRangeStart-1) { - SpannableStringBuilder str; - if (op.mOldText instanceof SpannableString) { - str = (SpannableStringBuilder)op.mOldText; - } else { - str = new SpannableStringBuilder(op.mOldText); - } - str.insert(0, dest, dstart, dend); - op.mRangeStart = dstart; - op.mOldText = str; - um.endUpdate(); - if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, range=(" - + op.mRangeStart + "-" + op.mRangeEnd - + "), oldText=" + op.mOldText); - return null; - } - } + // Text filters run before input operations are applied. However, some input operations + // are invalid and will throw exceptions when applied. This is common in tests. Don't + // attempt to undo invalid operations. + if (!isValidRange(source, start, end) || !isValidRange(dest, dstart, dend)) { + if (DEBUG_UNDO) Log.d(TAG, "filter: invalid op"); + return null; + } - // Couldn't add to the current undo operation, need to start a new - // undo state for a new undo operation. - um.commitState(null); - um.setUndoLabel("Edit text"); + // Earlier filters can rewrite input to be a no-op, for example due to a length limit + // on an input field. Skip no-op changes. + if (start == end && dstart == dend) { + if (DEBUG_UNDO) Log.d(TAG, "filter: skipping no-op"); + return null; } - // Create a new undo state reflecting the operation being performed. - op = new TextModifyOperation(mEditor.mUndoOwner); - op.mRangeStart = dstart; - if (start < end) { - op.mRangeEnd = dstart + (end-start); + // Build a new operation with all the information from this edit. + EditOperation edit = new EditOperation(mEditor, source, start, end, dest, dstart, dend); + + // Fetch the last edit operation and attempt to merge in the new edit. + um.beginUpdate("Edit text"); + EditOperation lastEdit = um.getLastOperation( + EditOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); + if (lastEdit == null) { + // Add this as the first edit. + if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit); + um.addOperation(edit, UndoManager.MERGE_MODE_NONE); + } else if (lastEdit.mergeWith(edit)) { + // Merge succeeded, nothing else to do. + if (DEBUG_UNDO) Log.d(TAG, "filter: merge succeeded, created " + lastEdit); } else { - op.mRangeEnd = dstart; + // Could not merge with the last edit, so commit the last edit and add this edit. + if (DEBUG_UNDO) Log.d(TAG, "filter: merge failed, adding " + edit); + um.commitState(mEditor.mUndoOwner); + um.addOperation(edit, UndoManager.MERGE_MODE_NONE); } - if (dstart < dend) { - op.mOldText = dest.subSequence(dstart, dend); - } - if (DEBUG_UNDO) Log.d(TAG, "*** adding new op, range=(" + op.mRangeStart - + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); - um.addOperation(op, UndoManager.MERGE_MODE_NONE); um.endUpdate(); - return null; + return null; // Text not changed. } } /** * An operation to undo a single "edit" to a text view. */ - public static class TextModifyOperation extends UndoOperation<Editor> { - int mRangeStart, mRangeEnd; - CharSequence mOldText; + public static class EditOperation extends UndoOperation<Editor> { + private static final int TYPE_INSERT = 0; + private static final int TYPE_DELETE = 1; + private static final int TYPE_REPLACE = 2; - public TextModifyOperation(UndoOwner owner) { - super(owner); + private int mType; + private String mOldText; + private int mOldTextStart; + private String mNewText; + private int mNewTextStart; + + private int mOldCursorPos; + private int mNewCursorPos; + + /** + * Constructs an edit operation from a text input operation that replaces the range + * (dstart, dend) of dest with (start, end) of source. See {@link InputFilter#filter}. + */ + public EditOperation(Editor editor, CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + super(editor.mUndoOwner); + + mOldText = dest.subSequence(dstart, dend).toString(); + mNewText = source.subSequence(start, end).toString(); + + // Determine the type of the edit and store where it occurred. Avoid storing + // irrevelant data (e.g. mNewTextStart for a delete) because that makes the + // merging logic more complex (e.g. merging deletes could lead to mNewTextStart being + // outside the bounds of the final text). + if (mNewText.length() > 0 && mOldText.length() == 0) { + mType = TYPE_INSERT; + mNewTextStart = dstart; + } else if (mNewText.length() == 0 && mOldText.length() > 0) { + mType = TYPE_DELETE; + mOldTextStart = dstart; + } else { + mType = TYPE_REPLACE; + mOldTextStart = mNewTextStart = dstart; + } + + // Store cursor data. + mOldCursorPos = editor.mTextView.getSelectionStart(); + mNewCursorPos = dstart + (end - start); } - public TextModifyOperation(Parcel src, ClassLoader loader) { + public EditOperation(Parcel src, ClassLoader loader) { super(src, loader); - mRangeStart = src.readInt(); - mRangeEnd = src.readInt(); - mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); + mType = src.readInt(); + mOldText = src.readString(); + mOldTextStart = src.readInt(); + mNewText = src.readString(); + mNewTextStart = src.readInt(); + mOldCursorPos = src.readInt(); + mNewCursorPos = src.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeString(mOldText); + dest.writeInt(mOldTextStart); + dest.writeString(mNewText); + dest.writeInt(mNewTextStart); + dest.writeInt(mOldCursorPos); + dest.writeInt(mNewCursorPos); } @Override @@ -4312,62 +4345,139 @@ public class Editor { @Override public void undo() { - swapText(); + if (DEBUG_UNDO) Log.d(TAG, "undo"); + // Remove the new text and insert the old. + modifyText(mNewTextStart, getNewTextEnd(), mOldText, mOldTextStart, mOldCursorPos); } @Override public void redo() { - swapText(); + if (DEBUG_UNDO) Log.d(TAG, "redo"); + // Remove the old text and insert the new. + modifyText(mOldTextStart, getOldTextEnd(), mNewText, mNewTextStart, mNewCursorPos); } - private void swapText() { - // Both undo and redo involves swapping the contents of the range - // in the text view with our local text. - Editor editor = getOwnerData(); - Editable editable = (Editable)editor.mTextView.getText(); - CharSequence curText; - if (mRangeStart >= mRangeEnd) { - curText = null; - } else { - curText = editable.subSequence(mRangeStart, mRangeEnd); + /** + * Attempts to merge this existing operation with a new edit. + * @param edit The new edit operation. + * @return If the merge succeeded, returns true. Otherwise returns false and leaves this + * object unchanged. + */ + private boolean mergeWith(EditOperation edit) { + switch (mType) { + case TYPE_INSERT: + return mergeInsertWith(edit); + case TYPE_DELETE: + return mergeDeleteWith(edit); + case TYPE_REPLACE: + return mergeReplaceWith(edit); + default: + return false; } - if (DEBUG_UNDO) { - Log.d(TAG, "Swap: range=(" + mRangeStart + "-" + mRangeEnd - + "), oldText=" + mOldText); - Log.d(TAG, "Swap: curText=" + curText); + } + + private boolean mergeInsertWith(EditOperation edit) { + if (DEBUG_UNDO) Log.d(TAG, "mergeInsertWith " + edit); + // Only merge continuous insertions. + if (edit.mType != TYPE_INSERT) { + return false; } - if (mOldText == null) { - editable.delete(mRangeStart, mRangeEnd); - mRangeEnd = mRangeStart; - } else { - editable.replace(mRangeStart, mRangeEnd, mOldText); - mRangeEnd = mRangeStart + mOldText.length(); + // Only merge insertions that are contiguous. + if (getNewTextEnd() != edit.mNewTextStart) { + return false; } - mOldText = curText; + mNewText += edit.mNewText; + mNewCursorPos = edit.mNewCursorPos; + return true; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRangeStart); - dest.writeInt(mRangeEnd); - TextUtils.writeToParcel(mOldText, dest, flags); + // TODO: Support forward delete. + private boolean mergeDeleteWith(EditOperation edit) { + if (DEBUG_UNDO) Log.d(TAG, "mergeDeleteWith " + edit); + // Only merge continuous deletes. + if (edit.mType != TYPE_DELETE) { + return false; + } + // Only merge deletions that are contiguous. + if (mOldTextStart != edit.getOldTextEnd()) { + return false; + } + mOldTextStart = edit.mOldTextStart; + mOldText = edit.mOldText + mOldText; + mNewCursorPos = edit.mNewCursorPos; + return true; } - public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR - = new Parcelable.ClassLoaderCreator<TextModifyOperation>() { + private boolean mergeReplaceWith(EditOperation edit) { + if (DEBUG_UNDO) Log.d(TAG, "mergeReplaceWith " + edit); + // Replacements can merge only with adjacent inserts and adjacent replacements. + if (edit.mType == TYPE_DELETE || + getNewTextEnd() != edit.mOldTextStart || + edit.mOldTextStart != edit.mNewTextStart) { + return false; + } + mOldText += edit.mOldText; + mNewText += edit.mNewText; + mNewCursorPos = edit.mNewCursorPos; + return true; + } + + private int getNewTextEnd() { + return mNewTextStart + mNewText.length(); + } + + private int getOldTextEnd() { + return mOldTextStart + mOldText.length(); + } + + private void modifyText(int deleteFrom, int deleteTo, CharSequence newText, + int newTextInsertAt, int newCursorPos) { + Editor editor = getOwnerData(); + Editable text = (Editable) editor.mTextView.getText(); + // Apply the edit if it is still valid. + if (isValidRange(text, deleteFrom, deleteTo) && + newTextInsertAt <= text.length() - (deleteTo - deleteFrom)) { + if (deleteFrom != deleteTo) { + text.delete(deleteFrom, deleteTo); + } + if (newText.length() != 0) { + text.insert(newTextInsertAt, newText); + } + } + // Restore the cursor position. + // TODO: Select all the text that was undone. + if (newCursorPos <= text.length()) { + Selection.setSelection(text, newCursorPos); + } + } + + @Override + public String toString() { + return "EditOperation: [" + + "mType=" + mType + ", " + + "mOldText=" + mOldText + ", " + + "mOldTextStart=" + mOldTextStart + ", " + + "mNewText=" + mNewText + ", " + + "mNewTextStart=" + mNewTextStart + ", " + + "mOldCursorPos=" + mOldCursorPos + ", " + + "mNewCursorPos=" + mNewCursorPos + "]"; + } + + public static final Parcelable.ClassLoaderCreator<EditOperation> CREATOR + = new Parcelable.ClassLoaderCreator<EditOperation>() { @Override - public TextModifyOperation createFromParcel(Parcel in) { - return new TextModifyOperation(in, null); + public EditOperation createFromParcel(Parcel in) { + return new EditOperation(in, null); } @Override - public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) { - return new TextModifyOperation(in, loader); + public EditOperation createFromParcel(Parcel in, ClassLoader loader) { + return new EditOperation(in, loader); } @Override - public TextModifyOperation[] newArray(int size) { - return new TextModifyOperation[size]; + public EditOperation[] newArray(int size) { + return new EditOperation[size]; } }; } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 406a274..03878fc 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -401,36 +401,49 @@ public class ProgressBar extends View { * traverse layer and state list drawables. */ private Drawable tileify(Drawable drawable, boolean clip) { + // TODO: This is a terrible idea that potentially destroys any drawable + // that extends any of these classes. We *really* need to remove this. if (drawable instanceof LayerDrawable) { - LayerDrawable background = (LayerDrawable) drawable; - final int N = background.getNumberOfLayers(); - Drawable[] outDrawables = new Drawable[N]; + final LayerDrawable orig = (LayerDrawable) drawable; + final int N = orig.getNumberOfLayers(); + final Drawable[] outDrawables = new Drawable[N]; for (int i = 0; i < N; i++) { - int id = background.getId(i); - outDrawables[i] = tileify(background.getDrawable(i), + final int id = orig.getId(i); + outDrawables[i] = tileify(orig.getDrawable(i), (id == R.id.progress || id == R.id.secondaryProgress)); } - LayerDrawable newBg = new LayerDrawable(outDrawables); - + final LayerDrawable clone = new LayerDrawable(outDrawables); for (int i = 0; i < N; i++) { - newBg.setId(i, background.getId(i)); + clone.setId(i, orig.getId(i)); + clone.setLayerGravity(i, orig.getLayerGravity(i)); + clone.setLayerWidth(i, orig.getLayerWidth(i)); + clone.setLayerHeight(i, orig.getLayerHeight(i)); + clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i)); + clone.setLayerInsetRight(i, orig.getLayerInsetRight(i)); + clone.setLayerInsetTop(i, orig.getLayerInsetTop(i)); + clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i)); + clone.setLayerInsetStart(i, orig.getLayerInsetStart(i)); + clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i)); } - return newBg; + return clone; + } - } else if (drawable instanceof StateListDrawable) { - StateListDrawable in = (StateListDrawable) drawable; - StateListDrawable out = new StateListDrawable(); - int numStates = in.getStateCount(); - for (int i = 0; i < numStates; i++) { + if (drawable instanceof StateListDrawable) { + final StateListDrawable in = (StateListDrawable) drawable; + final StateListDrawable out = new StateListDrawable(); + final int N = in.getStateCount(); + for (int i = 0; i < N; i++) { out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); } + return out; + } - } else if (drawable instanceof BitmapDrawable) { + if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmap = (BitmapDrawable) drawable; final Bitmap tileBitmap = bitmap.getBitmap(); if (mSampleTile == null) { @@ -1648,7 +1661,7 @@ public class ProgressBar extends View { // rotates properly in its animation final int saveCount = canvas.save(); - if(isLayoutRtl() && mMirrorForRtl) { + if (isLayoutRtl() && mMirrorForRtl) { canvas.translate(getWidth() - mPaddingRight, mPaddingTop); canvas.scale(-1.0f, 1.0f); } else { @@ -1680,20 +1693,23 @@ public class ProgressBar extends View { @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - Drawable d = mCurrentDrawable; - int dw = 0; int dh = 0; + + final Drawable d = mCurrentDrawable; if (d != null) { dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); } + updateDrawableState(); + dw += mPaddingLeft + mPaddingRight; dh += mPaddingTop + mPaddingBottom; - setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), - resolveSizeAndState(dh, heightMeasureSpec, 0)); + final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); + final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); + setMeasuredDimension(measuredWidth, measuredHeight); } @Override @@ -1703,7 +1719,7 @@ public class ProgressBar extends View { } private void updateDrawableState() { - int[] state = getDrawableState(); + final int[] state = getDrawableState(); if (mProgressDrawable != null && mProgressDrawable.isStateful()) { mProgressDrawable.setState(state); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9297731..2d0a9cb 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8412,6 +8412,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return onTextContextMenuItem(ID_REDO); } break; + case KeyEvent.KEYCODE_V: + if (canPaste()) { + return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); + } } } return super.onKeyShortcut(keyCode, event); @@ -8794,6 +8798,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener static final int ID_CUT = android.R.id.cut; static final int ID_COPY = android.R.id.copy; static final int ID_PASTE = android.R.id.paste; + static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; /** * Called when a context menu option for the text view is selected. Currently @@ -8834,7 +8839,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; // Returns true even if nothing was undone. case ID_PASTE: - paste(min, max); + paste(min, max, true /* withFormatting */); + return true; + + case ID_PASTE_AS_PLAIN_TEXT: + paste(min, max, false /* withFormatting */); return true; case ID_CUT: @@ -9018,14 +9027,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Paste clipboard content between min and max positions. */ - private void paste(int min, int max) { + private void paste(int min, int max, boolean withFormatting) { ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { boolean didFirst = false; for (int i=0; i<clip.getItemCount(); i++) { - CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext()); + final CharSequence paste; + if (withFormatting) { + paste = clip.getItemAt(i).coerceToStyledText(getContext()); + } else { + // Get an item as text and remove all spans by toString(). + final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); + paste = (text instanceof Spanned) ? text.toString() : text; + } if (paste != null) { if (!didFirst) { Selection.setSelection((Spannable) mText, max); @@ -9705,4 +9721,4 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener TextView.this.spanChange(buf, what, s, -1, e, -1); } } -} +}
\ No newline at end of file diff --git a/core/res/res/color/white_disabled_material.xml b/core/res/res/color/white_disabled_material.xml new file mode 100644 index 0000000..c61f900 --- /dev/null +++ b/core/res/res/color/white_disabled_material.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/white" + android:alpha="?attr/disabledAlpha" /> +</selector> diff --git a/core/res/res/drawable-hdpi/progress_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/progress_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index fbb2e0c..0000000 --- a/core/res/res/drawable-hdpi/progress_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/progress_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/progress_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 92d4b05..0000000 --- a/core/res/res/drawable-mdpi/progress_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/progress_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/progress_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index e3c4eeb..0000000 --- a/core/res/res/drawable-xhdpi/progress_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/progress_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/progress_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 452f45c..0000000 --- a/core/res/res/drawable-xxhdpi/progress_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable/progress_horizontal_material.xml b/core/res/res/drawable/progress_horizontal_material.xml index 6b64337..c179564 100644 --- a/core/res/res/drawable/progress_horizontal_material.xml +++ b/core/res/res/drawable/progress_horizontal_material.xml @@ -15,22 +15,32 @@ --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@id/background"> - <nine-patch android:src="@drawable/progress_mtrl_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> + <item android:id="@id/background" + android:gravity="center_vertical|fill_horizontal"> + <shape android:shape="rectangle" + android:tint="?attr/colorControlNormal"> + <size android:height="@dimen/progress_bar_height_material" /> + <solid android:color="@color/white_disabled_material" /> + </shape> </item> - <item android:id="@id/secondaryProgress"> + <item android:id="@id/secondaryProgress" + android:gravity="center_vertical|fill_horizontal"> <scale android:scaleWidth="100%"> - <nine-patch android:src="@drawable/progress_mtrl_alpha" - android:tint="?attr/colorControlActivated" - android:alpha="?attr/disabledAlpha" /> + <shape android:shape="rectangle" + android:tint="?attr/colorControlActivated"> + <size android:height="@dimen/progress_bar_height_material" /> + <solid android:color="@color/white_disabled_material" /> + </shape> </scale> </item> - <item android:id="@id/progress"> + <item android:id="@id/progress" + android:gravity="center_vertical|fill_horizontal"> <scale android:scaleWidth="100%"> - <nine-patch android:src="@drawable/progress_mtrl_alpha" - android:tint="?attr/colorControlActivated" /> + <shape android:shape="rectangle" + android:tint="?attr/colorControlActivated"> + <size android:height="@dimen/progress_bar_height_material" /> + <solid android:color="@color/white" /> + </shape> </scale> </item> </layer-list> diff --git a/core/res/res/drawable/scrubber_progress_horizontal_material.xml b/core/res/res/drawable/scrubber_progress_horizontal_material.xml index 89a1787..86a85c3 100644 --- a/core/res/res/drawable/scrubber_progress_horizontal_material.xml +++ b/core/res/res/drawable/scrubber_progress_horizontal_material.xml @@ -15,32 +15,42 @@ --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@id/background"> - <nine-patch android:src="@drawable/scrubber_track_mtrl_alpha" - android:tint="?attr/colorControlNormal" /> + <item android:id="@id/background" + android:gravity="center_vertical|fill_horizontal"> + <shape android:shape="rectangle" + android:tint="?attr/colorControlNormal"> + <size android:height="@dimen/scrubber_track_height_material" /> + <solid android:color="@color/white_disabled_material" /> + </shape> </item> - <item android:id="@id/secondaryProgress"> + <item android:id="@id/secondaryProgress" + android:gravity="center_vertical|fill_horizontal"> <scale android:scaleWidth="100%"> <selector> - <item android:state_enabled="false"> - <color android:color="@color/transparent" /> - </item> + <item android:state_enabled="false" + android:drawable="@color/transparent" /> <item> - <nine-patch android:src="@drawable/scrubber_primary_mtrl_alpha" - android:tint="?attr/colorControlNormal" /> + <shape android:shape="rectangle" + android:tint="?attr/colorControlActivated"> + <size android:height="@dimen/scrubber_track_height_material" /> + <solid android:color="@color/white_disabled_material" /> + </shape> </item> </selector> </scale> </item> - <item android:id="@id/progress"> + <item android:id="@id/progress" + android:gravity="center_vertical|fill_horizontal"> <scale android:scaleWidth="100%"> <selector> - <item android:state_enabled="false"> - <color android:color="@color/transparent" /> - </item> + <item android:state_enabled="false" + android:drawable="@color/transparent" /> <item> - <nine-patch android:src="@drawable/scrubber_primary_mtrl_alpha" - android:tint="?attr/colorControlActivated" /> + <shape android:shape="rectangle" + android:tint="?attr/colorControlActivated"> + <size android:height="@dimen/progress_bar_height_material" /> + <solid android:color="@color/white" /> + </shape> </item> </selector> </scale> diff --git a/core/res/res/drawable/switch_track_material.xml b/core/res/res/drawable/switch_track_material.xml index a825fe4..8b028d3 100644 --- a/core/res/res/drawable/switch_track_material.xml +++ b/core/res/res/drawable/switch_track_material.xml @@ -16,12 +16,12 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:gravity="center_vertical|fill_horizontal" - android:start="2dp" - android:end="2dp"> + android:start="4dp" + android:end="4dp"> <shape android:shape="rectangle" android:tint="@color/switch_track_material"> <corners android:radius="7dp" /> - <solid android:color="#4dffffff" /> + <solid android:color="@color/white_disabled_material" /> <size android:height="14dp" /> </shape> </item> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 51c6a66..4c0520e 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1852,6 +1852,12 @@ <item>users</item> </string-array> + <!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully + flushed to the display while dozing. This value needs to be large enough + to account for processing and rendering time plus a frame or two of latency + in the display pipeline plus some slack just to be sure. --> + <integer name="config_drawLockTimeoutMillis">120</integer> + <!-- default telephony hardware configuration for this platform. --> <!-- this string array should be overridden by the device to present a list diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c418dc3..d240047 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -31,7 +31,7 @@ <integer name="max_action_buttons">2</integer> <dimen name="toast_y_offset">64dip</dimen> <!-- Height of the status bar --> - <dimen name="status_bar_height">25dip</dimen> + <dimen name="status_bar_height">24dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_height">48dp</dimen> <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index 0e2c480..b84249a 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -115,5 +115,6 @@ <!-- Padding above and below selection dialog lists. --> <dimen name="dialog_list_padding_vertical_material">8dp</dimen> + <dimen name="scrubber_track_height_material">2dp</dimen> <dimen name="progress_bar_height_material">4dp</dimen> </resources> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 1f4d37c..b6e79ad 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -91,4 +91,5 @@ <item type="id" name="navigationBarBackground" /> <item type="id" name="undo" /> <item type="id" name="redo" /> + <item type="id" name="pasteAsPlainText" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index af8ff41..af4922f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2639,5 +2639,8 @@ <public type="id" name="undo" /> <!-- Context menu ID for the "Redo" menu item to redo the last text edit operation. --> <public type="id" name="redo" /> + <!-- Context menu ID for the "Paste as plain text" menu item to to copy the current contents + of the clipboard into the text view without formatting. --> + <public type="id" name="pasteAsPlainText" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index ff03ca9..d95b17e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3909,6 +3909,8 @@ <string name="usb_mtp_notification_title">Connected as a media device</string> <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in PTP mode. This is the title --> <string name="usb_ptp_notification_title">Connected as a camera</string> + <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in MIDI mode. This is the title --> + <string name="usb_midi_notification_title">Connected as a MIDI device</string> <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in mass storage mode (for installer CD image). This is the title --> <string name="usb_cd_installer_notification_title">Connected as an installer</string> <!-- USB_PREFERENCES: Notification for when a USB accessory is attached. This is the title --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 241bd51..615f445 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -345,6 +345,7 @@ <java-symbol type="integer" name="config_bluetooth_max_advertisers" /> <java-symbol type="integer" name="config_bluetooth_max_scan_filters" /> <java-symbol type="integer" name="config_cursorWindowSize" /> + <java-symbol type="integer" name="config_drawLockTimeoutMillis" /> <java-symbol type="integer" name="config_doublePressOnPowerBehavior" /> <java-symbol type="integer" name="config_extraFreeKbytesAdjust" /> <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" /> @@ -1750,6 +1751,7 @@ <java-symbol type="string" name="usb_notification_message" /> <java-symbol type="string" name="use_physical_keyboard" /> <java-symbol type="string" name="usb_ptp_notification_title" /> + <java-symbol type="string" name="usb_midi_notification_title" /> <java-symbol type="string" name="vpn_text" /> <java-symbol type="string" name="vpn_text_long" /> <java-symbol type="string" name="vpn_title" /> diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml index 1e32d43..94e6469 100644 --- a/docs/html/_redirects.yaml +++ b/docs/html/_redirects.yaml @@ -396,4 +396,8 @@ redirects: to: /distribute/googleplay/edu/index.html - from: /preview/google-play-services-wear.html - to: /training/building-wearables.html
\ No newline at end of file + to: /training/building-wearables.html + +# ---------- DISTRIBUTE ------------------ +- from /distribute/tools/promote/badge-files.html + to: /distribute/tools/promote/badges.html diff --git a/docs/html/distribute/tools/promote/badge-files.jd b/docs/html/distribute/tools/promote/badge-files.jd deleted file mode 100644 index e65e698..0000000 --- a/docs/html/distribute/tools/promote/badge-files.jd +++ /dev/null @@ -1,289 +0,0 @@ -page.title=Google Play Badge Files -page.image=/images/gp-badges-set.png -page.metaDescription=Download hi-res assets for localized Google Play badges. -page.tags="badge, google play" - -@jd:body - -<style> -table tr td {border:0} -</style> - -<p>The following links provide the Adobe® Illustrator® (.ai) file for the -two Google Play badges.</p> - - -<img src="{@docRoot}images/brand/en_generic_rgb_wo_60.png" alt="Get It On Google Play"> - -<div style="clear:left"> </div> - -<div class="col-4" style="margin-left:0"> - - <a href="{@docRoot}downloads/brand/v2/english_get.ai">English (English)</a><br/> - - <a href="{@docRoot}downloads/brand/af_generic_rgb_wo.ai">Afrikaans (Afrikaans)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/amharic_get.ai">ኣማርኛ (Amharic)</a><br/> - -<!-- - <a href="{@docRoot}downloads/brand/ar_generic_rgb_wo.ai">العربية (Arabic)</a><br/> ---> - <a href="{@docRoot}downloads/brand/v2/belarusian_get.ai">Беларуская (Belarusian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/bulgarian_get.ai">български (Bulgarian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/catalan_get.ai">Català (Catalan)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/traditional_chinese_get.ai">中文 (中国) (Chinese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hongkong_chinese_get.ai">中文(香港) (Chinese Hong Kong)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/taiwan_chinese_get.ai">中文 (台灣) (Chinese Taiwan)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/croatian_get.ai">Hrvatski (Croatian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/czech_get.ai">Česky (Czech)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/danish_get.ai">Dansk (Danish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/dutch_get.ai">Nederlands (Dutch)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/estonian_get.ai">Eesti keel (Estonian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/farsi_get.ai">فارسی (Farsi Persian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/filipino_get.ai">Tagalog (Filipino)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/finnish_get.ai">Suomi (Finnish)</a><br/> - -</div> - -<div class="col-4"> - - <a href="{@docRoot}downloads/brand/v2/french_get.ai">Français (French)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/german_get.ai">Deutsch (German)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/greek_get.ai">Ελληνικά (Greek)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hebrew_get.ai">עברית (Hebrew)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hindi_get.ai">हिन्दी (Hindi)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hungarian_get.ai">Magyar (Hungarian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/indonesian_get.ai">Bahasa Indonesia (Indonesian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/italian_get.ai">Italiano (Italian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/japanese_get.ai">日本語 (Japanese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/kazakh_get.ai">Қазақ тілі (Kazakh)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/korean_get.ai">한국어 (Korean)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/latvian_get.ai">Latviski (Latvian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/lithuanian_get.ai">Lietuviškai (Lithuanian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/malay_get.ai">Bahasa Melayu (Malay)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/norwegian_get.ai">Norsk (Norwegian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/polish_get.ai">Polski (Polish)</a><br/> - -</div> - -<div class="col-4" style="margin-right:0"> - - <a href="{@docRoot}downloads/brand/v2/portugal_portuguese_get.ai">Português (Portuguese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/brazilian_portuguese_get.ai">Português Brasil (Portuguese Brazil)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/romanian_get.ai">Românã (Romanian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/russian_get.ai">Pусский (Russian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/serbian_get.ai">Српски / srpski (Serbian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/slovak_get.ai">Slovenčina (Slovak)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/slovenian_get.ai">Slovenščina (Slovenian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/spanish_get.ai">Español (Spanish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/spanish_latam_get.ai">Español Latinoamérica (Spanish Latin America)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/swahili_get.ai">Kiswahili (Swahili)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/swedish_get.ai">Svenska (Swedish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/thai_get.ai">ภาษาไทย (Thai)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/turkish_get.ai">Türkçe (Turkish)</a><br/> - - <a href="{@docRoot}downloads/brand/uk_generic_rgb_wo.ai">Українська (Ukrainian)</a><br/> - <a href="{@docRoot}downloads/brand/vi_generic_rgb_wo.ai">Tiếng Việt (Vietnamese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/zulu_get.ai">isiZulu (Zulu)</a><br/> - -</div> -<div style="clear:left"> </div> - - - - - - - - - - -<img src="{@docRoot}images/brand/en_app_rgb_wo_60.png" alt="Android App On Google Play"> - -<div style="clear:left"> </div> - -<div class="col-4" style="margin-left:0"> - - <a href="{@docRoot}downloads/brand/v2/english_app.ai">English (English)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/afrikaans_app.ai">Afrikaans (Afrikaans)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/amharic_app.ai">ኣማርኛ (Amharic)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/arabic_app.ai">العربية (Arabic)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/belarusian_app.ai">Беларуская (Belarusian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/bulgarian_app.ai">български (Bulgarian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/catalan_app.ai">Català (Catalan)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/traditional_chinese_app.ai">中文 (中国) (Chinese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hongkong_chinese_app.ai">中文(香港) (Chinese Hong Kong)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/taiwan_chinese_app.ai">中文 (台灣) (Chinese Taiwan)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/croatian_app.ai">Hrvatski (Croatian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/czech_app.ai">Česky (Czech)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/danish_app.ai">Dansk (Danish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/dutch_app.ai">Nederlands (Dutch)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/estonian_app.ai">Eesti keel (Estonian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/farsi_app.ai">فارسی (Farsi Persian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/filipino_app.ai">Tagalog (Filipino)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/finnish_app.ai">Suomi (Finnish)</a><br/> - -</div> - -<div class="col-4"> - - <a href="{@docRoot}downloads/brand/v2/french_app.ai">Français (French)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/german_app.ai">Deutsch (German)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/greek_app.ai">Ελληνικά (Greek)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hebrew_app.ai">עברית (Hebrew)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hindi_app.ai">हिन्दी (Hindi)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/hungarian_app.ai">Magyar (Hungarian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/indonesian_app.ai">Bahasa Indonesia (Indonesian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/italian_app.ai">Italiano (Italian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/japanese_app.ai">日本語 (Japanese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/korean_app.ai">한국어 (Korean)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/latvian_app.ai">Latviski (Latvian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/lithuanian_app.ai">Lietuviškai (Lithuanian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/malay_app.ai">Bahasa Melayu (Malay)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/norwegian_app.ai">Norsk (Norwegian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/polish_app.ai">Polski (Polish)</a><br/> - - -</div> - -<div class="col-4" style="margin-right:0"> - - <a href="{@docRoot}downloads/brand/v2/portugal_portuguese_app.ai">Português (Portuguese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/brazilian_portuguese_app.ai">Português Brasil (Portuguese Brazil)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/romanian_app.ai">Românã (Romanian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/russian_app.ai">Pусский (Russian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/serbian_app.ai">Српски / srpski (Serbian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/slovak_app.ai">Slovenčina (Slovak)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/slovenian_app.ai">Slovenščina (Slovenian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/spanish_app.ai">Español (Spanish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/spanish_latam_app.ai">Español Latinoamérica (Spanish Latin America)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/swahili_app.ai">Kiswahili (Swahili)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/swedish_app.ai">Svenska (Swedish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/thai_app.ai">ภาษาไทย (Thai)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/turkish_app.ai">Türkçe (Turkish)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/ukranian_app.ai">Українська (Ukrainian)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/vietnamese_app.ai">Tiếng Việt (Vietnamese)</a><br/> - - <a href="{@docRoot}downloads/brand/v2/zulu_app.ai">isiZulu (Zulu)</a><br/> - -</div> -<div style="clear:left"> </div> - - - - - - -<h2>Guidelines</h2> - - <ul> - <li>Do not modify the color, proportions, spacing or any other aspect of the badge image. - </li> - <li>When used alongside logos for other application marketplaces, the Google Play logo - should be of equal or greater size.</li> - <li>When used online, the badge should link to either: - <ul> - <li>A list of products published by you, for example:<br /> - <span style="margin-left:1em;">http://play.google.com/store/search?q=<em>publisherName</em></span> - </li> - <li>A specific app product details page within Google Play, for example:<br /> - <span style="margin-left:1em;">http://play.google.com/store/apps/details?id=<em>packageName</em></span> - </li> - </ul> - </li> - </ul> - -<p>For more information, see the -<a href="{@docRoot}distribute/tools/promote/brand.html#brand-google_play">Brand -Guidelines</a>. - - -<p>To quickly create a badge that links to your apps on Google Play, -use the <a -href="{@docRoot}distribute/tools/promote/badges.html">Googe Play badge generator</a>.</p> diff --git a/docs/html/distribute/tools/promote/brand.jd b/docs/html/distribute/tools/promote/brand.jd index 22441df..a12e753 100644 --- a/docs/html/distribute/tools/promote/brand.jd +++ b/docs/html/distribute/tools/promote/brand.jd @@ -5,7 +5,7 @@ page.tags="brand, bugdroid, assets" @jd:body -<p>We encourage you to use the Android and Google Play brands with your Android app +<p>We encourage you to use the Android brand with your Android app promotional materials. You can use the icons and other assets on this page provided that you follow the guidelines.</p> @@ -89,47 +89,9 @@ used according to terms described in the Creative Commons 3.0 Attribution Licens <p>The custom typeface may not be used.</p> -<h2 id="brand-google_play">Google Play</h2> +<p>The following are guidelines for the Google Play brand.</p> - - <p>The following are guidelines for the Google Play brand - and related assets.</p> - -<h4>Google Play in text</h4> - -<p>Always include a TM symbol on the first or most prominent instance of Google Play™ -in text.</p> - -<p>When referring to the mobile experience, use "Google Play" unless the text is clearly -instructional for the user. For example, a marketing headline might read "Download our -games on Google Play™," but instructional text would read "Download our games using the Google -Play™ store app." - - <p>Any use of the Google Play name or icon needs to include this - attribution in your communication:</p> - -<blockquote><em>Google Play is a trademark of Google Inc.</em></blockquote> - - - <div style="float:right;width:96px;margin-left:30px;margin-top:-20px"> - <img src="{@docRoot}images/brand/Google_Play_Store_96.png" alt=""> - <p style="text-align:center"> - <a href="{@docRoot}images/brand/Google_Play_Store_48.png">48x48</a> | - <a href="{@docRoot}images/brand/Google_Play_Store_96.png">96x96</a><br> - <a href="{@docRoot}images/brand/Google_Play_Store_600.png">600x576</a> - </p> - </div> - -<h4>Google Play store icon</h4> - -<p>You may use the Google Play store icon, but you may not modify it.</p> - -<p>As mentioned above, when referring to the Google Play store app in copy, use the full name: -"Google Play store." However, when labeling the Google Play store icon directly, it's OK to use -"Play Store" alone to accurately reflect the icon label as it appears on a device.</p> - - -<h4>Google Play badge</h4> +<h4>Google Play™ Badges</h4> <div style="float:right;clear:right;width:172px;margin-left:30px"> <img src="{@docRoot}images/brand/en_app_rgb_wo_60.png" alt=""> @@ -145,21 +107,21 @@ Play™ store app." <a href="{@docRoot}images/brand/en_generic_rgb_wo_60.png">172x60</a></p> </div> - <p>The "Get it on Google Play" and "Android App on Google Play" logos are - badges that you can use on your website and promotional materials, to point - to your products on Google Play. Additional Google Play badge formats and + <p>Use the "Get it on Google Play" and "Android App on Google Play" badges on your website and + promotional materials to point to your products on Google Play. These badges are both available + in over 40 languages. Additional Google Play badge formats and badges for music, books, magazines, movies, and TV shows are also available. Use the <a href="https://support.google.com/googleplay/contact/brand_developer">Android and Google Play Brand Permissions Inquiry form</a> to request those badges.</p> + <p>Google Play badge guidelines:</p> <ul> - <li>Don't modify the color, proportions, spacing, or any other aspect of the badge image. - </li> - <li>When used alongside logos for other application marketplaces, the Google Play logo + <li>Don't modify the color, proportions, spacing, or any other aspect of the badge.</li> + <li>When used alongside logos for other application marketplaces, the Google Play badge should be of equal or greater size.</li> - <li>When used online, the badge should link to either: + <li>When used online, the badge should link to either:</li> <ul> <li>A list of products published by you, for example:<br /> <span style="margin-left:1em;">http://play.google.com/store/search?q=<em>publisherName</em></span> @@ -168,24 +130,62 @@ Play™ store app." <span style="margin-left:1em;">http://play.google.com/store/apps/details?id=<em>packageName</em></span> </li> </ul> + <li>You do not need to include a legal attribution if you are only using a Google Play badge. + However, keep in mind that:</li> + <ul> + <li>If you make any mention of Google Play or Android outside of the badge a legal attribution + must be included. </li> + <li>If you are including another app store’s legal attribution then include this legal + attribution:</li> + <ul> + <li>Google Play is a trademark of Google Inc.</li> + </ul> </li> + <li>Use of the Google Play badge does not need to be reviewed or approved by the Google Play + brand team unless the marketing campaign will have over 1 million impressions.</li> </ul> <p>To quickly create a badge that links to your apps on Google Play, use the <a href="{@docRoot}distribute/tools/promote/badges.html">Google Play badge generator</a> - (provides the badge in over 40 languages).</p> - - <p>To create your own size, download an Adobe® Illustrator® (.ai) file for the - <a href="{@docRoot}distribute/tools/promote/badge-files.html">Google Play - badge in over 40 languages</a>.</p> + (badges available in over 40 languages).</p> <p>For details on all the ways that you can link to your product details page in Google Play, see <a href="{@docRoot}distribute/tools/promote/linking.html">Linking to your products</a>.</p> +<h2 id="Marketing_Review">Google Play in Text</h2> + +<p>Any use of Google Play in text must be reviewed and approved by the Google Play brand team. +Submit your marketing via the <a href="https://support.google.com/googleplay/contact/brand_developer"> +Android and Google Play Partner Brand Inquiry Form.</a></p> + +<p>Always include a ™ symbol on the first or most prominent instance of Google Play™ in text.</p> + +<p>When mentioning that a product is available on Google Play always say “on Google Play”</p> + +<ul> +<li><span style="color:red">Incorrect</span>: Our app is now available from Google Play.</li> +<li><span style="color:green">Correct</span>: Our app is now available on Google Play.</li> +</ul> +<br> +<p>Only refer to Google Play as the Google Play™ store app in instructional text meant to inform a +customer about how to find or download your product on Google Play.</p> + +<ul> +<li><span style="color:red">Incorrect</span>: Download our games using the Google Play™ store app.</li> +<li><span style="color:green">Correct</span>: This is how you download our app using the Google +Play™ store app.</li> +</ul> +<br> + +<p>Any use of the Google Play name in your marketing or communications needs to be accompanied by +this legal attribution:</p> + +<p><em>Google Play is a trademark of Google Inc.</em></p> + <h2 id="Marketing_Review">Marketing Reviews and Brand Inquiries</h2> <p>Use the <a href="https://support.google.com/googleplay/contact/brand_developer">Android and Google Play Brand Permissions Inquiry form</a> to submit any marketing -reviews or brand inquires. Typical response time is at least one week.</p> +reviews or brand inquires. Typical response time is at least one week.</p>
\ No newline at end of file diff --git a/docs/html/tools/studio/index.jd b/docs/html/tools/studio/index.jd index 9e7721b..1860feb 100644 --- a/docs/html/tools/studio/index.jd +++ b/docs/html/tools/studio/index.jd @@ -96,10 +96,9 @@ top level of the project hierarchy and contains these three elements at the top <p>For example, <em>Android</em> project view groups all the instances of the <code>ic_launcher.png</code> resource for different screen densities under the same element.</p> -<p class="note"><strong>Note:</strong> The project structure on disk differs from this flattened -representation. To switch to back the segregated project view, select <strong>Project</strong> from -the <strong>Project</strong drop-down. </p> - +<p class="note"><strong>Note:</strong> The project structure on disk differs from this flattened +representation. To switch to back to the segregated project view, select <strong>Project</strong> from +the <strong>Project</strong> drop-down. </p> <h3>Android Studio Project and Directory Structure</h3> @@ -182,7 +181,7 @@ uniquely identify application packages for publishing. The application ID is set <p class="note"><strong>Note:</strong> The <em>applicationId</em> is specified only in your build.gradle file, and not in the AndroidManifest.xml file.</p> -<p>When using build variants, the build system enables you to to uniquely identify different +<p>When using build variants, the build system enables you to uniquely identify different packages for each product flavors and build types. The application ID in the build type is added as a suffix to those specified for the product flavors. </p> diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index d2799e1..db94d89 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -20,15 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.ColorFilter; -import android.graphics.PorterDuff.Mode; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.util.AttributeSet; import android.util.TypedValue; -import android.util.Log; import android.os.SystemClock; import org.xmlpull.v1.XmlPullParser; @@ -41,35 +37,15 @@ import com.android.internal.R; /** * @hide */ -public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable, - Animatable { - private static final String TAG = "AnimatedRotateDrawable"; - +public class AnimatedRotateDrawable extends DrawableWrapper implements Runnable, Animatable { private AnimatedRotateState mState; - private boolean mMutated; + private float mCurrentDegrees; private float mIncrement; private boolean mRunning; public AnimatedRotateDrawable() { - this(null, null); - } - - private AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res) { - mState = new AnimatedRotateState(rotateState, this, res); - init(); - } - - private void init() { - final AnimatedRotateState state = mState; - mIncrement = 360.0f / state.mFramesCount; - final Drawable drawable = state.mDrawable; - if (drawable != null) { - drawable.setFilterBitmap(true); - if (drawable instanceof BitmapDrawable) { - ((BitmapDrawable) drawable).setAntiAlias(true); - } - } + this(new AnimatedRotateState(null), null); } @Override @@ -131,8 +107,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac @Override public boolean setVisible(boolean visible, boolean restart) { - mState.mDrawable.setVisible(visible, restart); - boolean changed = super.setVisible(visible, restart); + final boolean changed = super.setVisible(visible, restart); if (visible) { if (changed || restart) { mCurrentDegrees = 0.0f; @@ -144,123 +119,6 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac return changed; } - /** - * Returns the drawable rotated by this RotateDrawable. - */ - public Drawable getDrawable() { - return mState.mDrawable; - } - - @Override - public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mState.mChangingConfigurations - | mState.mDrawable.getChangingConfigurations(); - } - - @Override - public void setAlpha(int alpha) { - mState.mDrawable.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mState.mDrawable.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mState.mDrawable.setColorFilter(cf); - } - - @Override - public void setTintList(ColorStateList tint) { - mState.mDrawable.setTintList(tint); - } - - @Override - public void setTintMode(Mode tintMode) { - mState.mDrawable.setTintMode(tintMode); - } - - @Override - public int getOpacity() { - return mState.mDrawable.getOpacity(); - } - - @Override - public boolean canApplyTheme() { - return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); - } - - @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 boolean getPadding(Rect padding) { - return mState.mDrawable.getPadding(padding); - } - - @Override - public boolean isStateful() { - return mState.mDrawable.isStateful(); - } - - @Override - protected void onBoundsChange(Rect bounds) { - mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom); - } - - @Override - protected boolean onLevelChange(int level) { - return mState.mDrawable.setLevel(level); - } - - @Override - protected boolean onStateChange(int[] state) { - return mState.mDrawable.setState(state); - } - - @Override - public int getIntrinsicWidth() { - return mState.mDrawable.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mState.mDrawable.getIntrinsicHeight(); - } - - @Override - public ConstantState getConstantState() { - if (mState.canConstantState()) { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; - } - return null; - } - @Override public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) @@ -268,50 +126,27 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); updateStateFromTypedArray(a); + verifyRequiredAttributes(a); a.recycle(); - inflateChildElements(r, parser, attrs, theme); - - init(); + updateLocalState(); } - @Override - public void applyTheme(@Nullable Theme t) { - super.applyTheme(t); - - final AnimatedRotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { - state.mDrawable.applyTheme(t); + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <animated-rotate> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); } - - init(); } - private void updateStateFromTypedArray(TypedArray a) { - final AnimatedRotateState state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); + @Override + void updateStateFromTypedArray(TypedArray a) { + super.updateStateFromTypedArray(a); - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + final AnimatedRotateState state = mState; if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); @@ -332,43 +167,35 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); if (dr != null) { - state.mDrawable = dr; - dr.setCallback(this); + setDrawable(dr); } } - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { + @Override + public void applyTheme(@Nullable Theme t) { final AnimatedRotateState state = mState; + if (state == null) { + return; + } - Drawable dr = null; - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - - if ((dr = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { - Log.w(TAG, "Bad element under <animated-rotate>: " + parser.getName()); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); } } - if (dr != null) { - state.mDrawable = dr; - dr.setCallback(this); - } - } + // The drawable may have changed as a result of applying the theme, so + // apply the theme to the wrapped drawable last. + super.applyTheme(t); - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (mState.mDrawable == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": <animated-rotate> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } + updateLocalState(); } public void setFramesCount(int framesCount) { @@ -380,25 +207,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac mState.mFrameDuration = framesDuration; } - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState.mDrawable.mutate(); - mMutated = true; - } - return this; - } - - /** - * @hide - */ - public void clearMutated() { - super.clearMutated(); - mState.mDrawable.clearMutated(); - mMutated = false; - } - - final static class AnimatedRotateState extends Drawable.ConstantState { + static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState { Drawable mDrawable; int[] mThemeAttrs; @@ -414,35 +223,20 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac private boolean mCanConstantState; private boolean mCheckedConstantState; - public AnimatedRotateState(AnimatedRotateState orig, AnimatedRotateDrawable owner, - Resources res) { + public AnimatedRotateState(AnimatedRotateState orig) { + super(orig); + if (orig != null) { - if (res != null) { - mDrawable = orig.mDrawable.getConstantState().newDrawable(res); - } else { - mDrawable = orig.mDrawable.getConstantState().newDrawable(); - } - mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); - mDrawable.setBounds(orig.mDrawable.getBounds()); - mDrawable.setLevel(orig.mDrawable.getLevel()); - mThemeAttrs = orig.mThemeAttrs; mPivotXRel = orig.mPivotXRel; mPivotX = orig.mPivotX; mPivotYRel = orig.mPivotYRel; mPivotY = orig.mPivotY; mFramesCount = orig.mFramesCount; mFrameDuration = orig.mFrameDuration; - mCanConstantState = mCheckedConstantState = true; } } @Override - public Drawable newDrawable() { - return new AnimatedRotateDrawable(this, null); - } - - @Override public Drawable newDrawable(Resources res) { return new AnimatedRotateDrawable(this, res); } @@ -467,4 +261,27 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac return mCanConstantState; } } + + private AnimatedRotateDrawable(AnimatedRotateState state, Resources res) { + super(state, res); + + mState = state; + + updateLocalState(); + } + + private void updateLocalState() { + final AnimatedRotateState state = mState; + mIncrement = 360.0f / state.mFramesCount; + + // Force the wrapped drawable to use filtering and AA, if applicable, + // so that it looks smooth when rotated. + final Drawable drawable = state.mDrawable; + if (drawable != null) { + drawable.setFilterBitmap(true); + if (drawable instanceof BitmapDrawable) { + ((BitmapDrawable) drawable).setAntiAlias(true); + } + } + } } diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 2a17a60..17e5b0b 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -197,6 +197,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { } @Override + public boolean onLayoutDirectionChange(int layoutDirection) { + return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); + } + + @Override public int getAlpha() { return mAnimatedVectorState.mVectorDrawable.getAlpha(); } @@ -237,12 +242,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { return super.setVisible(visible, restart); } - /** {@hide} */ - @Override - public void setLayoutDirection(int layoutDirection) { - mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); - } - @Override public boolean isStateful() { return mAnimatedVectorState.mVectorDrawable.isStateful(); diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index e5b2b76..f3574e8 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -50,32 +50,36 @@ import java.io.IOException; * @attr ref android.R.styleable#ClipDrawable_gravity * @attr ref android.R.styleable#ClipDrawable_drawable */ -public class ClipDrawable extends Drawable implements Drawable.Callback { - private ClipState mState; - private final Rect mTmpRect = new Rect(); - +public class ClipDrawable extends DrawableWrapper { public static final int HORIZONTAL = 1; public static final int VERTICAL = 2; - private boolean mMutated; + private static final int MAX_LEVEL = 10000; + + private final Rect mTmpRect = new Rect(); + + private ClipState mState; ClipDrawable() { - this(null, null); + this(new ClipState(null), null); } /** - * @param orientation Bitwise-or of {@link #HORIZONTAL} and/or {@link #VERTICAL} + * Creates a new clip drawable with the specified gravity and orientation. + * + * @param drawable the drawable to clip + * @param gravity gravity constant (see {@link Gravity} used to position + * the clipped drawable within the parent container + * @param orientation bitwise-or of {@link #HORIZONTAL} and/or + * {@link #VERTICAL} */ public ClipDrawable(Drawable drawable, int gravity, int orientation) { - this(null, null); + this(new ClipState(null), null); - mState.mDrawable = drawable; mState.mGravity = gravity; mState.mOrientation = orientation; - if (drawable != null) { - drawable.setCallback(this); - } + setDrawable(drawable); } @Override @@ -84,39 +88,15 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { super.inflate(r, parser, attrs, theme); final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); - - // Reset mDrawable to preserve old multiple-inflate behavior. This is - // silly, but we have CTS tests that rely on it. - mState.mDrawable = null; - updateStateFromTypedArray(a); - inflateChildElements(r, parser, attrs, theme); + inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { - Drawable dr = null; - int type; - final int outerDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - dr = Drawable.createFromXmlInner(r, parser, attrs, theme); - } - - if (dr != null) { - mState.mDrawable = dr; - dr.setCallback(this); - } - } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. - if (mState.mDrawable == null && (mState.mThemeAttrs == null + if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { throw new XmlPullParserException(a.getPositionDescription() + ": <clip> tag requires a 'drawable' attribute or " @@ -124,292 +104,110 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } } - private void updateStateFromTypedArray(TypedArray a) { - final ClipState state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + @Override + void updateStateFromTypedArray(TypedArray a) { + super.updateStateFromTypedArray(a); - state.mOrientation = a.getInt(R.styleable.ClipDrawable_clipOrientation, state.mOrientation); - state.mGravity = a.getInt(R.styleable.ClipDrawable_gravity, state.mGravity); + final ClipState state = mState; + state.mOrientation = a.getInt( + R.styleable.ClipDrawable_clipOrientation, state.mOrientation); + state.mGravity = a.getInt( + R.styleable.ClipDrawable_gravity, state.mGravity); final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); if (dr != null) { - state.mDrawable = dr; - dr.setCallback(this); + setDrawable(dr); } } @Override public void applyTheme(Theme t) { - super.applyTheme(t); - final ClipState state = mState; - if (state == null || state.mThemeAttrs == null) { + if (state == null) { return; } - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - - if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { - state.mDrawable.applyTheme(t); - } - } - - @Override - public boolean canApplyTheme() { - return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); - } - - // overrides from Drawable.Callback - - @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); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } } - } - - // overrides from Drawable - - @Override - public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mState.mChangingConfigurations - | mState.mDrawable.getChangingConfigurations(); - } - - @Override - public boolean getPadding(Rect padding) { - // XXX need to adjust padding! - return mState.mDrawable.getPadding(padding); - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - mState.mDrawable.setVisible(visible, restart); - return super.setVisible(visible, restart); - } - @Override - public void setAlpha(int alpha) { - mState.mDrawable.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mState.mDrawable.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mState.mDrawable.setColorFilter(cf); - } - - @Override - public void setTintList(ColorStateList tint) { - mState.mDrawable.setTintList(tint); - } - - @Override - public void setTintMode(Mode tintMode) { - mState.mDrawable.setTintMode(tintMode); - } - - @Override - public int getOpacity() { - return mState.mDrawable.getOpacity(); - } - - @Override - public boolean isStateful() { - return mState.mDrawable.isStateful(); - } - - @Override - protected boolean onStateChange(int[] state) { - return mState.mDrawable.setState(state); + // The drawable may have changed as a result of applying the theme, so + // apply the theme to the wrapped drawable last. + super.applyTheme(t); } @Override protected boolean onLevelChange(int level) { - mState.mDrawable.setLevel(level); + super.onLevelChange(level); invalidateSelf(); return true; } @Override - protected void onBoundsChange(Rect bounds) { - mState.mDrawable.setBounds(bounds); - } - - @Override public void draw(Canvas canvas) { - - if (mState.mDrawable.getLevel() == 0) { + final Drawable dr = getDrawable(); + if (dr.getLevel() == 0) { return; } final Rect r = mTmpRect; final Rect bounds = getBounds(); - int level = getLevel(); + final int level = getLevel(); + int w = bounds.width(); final int iw = 0; //mState.mDrawable.getIntrinsicWidth(); if ((mState.mOrientation & HORIZONTAL) != 0) { - w -= (w - iw) * (10000 - level) / 10000; + w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL; } + int h = bounds.height(); final int ih = 0; //mState.mDrawable.getIntrinsicHeight(); if ((mState.mOrientation & VERTICAL) != 0) { - h -= (h - ih) * (10000 - level) / 10000; + h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL; } + final int layoutDirection = getLayoutDirection(); Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); if (w > 0 && h > 0) { canvas.save(); canvas.clipRect(r); - mState.mDrawable.draw(canvas); + dr.draw(canvas); canvas.restore(); } } - @Override - public int getIntrinsicWidth() { - return mState.mDrawable.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mState.mDrawable.getIntrinsicHeight(); - } - - @Override - public ConstantState getConstantState() { - if (mState.canConstantState()) { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; - } - return null; - } - - /** @hide */ - @Override - public void setLayoutDirection(int layoutDirection) { - mState.mDrawable.setLayoutDirection(layoutDirection); - super.setLayoutDirection(layoutDirection); - } - - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState.mDrawable.mutate(); - mMutated = true; - } - return this; - } - - /** - * @hide - */ - public void clearMutated() { - super.clearMutated(); - mState.mDrawable.clearMutated(); - mMutated = false; - } - - final static class ClipState extends ConstantState { - int[] mThemeAttrs; - int mChangingConfigurations; - - Drawable mDrawable; - + static final class ClipState extends DrawableWrapper.DrawableWrapperState { int mOrientation = HORIZONTAL; int mGravity = Gravity.LEFT; - private boolean mCheckedConstantState; - private boolean mCanConstantState; + ClipState(ClipState orig) { + super(orig); - ClipState(ClipState orig, ClipDrawable owner, Resources res) { if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mChangingConfigurations = orig.mChangingConfigurations; - if (res != null) { - mDrawable = orig.mDrawable.getConstantState().newDrawable(res); - } else { - mDrawable = orig.mDrawable.getConstantState().newDrawable(); - } - mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); - mDrawable.setBounds(orig.mDrawable.getBounds()); - mDrawable.setLevel(orig.mDrawable.getLevel()); mOrientation = orig.mOrientation; mGravity = orig.mGravity; - mCheckedConstantState = mCanConstantState = true; } } @Override - public boolean canApplyTheme() { - return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) - || super.canApplyTheme(); - } - - @Override - public Drawable newDrawable() { - return new ClipDrawable(this, null); - } - - @Override public Drawable newDrawable(Resources res) { return new ClipDrawable(this, res); } - - @Override - public int getChangingConfigurations() { - return mChangingConfigurations; - } - - boolean canConstantState() { - if (!mCheckedConstantState) { - mCanConstantState = mDrawable.getConstantState() != null; - mCheckedConstantState = true; - } - - return mCanConstantState; - } } private ClipDrawable(ClipState state, Resources res) { - mState = new ClipState(state, this, res); + super(state, res); + + mState = state; } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index c9d7869..98767a7 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -423,27 +423,41 @@ public abstract class Drawable { * Returns the resolved layout direction for this Drawable. * * @return One of {@link android.view.View#LAYOUT_DIRECTION_LTR}, - * {@link android.view.View#LAYOUT_DIRECTION_RTL} - * - * @hide + * {@link android.view.View#LAYOUT_DIRECTION_RTL} + * @see #setLayoutDirection(int) */ public int getLayoutDirection() { return mLayoutDirection; } /** - * Set the layout direction for this drawable. Should be a resolved direction as the - * Drawable as no capacity to do the resolution on his own. + * Set the layout direction for this drawable. Should be a resolved + * layout direction, as the Drawable as no capacity to do the resolution on + * its own. * - * @param layoutDirection One of {@link android.view.View#LAYOUT_DIRECTION_LTR}, - * {@link android.view.View#LAYOUT_DIRECTION_RTL} - * - * @hide + * @param layoutDirection the resolved layout direction for the drawable, + * either {@link android.view.View#LAYOUT_DIRECTION_LTR} + * or {@link android.view.View#LAYOUT_DIRECTION_RTL} + * @see #getLayoutDirection() */ - public void setLayoutDirection(@View.ResolvedLayoutDir int layoutDirection) { - if (getLayoutDirection() != layoutDirection) { + public final boolean setLayoutDirection(@View.ResolvedLayoutDir int layoutDirection) { + if (mLayoutDirection != layoutDirection) { mLayoutDirection = layoutDirection; + return onLayoutDirectionChange(layoutDirection); } + return false; + } + + /** + * Called when the drawable's resolved layout direction changes. + * + * @param layoutDirection the new resolved layout direction + * @return true if the layout direction change has caused the appearance of + * the drawable to change and it needs to be re-drawn + * @see #setLayoutDirection(int) + */ + public boolean onLayoutDirectionChange(@View.ResolvedLayoutDir int layoutDirection) { + return false; } /** @@ -554,14 +568,20 @@ public abstract class Drawable { * 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 + * @param left position in pixels of the left bound + * @param top position in pixels of the top bound + * @param right position in pixels of the right bound + * @param bottom position in pixels of the bottom bound + * @see #getHotspotBounds(android.graphics.Rect) */ public void setHotspotBounds(int left, int top, int right, int bottom) {} - /** @hide For internal use only. Individual results may vary. */ + /** + * Populates {@code outRect} with the hotspot bounds. + * + * @param outRect the rect to populate with the hotspot bounds + * @see #setHotspotBounds(int, int, int, int) + */ public void getHotspotBounds(Rect outRect) { outRect.set(getBounds()); } diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 557f563..c4794d9 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -173,7 +173,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Override public void setColorFilter(ColorFilter cf) { - mDrawableContainerState.mHasColorFilter = (cf != null); + mDrawableContainerState.mHasColorFilter = true; if (mDrawableContainerState.mColorFilter != cf) { mDrawableContainerState.mColorFilter = cf; @@ -306,7 +306,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } } - /** @hide */ @Override public void getHotspotBounds(Rect outRect) { if (mHotspotBounds != null) { @@ -339,6 +338,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override + public boolean onLayoutDirectionChange(int layoutDirection) { + // Let the container handle setting its own layout direction. Otherwise, + // we're accessing potentially unused states. + return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex()); + } + + @Override public int getIntrinsicWidth() { if (mDrawableContainerState.isConstantSize()) { return mDrawableContainerState.getConstantWidth(); @@ -829,18 +835,25 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return null; } - final void setLayoutDirection(int layoutDirection) { + final boolean setLayoutDirection(int layoutDirection, int currentIndex) { + boolean changed = false; + // No need to call createAllFutures, since future drawables will // change layout direction when they are prepared. final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i] != null) { - drawables[i].setLayoutDirection(layoutDirection); + final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection); + if (i == currentIndex) { + changed = childChanged; + } } } mLayoutDirection = layoutDirection; + + return changed; } final void applyTheme(Theme theme) { diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java new file mode 100644 index 0000000..b17bab0 --- /dev/null +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Insets; +import android.graphics.Outline; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; + +import java.io.IOException; +import java.util.Collection; + +/** + * Drawable container with only one child element. + */ +public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { + private DrawableWrapperState mState; + private Drawable mDrawable; + private boolean mMutated; + + DrawableWrapper(DrawableWrapperState state, Resources res) { + mState = state; + + updateLocalState(res); + } + + /** + * Creates a new wrapper around the specified drawable. + * + * @param dr the drawable to wrap + */ + public DrawableWrapper(@Nullable Drawable dr) { + mState = null; + mDrawable = dr; + } + + /** + * Initializes local dynamic properties from state. This should be called + * after significant state changes, e.g. from the One True Constructor and + * after inflating or applying a theme. + */ + private void updateLocalState(Resources res) { + if (mState != null && mState.mDrawableState != null) { + final Drawable dr = mState.mDrawableState.newDrawable(res); + setDrawable(dr); + } + } + + /** + * Sets the wrapped drawable. + * + * @param dr the wrapped drawable + */ + public void setDrawable(@Nullable Drawable dr) { + if (mDrawable != null) { + mDrawable.setCallback(null); + } + + mDrawable = dr; + + if (dr != null) { + dr.setCallback(this); + + // Only call setters for data that's stored in the base Drawable. + dr.setVisible(isVisible(), true); + dr.setState(getState()); + dr.setLevel(getLevel()); + dr.setBounds(getBounds()); + dr.setLayoutDirection(getLayoutDirection()); + + if (mState != null) { + mState.mDrawableState = dr.getConstantState(); + } + } + + invalidateSelf(); + } + + /** + * @return the wrapped drawable + */ + @Nullable + public Drawable getDrawable() { + return mDrawable; + } + + void updateStateFromTypedArray(TypedArray a) { + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + // TODO: Consider using R.styleable.DrawableWrapper_drawable + } + + @Override + public void applyTheme(Resources.Theme t) { + super.applyTheme(t); + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + if (mDrawable != null && mDrawable.canApplyTheme()) { + mDrawable.applyTheme(t); + } + } + + @Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); + } + + @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(@NonNull Canvas canvas) { + if (mDrawable != null) { + mDrawable.draw(canvas); + } + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() + | (mState != null ? mState.mChangingConfigurations : 0) + | mDrawable.getChangingConfigurations(); + } + + @Override + public boolean getPadding(@NonNull Rect padding) { + return mDrawable != null && mDrawable.getPadding(padding); + } + + /** @hide */ + @Override + public Insets getOpticalInsets() { + return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; + } + + @Override + public void setHotspot(float x, float y) { + if (mDrawable != null) { + mDrawable.setHotspot(x, y); + } + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + if (mDrawable != null) { + mDrawable.setHotspotBounds(left, top, right, bottom); + } + } + + @Override + public void getHotspotBounds(@NonNull Rect outRect) { + if (mDrawable != null) { + mDrawable.getHotspotBounds(outRect); + } else { + outRect.set(getBounds()); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + final boolean superChanged = super.setVisible(visible, restart); + final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart); + return superChanged | changed; + } + + @Override + public void setAlpha(int alpha) { + if (mDrawable != null) { + mDrawable.setAlpha(alpha); + } + } + + @Override + public int getAlpha() { + return mDrawable != null ? mDrawable.getAlpha() : 255; + } + + @Override + public void setColorFilter(@Nullable ColorFilter cf) { + if (mDrawable != null) { + mDrawable.setColorFilter(cf); + } + } + + @Override + public void setTintList(@Nullable ColorStateList tint) { + if (mDrawable != null) { + mDrawable.setTintList(tint); + } + } + + @Override + public void setTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mDrawable != null) { + mDrawable.setTintMode(tintMode); + } + } + + @Override + public boolean onLayoutDirectionChange(@View.ResolvedLayoutDir int layoutDirection) { + return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection); + } + + @Override + public int getOpacity() { + return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT; + } + + @Override + public boolean isStateful() { + return mDrawable != null && mDrawable.isStateful(); + } + + @Override + protected boolean onStateChange(int[] state) { + if (mDrawable != null) { + final boolean changed = mDrawable.setState(state); + if (changed) { + onBoundsChange(getBounds()); + } + return changed; + } + return false; + } + + @Override + protected boolean onLevelChange(int level) { + return mDrawable != null && mDrawable.setLevel(level); + } + + @Override + protected void onBoundsChange(@NonNull Rect bounds) { + if (mDrawable != null) { + mDrawable.setBounds(bounds); + } + } + + @Override + public int getIntrinsicWidth() { + return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1; + } + + @Override + public int getIntrinsicHeight() { + return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1; + } + + @Override + public void getOutline(@NonNull Outline outline) { + if (mDrawable != null) { + mDrawable.getOutline(outline); + } else { + super.getOutline(outline); + } + } + + @Override + @Nullable + public ConstantState getConstantState() { + if (mState != null && mState.canConstantState()) { + mState.mChangingConfigurations = getChangingConfigurations(); + return mState; + } + return null; + } + + @Override + @NonNull + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState = mutateConstantState(); + if (mDrawable != null) { + mDrawable.mutate(); + } + if (mState != null) { + mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; + } + mMutated = true; + } + return this; + } + + /** + * Mutates the constant state and returns the new state. Responsible for + * updating any local copy. + * <p> + * This method should never call the super implementation; it should always + * mutate and return its own constant state. + * + * @return the new state + */ + DrawableWrapperState mutateConstantState() { + return mState; + } + + /** + * @hide Only used by the framework for pre-loading resources. + */ + public void clearMutated() { + super.clearMutated(); + if (mDrawable != null) { + mDrawable.clearMutated(); + } + mMutated = false; + } + + /** + * Called during inflation to inflate the child element. + */ + void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, + Resources.Theme theme) throws XmlPullParserException, IOException { + // Drawable specified on the root element takes precedence. + if (getDrawable() != null) { + return; + } + + // Seek to the first child element. + Drawable dr = null; + int type; + final int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.START_TAG) { + dr = Drawable.createFromXmlInner(r, parser, attrs, theme); + break; + } + } + + if (dr != null) { + setDrawable(dr); + } + } + + abstract static class DrawableWrapperState extends Drawable.ConstantState { + int[] mThemeAttrs; + int mChangingConfigurations; + + Drawable.ConstantState mDrawableState; + + DrawableWrapperState(DrawableWrapperState orig) { + if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mChangingConfigurations = orig.mChangingConfigurations; + mDrawableState = orig.mDrawableState; + } + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null + || (mDrawableState != null && mDrawableState.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final Drawable.ConstantState state = mDrawableState; + if (state != null) { + return state.addAtlasableBitmaps(atlasList); + } + return 0; + } + + @Override + public Drawable newDrawable() { + return newDrawable(null); + } + + @Override + public abstract Drawable newDrawable(Resources res); + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + public boolean canConstantState() { + return mDrawableState != null; + } + } +} diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 50a6df4..b0cd386 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -18,27 +18,20 @@ package android.graphics.drawable; import com.android.internal.R; -import android.annotation.NonNull; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.content.res.ColorStateList; +import android.annotation.NonNull; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.Resources.Theme; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; +import android.content.res.TypedArray; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.util.AttributeSet; import java.io.IOException; -import java.util.Collection; /** * A Drawable that insets another Drawable by a specified distance. @@ -56,13 +49,10 @@ import java.util.Collection; * @attr ref android.R.styleable#InsetDrawable_insetTop * @attr ref android.R.styleable#InsetDrawable_insetBottom */ -public class InsetDrawable extends Drawable implements Drawable.Callback { +public class InsetDrawable extends DrawableWrapper { private final Rect mTmpRect = new Rect(); private InsetState mState; - private Drawable mDrawable; - - private boolean mMutated; /** * No-arg constructor used by drawable inflation. @@ -94,72 +84,29 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { int insetBottom) { this(new InsetState(), null); - mState.mDrawableState = drawable == null ? null : drawable.getConstantState(); mState.mInsetLeft = insetLeft; mState.mInsetTop = insetTop; mState.mInsetRight = insetRight; mState.mInsetBottom = insetBottom; - mDrawable = drawable; - - if (drawable != null) { - drawable.setCallback(this); - } + setDrawable(drawable); } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible); + super.inflate(r, parser, attrs, theme); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); updateStateFromTypedArray(a); - inflateChildElements(r, parser, attrs, theme); + inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void setDrawable(Drawable dr) { - if (mDrawable != null) { - mDrawable.setCallback(null); - } - - mDrawable = dr; - - if (dr != null) { - dr.setCallback(this); - dr.setVisible(isVisible(), true); - dr.setState(getState()); - dr.setLevel(getLevel()); - dr.setBounds(getBounds()); - dr.setLayoutDirection(getLayoutDirection()); - } - } - - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { - // Load inner XML elements. - if (mDrawable == null) { - int type; - while ((type=parser.next()) == XmlPullParser.TEXT) { - } - if (type != XmlPullParser.START_TAG) { - throw new XmlPullParserException(parser.getPositionDescription() - + ": <inset> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - - final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs, theme); - if (dr != null) { - mState.mDrawableState = dr.getConstantState(); - setDrawable(dr); - } - } - } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. - if (mDrawable == null && (mState.mThemeAttrs == null + if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { throw new XmlPullParserException(a.getPositionDescription() + ": <inset> tag requires a 'drawable' attribute or " @@ -167,15 +114,11 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } } - private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { - final InsetState state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + @Override + void updateStateFromTypedArray(TypedArray a) { + super.updateStateFromTypedArray(a); + final InsetState state = mState; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { final int attr = a.getIndex(i); @@ -183,7 +126,6 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { case R.styleable.InsetDrawable_drawable: final Drawable dr = a.getDrawable(attr); if (dr != null) { - mState.mDrawableState = dr.getConstantState(); setDrawable(dr); } break; @@ -214,8 +156,6 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { @Override public void applyTheme(Theme t) { - super.applyTheme(t); - final InsetState state = mState; if (state == null) { return; @@ -233,55 +173,14 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } } - if (mDrawable != null && mDrawable.canApplyTheme()) { - mDrawable.applyTheme(t); - } - } - - @Override - public boolean canApplyTheme() { - return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); - } - - @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) { - mDrawable.draw(canvas); - } - - @Override - public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mState.mChangingConfigurations - | mDrawable.getChangingConfigurations(); + // The drawable may have changed as a result of applying the theme, so + // apply the theme to the wrapped drawable last. + super.applyTheme(t); } @Override public boolean getPadding(Rect padding) { - final boolean pad = mDrawable.getPadding(padding); + final boolean pad = super.getPadding(padding); padding.left += mState.mInsetLeft; padding.right += mState.mInsetRight; @@ -303,62 +202,9 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } @Override - public void setHotspot(float x, float y) { - mDrawable.setHotspot(x, y); - } - - @Override - public void setHotspotBounds(int left, int top, int right, int bottom) { - mDrawable.setHotspotBounds(left, top, right, bottom); - } - - /** @hide */ - @Override - public void getHotspotBounds(Rect outRect) { - mDrawable.getHotspotBounds(outRect); - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - mDrawable.setVisible(visible, restart); - return super.setVisible(visible, restart); - } - - @Override - public void setAlpha(int alpha) { - mDrawable.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mDrawable.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mDrawable.setColorFilter(cf); - } - - @Override - public void setTintList(ColorStateList tint) { - mDrawable.setTintList(tint); - } - - @Override - public void setTintMode(Mode tintMode) { - mDrawable.setTintMode(tintMode); - } - - /** {@hide} */ - @Override - public void setLayoutDirection(int layoutDirection) { - mDrawable.setLayoutDirection(layoutDirection); - } - - @Override public int getOpacity() { final InsetState state = mState; - final int opacity = mDrawable.getOpacity(); + final int opacity = getDrawable().getOpacity(); if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0 || state.mInsetRight > 0 || state.mInsetBottom > 0)) { return PixelFormat.TRANSLUCENT; @@ -367,23 +213,6 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } @Override - public boolean isStateful() { - return mDrawable.isStateful(); - } - - @Override - protected boolean onStateChange(int[] state) { - final boolean changed = mDrawable.setState(state); - onBoundsChange(getBounds()); - return changed; - } - - @Override - protected boolean onLevelChange(int level) { - return mDrawable.setLevel(level); - } - - @Override protected void onBoundsChange(Rect bounds) { final Rect r = mTmpRect; r.set(bounds); @@ -393,22 +222,23 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { r.right -= mState.mInsetRight; r.bottom -= mState.mInsetBottom; - mDrawable.setBounds(r.left, r.top, r.right, r.bottom); + // Apply inset bounds to the wrapped drawable. + super.onBoundsChange(r); } @Override public int getIntrinsicWidth() { - return mDrawable.getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight; + return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight; } @Override public int getIntrinsicHeight() { - return mDrawable.getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom; + return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom; } @Override public void getOutline(@NonNull Outline outline) { - mDrawable.getOutline(outline); + getDrawable().getOutline(outline); } @Override @@ -421,33 +251,12 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState = new InsetState(mState); - mDrawable.mutate(); - mState.mDrawableState = mDrawable.getConstantState(); - mMutated = true; - } - return this; + DrawableWrapperState mutateConstantState() { + mState = new InsetState(mState); + return mState; } - /** - * @hide - */ - public void clearMutated() { - super.clearMutated(); - mDrawable.clearMutated(); - mMutated = false; - } - - /** - * Returns the drawable wrapped by this InsetDrawable. May be null. - */ - public Drawable getDrawable() { - return mDrawable; - } - - private static final class InsetState extends ConstantState { + static final class InsetState extends DrawableWrapper.DrawableWrapperState { int[] mThemeAttrs; int mChangingConfigurations; @@ -458,15 +267,14 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { int mInsetRight = 0; int mInsetBottom = 0; - public InsetState() { - // Empty constructor. + InsetState() { + this(null); } - public InsetState(InsetState orig) { + InsetState(InsetState orig) { + super(orig); + if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mChangingConfigurations = orig.mChangingConfigurations; - mDrawableState = orig.mDrawableState; mInsetLeft = orig.mInsetLeft; mInsetTop = orig.mInsetTop; mInsetRight = orig.mInsetRight; @@ -475,39 +283,9 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } @Override - public boolean canApplyTheme() { - return mThemeAttrs != null - || (mDrawableState != null && mDrawableState.canApplyTheme()) - || super.canApplyTheme(); - } - - @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final ConstantState state = mDrawableState; - if (state != null) { - return state.addAtlasableBitmaps(atlasList); - } - return 0; - } - - @Override - public Drawable newDrawable() { - return new InsetDrawable(this, null); - } - - @Override public Drawable newDrawable(Resources res) { return new InsetDrawable(this, res); } - - @Override - public int getChangingConfigurations() { - return mChangingConfigurations; - } - - public boolean canConstantState() { - return mDrawableState != null; - } } /** @@ -515,21 +293,9 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { * constructors to set the state and initialize local properties. */ private InsetDrawable(InsetState state, Resources res) { - mState = state; - - updateLocalState(res); - } + super(state, res); - /** - * Initializes local dynamic properties from state. This should be called - * after significant state changes, e.g. from the One True Constructor and - * after inflating or applying a theme. - */ - private void updateLocalState(Resources res) { - if (mState.mDrawableState != null) { - final Drawable dr = mState.mDrawableState.newDrawable(res); - setDrawable(dr); - } + mState = state; } } diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 616aebd..f5353d4 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -558,10 +558,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * default layer gravity behavior. See {@link #setLayerGravity(int, int)} * for more information. * - * @param index the index of the drawable to adjust + * @param index the index of the layer to adjust * @param w width in pixels, or -1 to use the intrinsic width * @param h height in pixels, or -1 to use the intrinsic height - * * @see #getLayerWidth(int) * @see #getLayerHeight(int) * @attr ref android.R.styleable#LayerDrawableItem_width @@ -574,9 +573,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } /** + * @param index the index of the layer to adjust + * @param w width in pixels, or -1 to use the intrinsic width + * @attr ref android.R.styleable#LayerDrawableItem_width + */ + public void setLayerWidth(int index, int w) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mWidth = w; + } + + /** * @param index the index of the drawable to adjust * @return the explicit width of the layer, or -1 if not specified - * * @see #setLayerSize(int, int, int) * @attr ref android.R.styleable#LayerDrawableItem_width */ @@ -586,9 +594,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } /** + * @param index the index of the layer to adjust + * @param h height in pixels, or -1 to use the intrinsic height + * @attr ref android.R.styleable#LayerDrawableItem_height + */ + public void setLayerHeight(int index, int h) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mHeight = h; + } + + /** * @param index the index of the drawable to adjust * @return the explicit height of the layer, or -1 if not specified - * * @see #setLayerSize(int, int, int) * @attr ref android.R.styleable#LayerDrawableItem_height */ @@ -656,7 +673,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * Specifies the relative insets in pixels for the drawable at the * specified index. * - * @param index the index of the drawable to adjust + * @param index the index of the layer to adjust * @param s number of pixels to inset from the start bound * @param t number of pixels to inset from the top bound * @param e number of pixels to inset from the end bound @@ -671,6 +688,126 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { setLayerInsetInternal(index, 0, t, 0, b, s, e); } + /** + * @param index the index of the layer to adjust + * @param l number of pixels to inset from the left bound + * @attr ref android.R.styleable#LayerDrawableItem_left + */ + public void setLayerInsetLeft(int index, int l) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetL = l; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the left bound + * @attr ref android.R.styleable#LayerDrawableItem_left + */ + public int getLayerInsetLeft(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetL; + } + + /** + * @param index the index of the layer to adjust + * @param r number of pixels to inset from the right bound + * @attr ref android.R.styleable#LayerDrawableItem_right + */ + public void setLayerInsetRight(int index, int r) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetR = r; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the right bound + * @attr ref android.R.styleable#LayerDrawableItem_right + */ + public int getLayerInsetRight(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetR; + } + + /** + * @param index the index of the layer to adjust + * @param t number of pixels to inset from the top bound + * @attr ref android.R.styleable#LayerDrawableItem_top + */ + public void setLayerInsetTop(int index, int t) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetT = t; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the top bound + * @attr ref android.R.styleable#LayerDrawableItem_top + */ + public int getLayerInsetTop(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetT; + } + + /** + * @param index the index of the layer to adjust + * @param b number of pixels to inset from the bottom bound + * @attr ref android.R.styleable#LayerDrawableItem_bottom + */ + public void setLayerInsetBottom(int index, int b) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetB = b; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the bottom bound + * @attr ref android.R.styleable#LayerDrawableItem_bottom + */ + public int getLayerInsetBottom(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetB; + } + + /** + * @param index the index of the layer to adjust + * @param s number of pixels to inset from the start bound + * @attr ref android.R.styleable#LayerDrawableItem_start + */ + public void setLayerInsetStart(int index, int s) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetS = s; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the start bound + * @attr ref android.R.styleable#LayerDrawableItem_start + */ + public int getLayerInsetStart(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetS; + } + + /** + * @param index the index of the layer to adjust + * @param e number of pixels to inset from the end bound + * @attr ref android.R.styleable#LayerDrawableItem_end + */ + public void setLayerInsetEnd(int index, int e) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + childDrawable.mInsetE = e; + } + + /** + * @param index the index of the layer + * @return number of pixels to inset from the end bound + * @attr ref android.R.styleable#LayerDrawableItem_end + */ + public int getLayerInsetEnd(int index) { + final ChildDrawable childDrawable = mLayerState.mChildren[index]; + return childDrawable.mInsetE; + } + private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) { final ChildDrawable childDrawable = mLayerState.mChildren[index]; childDrawable.mInsetL = l; @@ -834,7 +971,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } } - /** @hide */ @Override public void getHotspotBounds(Rect outRect) { if (mHotspotBounds != null) { @@ -1230,16 +1366,16 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mMutated = false; } - /** @hide */ @Override - public void setLayoutDirection(int layoutDirection) { - super.setLayoutDirection(layoutDirection); + public boolean onLayoutDirectionChange(int layoutDirection) { + boolean changed = false; final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i = 0; i < N; i++) { - array[i].mDrawable.setLayoutDirection(layoutDirection); + changed |= array[i].mDrawable.setLayoutDirection(layoutDirection); } updateLayerBounds(getBounds()); + return changed; } static class ChildDrawable { diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 556c59f..c6ea91d 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -614,7 +614,6 @@ public class RippleDrawable extends LayerDrawable { onHotspotBoundsChanged(); } - /** @hide */ @Override public void getHotspotBounds(Rect outRect) { outRect.set(mHotspotBounds); diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index e1991fe..595061c 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -29,6 +29,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; +import android.util.MathUtils; import android.util.TypedValue; import android.util.AttributeSet; @@ -51,12 +52,10 @@ import java.io.IOException; * @attr ref android.R.styleable#RotateDrawable_pivotY * @attr ref android.R.styleable#RotateDrawable_drawable */ -public class RotateDrawable extends Drawable implements Drawable.Callback { - private static final float MAX_LEVEL = 10000.0f; +public class RotateDrawable extends DrawableWrapper { + private static final int MAX_LEVEL = 10000; - private final RotateState mState; - - private boolean mMutated; + private RotateState mState; /** * Create a new rotating drawable with an empty state. @@ -65,100 +64,108 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { this(null, null); } - /** - * Create a new rotating drawable with the specified state. A copy of - * this state is used as the internal state for the newly created - * drawable. - * - * @param rotateState the state for this drawable - */ - private RotateDrawable(RotateState rotateState, Resources res) { - mState = new RotateState(rotateState, this, res); - } - @Override - public void draw(Canvas canvas) { - final RotateState st = mState; - final Drawable d = st.mDrawable; - final Rect bounds = d.getBounds(); - final int w = bounds.right - bounds.left; - final int h = bounds.bottom - bounds.top; - final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; - final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); - final int saveCount = canvas.save(); - canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top); - d.draw(canvas); - canvas.restoreToCount(saveCount); + updateStateFromTypedArray(a); + inflateChildDrawable(r, parser, attrs, theme); + verifyRequiredAttributes(a); + a.recycle(); } - /** - * Sets the drawable rotated by this RotateDrawable. - * - * @param drawable The drawable to rotate - */ - public void setDrawable(Drawable drawable) { - final Drawable oldDrawable = mState.mDrawable; - if (oldDrawable != drawable) { - if (oldDrawable != null) { - oldDrawable.setCallback(null); - } - mState.mDrawable = drawable; - if (drawable != null) { - drawable.setCallback(this); - } + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <rotate> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); } } - /** - * @return The drawable rotated by this RotateDrawable - */ - public Drawable getDrawable() { - return mState.mDrawable; - } - @Override - public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mState.mChangingConfigurations - | mState.mDrawable.getChangingConfigurations(); - } + void updateStateFromTypedArray(TypedArray a) { + super.updateStateFromTypedArray(a); - @Override - public void setAlpha(int alpha) { - mState.mDrawable.setAlpha(alpha); - } + final RotateState state = mState; - @Override - public int getAlpha() { - return mState.mDrawable.getAlpha(); - } + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); - @Override - public void setColorFilter(ColorFilter cf) { - mState.mDrawable.setColorFilter(cf); - } + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); - @Override - public void setTintList(ColorStateList tint) { - mState.mDrawable.setTintList(tint); + if (a.hasValue(R.styleable.RotateDrawable_pivotX)) { + final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX); + state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + if (a.hasValue(R.styleable.RotateDrawable_pivotY)) { + final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY); + state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + state.mFromDegrees = a.getFloat( + R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees); + state.mToDegrees = a.getFloat( + R.styleable.RotateDrawable_toDegrees, state.mToDegrees); + state.mCurrentDegrees = state.mFromDegrees; + + final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); + if (dr != null) { + setDrawable(dr); + } } @Override - public void setTintMode(Mode tintMode) { - mState.mDrawable.setTintMode(tintMode); + public void applyTheme(Theme t) { + final RotateState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + // The drawable may have changed as a result of applying the theme, so + // apply the theme to the wrapped drawable last. + super.applyTheme(t); } @Override - public int getOpacity() { - return mState.mDrawable.getOpacity(); + public void draw(Canvas canvas) { + final Drawable d = getDrawable(); + final Rect bounds = d.getBounds(); + final int w = bounds.right - bounds.left; + final int h = bounds.bottom - bounds.top; + final RotateState st = mState; + final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; + final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; + + final int saveCount = canvas.save(); + canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top); + d.draw(canvas); + canvas.restoreToCount(saveCount); } /** * Sets the start angle for rotation. * - * @param fromDegrees Starting angle in degrees - * + * @param fromDegrees starting angle in degrees * @see #getFromDegrees() * @attr ref android.R.styleable#RotateDrawable_fromDegrees */ @@ -170,8 +177,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } /** - * @return The starting angle for rotation in degrees - * + * @return starting angle for rotation in degrees * @see #setFromDegrees(float) * @attr ref android.R.styleable#RotateDrawable_fromDegrees */ @@ -182,8 +188,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { /** * Sets the end angle for rotation. * - * @param toDegrees Ending angle in degrees - * + * @param toDegrees ending angle in degrees * @see #getToDegrees() * @attr ref android.R.styleable#RotateDrawable_toDegrees */ @@ -195,8 +200,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } /** - * @return The ending angle for rotation in degrees - * + * @return ending angle for rotation in degrees * @see #setToDegrees(float) * @attr ref android.R.styleable#RotateDrawable_toDegrees */ @@ -211,7 +215,6 @@ 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 */ @@ -224,7 +227,6 @@ 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 */ @@ -236,9 +238,8 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * Sets whether the X pivot value represents a fraction of the drawable * width or an absolute value in pixels. * - * @param relative True if the X pivot represents a fraction of the drawable + * @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) { @@ -249,9 +250,8 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } /** - * @return True if the X pivot represents a fraction of the drawable width, + * @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() { @@ -265,7 +265,6 @@ 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 #getPivotY() * @attr ref android.R.styleable#RotateDrawable_pivotY */ @@ -278,7 +277,6 @@ 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 */ @@ -292,7 +290,6 @@ 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) { @@ -303,9 +300,8 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } /** - * @return True if the Y pivot represents a fraction of the drawable height, + * @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() { @@ -313,254 +309,36 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } @Override - public boolean canApplyTheme() { - return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); - } - - @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 boolean getPadding(Rect padding) { - return mState.mDrawable.getPadding(padding); - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - mState.mDrawable.setVisible(visible, restart); - return super.setVisible(visible, restart); - } - - @Override - public boolean isStateful() { - return mState.mDrawable.isStateful(); - } - - @Override - protected boolean onStateChange(int[] state) { - final boolean changed = mState.mDrawable.setState(state); - onBoundsChange(getBounds()); - return changed; - } - - @Override protected boolean onLevelChange(int level) { - mState.mDrawable.setLevel(level); - onBoundsChange(getBounds()); + super.onLevelChange(level); - mState.mCurrentDegrees = mState.mFromDegrees + - (mState.mToDegrees - mState.mFromDegrees) * - (level / MAX_LEVEL); + final float value = level / (float) MAX_LEVEL; + final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value); + mState.mCurrentDegrees = degrees; invalidateSelf(); return true; } @Override - protected void onBoundsChange(Rect bounds) { - mState.mDrawable.setBounds(bounds.left, bounds.top, - bounds.right, bounds.bottom); - } - - @Override - public int getIntrinsicWidth() { - return mState.mDrawable.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mState.mDrawable.getIntrinsicHeight(); - } - - @Override - public ConstantState getConstantState() { - if (mState.canConstantState()) { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; - } - return null; - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) - throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); - - // Reset mDrawable to preserve old multiple-inflate behavior. This is - // silly, but we have CTS tests that rely on it. - mState.mDrawable = null; - - updateStateFromTypedArray(a); - inflateChildElements(r, parser, attrs, theme); - verifyRequiredAttributes(a); - a.recycle(); - } - - @Override - public void applyTheme(Theme t) { - super.applyTheme(t); - - final RotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { - state.mDrawable.applyTheme(t); - } - - } - - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { - Drawable dr = null; - int type; - final int outerDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - dr = Drawable.createFromXmlInner(r, parser, attrs, theme); - } - - if (dr != null) { - mState.mDrawable = dr; - dr.setCallback(this); - } - } - - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (mState.mDrawable == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": <rotate> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - - private void updateStateFromTypedArray(TypedArray a) { - final RotateState state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); - - if (a.hasValue(R.styleable.RotateDrawable_pivotX)) { - final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX); - state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - if (a.hasValue(R.styleable.RotateDrawable_pivotY)) { - final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY); - state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - state.mFromDegrees = a.getFloat(R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees); - state.mToDegrees = a.getFloat(R.styleable.RotateDrawable_toDegrees, state.mToDegrees); - state.mCurrentDegrees = state.mFromDegrees; - - final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); - if (dr != null) { - state.mDrawable = dr; - dr.setCallback(this); - } - } - - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState.mDrawable.mutate(); - mMutated = true; - } - return this; - } - - /** - * @hide - */ - public void clearMutated() { - super.clearMutated(); - mState.mDrawable.clearMutated(); - mMutated = false; + DrawableWrapperState mutateConstantState() { + mState = new RotateState(mState); + return mState; } - /** - * Represents the state of a rotation for a given drawable. The same - * rotate drawable can be invoked with different states to drive several - * rotations at the same time. - */ - final static class RotateState extends Drawable.ConstantState { - int[] mThemeAttrs; - int mChangingConfigurations; - - Drawable mDrawable; - + static final class RotateState extends DrawableWrapper.DrawableWrapperState { boolean mPivotXRel = true; float mPivotX = 0.5f; boolean mPivotYRel = true; float mPivotY = 0.5f; - float mFromDegrees = 0.0f; float mToDegrees = 360.0f; - float mCurrentDegrees = 0.0f; - private boolean mCheckedConstantState; - private boolean mCanConstantState; + RotateState(RotateState orig) { + super(orig); - RotateState(RotateState orig, RotateDrawable owner, Resources res) { if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mChangingConfigurations = orig.mChangingConfigurations; - if (res != null) { - mDrawable = orig.mDrawable.getConstantState().newDrawable(res); - } else { - mDrawable = orig.mDrawable.getConstantState().newDrawable(); - } - mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); - mDrawable.setBounds(orig.mDrawable.getBounds()); - mDrawable.setLevel(orig.mDrawable.getLevel()); mPivotXRel = orig.mPivotXRel; mPivotX = orig.mPivotX; mPivotYRel = orig.mPivotYRel; @@ -568,38 +346,18 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { mFromDegrees = orig.mFromDegrees; mToDegrees = orig.mToDegrees; mCurrentDegrees = orig.mCurrentDegrees; - mCheckedConstantState = mCanConstantState = true; } } @Override - public boolean canApplyTheme() { - return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) - || super.canApplyTheme(); - } - - @Override - public Drawable newDrawable() { - return new RotateDrawable(this, null); - } - - @Override public Drawable newDrawable(Resources res) { return new RotateDrawable(this, res); } + } - @Override - public int getChangingConfigurations() { - return mChangingConfigurations; - } - - public boolean canConstantState() { - if (!mCheckedConstantState) { - mCanConstantState = mDrawable.getConstantState() != null; - mCheckedConstantState = true; - } + private RotateDrawable(RotateState state, Resources res) { + super(state, res); - return mCanConstantState; - } + mState = state; } } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index da722f3..0acbeda 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,17 +21,17 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.content.res.ColorStateList; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.Resources.Theme; -import android.graphics.*; -import android.graphics.PorterDuff.Mode; -import android.view.Gravity; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; import java.io.IOException; -import java.util.Collection; /** * A Drawable that changes the size of another Drawable based on its current @@ -49,44 +49,37 @@ import java.util.Collection; * @attr ref android.R.styleable#ScaleDrawable_scaleGravity * @attr ref android.R.styleable#ScaleDrawable_drawable */ -public class ScaleDrawable extends Drawable implements Drawable.Callback { - private ScaleState mState; - private boolean mMutated; +public class ScaleDrawable extends DrawableWrapper { + private static final int MAX_LEVEL = 10000; + private final Rect mTmpRect = new Rect(); + private ScaleState mState; + ScaleDrawable() { - this(null, null); + this(new ScaleState(null), null); } + /** + * Creates a new scale drawable with the specified gravity and scale + * properties. + * + * @param drawable the drawable to scale + * @param gravity gravity constant (see {@link Gravity} used to position + * the scaled drawable within the parent container + * @param scaleWidth width scaling factor [0...1] to use then the level is + * at the maximum value, or -1 to not scale width + * @param scaleHeight height scaling factor [0...1] to use then the level + * is at the maximum value, or -1 to not scale height + */ public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { - this(null, null); + this(new ScaleState(null), null); - mState.mDrawable = drawable; mState.mGravity = gravity; mState.mScaleWidth = scaleWidth; mState.mScaleHeight = scaleHeight; - if (drawable != null) { - drawable.setCallback(this); - } - } - - /** - * Returns the drawable scaled by this ScaleDrawable. - */ - public Drawable getDrawable() { - return mState.mDrawable; - } - - private static float getPercent(TypedArray a, int name, float defaultValue) { - final String s = a.getString(name); - if (s != null) { - if (s.endsWith("%")) { - String f = s.substring(0, s.length() - 1); - return Float.parseFloat(f) / 100.0f; - } - } - return defaultValue; + setDrawable(drawable); } @Override @@ -95,65 +88,15 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { super.inflate(r, parser, attrs, theme); final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); - - // Reset mDrawable to preserve old multiple-inflate behavior. This is - // silly, but we have CTS tests that rely on it. - mState.mDrawable = null; - updateStateFromTypedArray(a); - inflateChildElements(r, parser, attrs, theme); + inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - @Override - public void applyTheme(Theme t) { - super.applyTheme(t); - - final ScaleState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { - state.mDrawable.applyTheme(t); - } - } - - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { - Drawable dr = null; - int type; - final int outerDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - dr = Drawable.createFromXmlInner(r, parser, attrs, theme); - } - - if (dr != null) { - mState.mDrawable = dr; - dr.setCallback(this); - } - } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. - if (mState.mDrawable == null && (mState.mThemeAttrs == null + if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { throw new XmlPullParserException(a.getPositionDescription() + ": <scale> tag requires a 'drawable' attribute or " @@ -161,127 +104,95 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } } - private void updateStateFromTypedArray(TypedArray a) { - final ScaleState state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + @Override + void updateStateFromTypedArray(TypedArray a) { + super.updateStateFromTypedArray(a); - state.mScaleWidth = getPercent( - a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); - state.mScaleHeight = getPercent( - a, R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight); - state.mGravity = a.getInt(R.styleable.ScaleDrawable_scaleGravity, state.mGravity); + final ScaleState state = mState; + state.mScaleWidth = getPercent(a, + R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); + state.mScaleHeight = getPercent(a, + R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight); + state.mGravity = a.getInt( + R.styleable.ScaleDrawable_scaleGravity, state.mGravity); state.mUseIntrinsicSizeAsMin = a.getBoolean( R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); if (dr != null) { - state.mDrawable = dr; - dr.setCallback(this); - } - } - - @Override - public boolean canApplyTheme() { - return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); - } - - // overrides from Drawable.Callback - - public void invalidateDrawable(Drawable who) { - if (getCallback() != null) { - getCallback().invalidateDrawable(this); + setDrawable(dr); } } - public void scheduleDrawable(Drawable who, Runnable what, long when) { - if (getCallback() != null) { - getCallback().scheduleDrawable(this, what, when); + private static float getPercent(TypedArray a, int index, float defaultValue) { + final int type = a.getType(index); + if (type == TypedValue.TYPE_FRACTION || type == TypedValue.TYPE_NULL) { + return a.getFraction(index, 1, 1, defaultValue); } - } - public void unscheduleDrawable(Drawable who, Runnable what) { - if (getCallback() != null) { - getCallback().unscheduleDrawable(this, what); + // Coerce to float. + final String s = a.getString(index); + if (s != null) { + if (s.endsWith("%")) { + final String f = s.substring(0, s.length() - 1); + return Float.parseFloat(f) / 100.0f; + } } - } - - // overrides from Drawable - - @Override - public void draw(Canvas canvas) { - if (mState.mDrawable.getLevel() != 0) - mState.mDrawable.draw(canvas); - } - @Override - public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mState.mChangingConfigurations - | mState.mDrawable.getChangingConfigurations(); - } - - @Override - public boolean getPadding(Rect padding) { - // XXX need to adjust padding! - return mState.mDrawable.getPadding(padding); - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - mState.mDrawable.setVisible(visible, restart); - return super.setVisible(visible, restart); - } - - @Override - public void setAlpha(int alpha) { - mState.mDrawable.setAlpha(alpha); + return defaultValue; } @Override - public int getAlpha() { - return mState.mDrawable.getAlpha(); - } + public void applyTheme(Theme t) { + final ScaleState state = mState; + if (state == null) { + return; + } - @Override - public void setColorFilter(ColorFilter cf) { - mState.mDrawable.setColorFilter(cf); - } + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.ScaleDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } - @Override - public void setTintList(ColorStateList tint) { - mState.mDrawable.setTintList(tint); + // The drawable may have changed as a result of applying the theme, so + // apply the theme to the wrapped drawable last. + super.applyTheme(t); } @Override - public void setTintMode(Mode tintMode) { - mState.mDrawable.setTintMode(tintMode); + public void draw(Canvas canvas) { + final Drawable d = getDrawable(); + if (d != null && d.getLevel() != 0) { + d.draw(canvas); + } } @Override public int getOpacity() { - return mState.mDrawable.getOpacity(); - } + final Drawable d = getDrawable(); + if (d.getLevel() == 0) { + return PixelFormat.TRANSPARENT; + } - @Override - public boolean isStateful() { - return mState.mDrawable.isStateful(); - } + final int opacity = d.getOpacity(); + if (opacity == PixelFormat.OPAQUE && d.getLevel() < MAX_LEVEL) { + return PixelFormat.TRANSLUCENT; + } - @Override - protected boolean onStateChange(int[] state) { - boolean changed = mState.mDrawable.setState(state); - onBoundsChange(getBounds()); - return changed; + return opacity; } @Override protected boolean onLevelChange(int level) { - mState.mDrawable.setLevel(level); + super.onLevelChange(level); onBoundsChange(getBounds()); invalidateSelf(); return true; @@ -289,144 +200,67 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { @Override protected void onBoundsChange(Rect bounds) { + final Drawable d = getDrawable(); final Rect r = mTmpRect; final boolean min = mState.mUseIntrinsicSizeAsMin; - int level = getLevel(); + final int level = getLevel(); + int w = bounds.width(); if (mState.mScaleWidth > 0) { - final int iw = min ? mState.mDrawable.getIntrinsicWidth() : 0; - w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000); + final int iw = min ? d.getIntrinsicWidth() : 0; + w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL); } + int h = bounds.height(); if (mState.mScaleHeight > 0) { - final int ih = min ? mState.mDrawable.getIntrinsicHeight() : 0; - h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000); + final int ih = min ? d.getIntrinsicHeight() : 0; + h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL); } + final int layoutDirection = getLayoutDirection(); Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); if (w > 0 && h > 0) { - mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); - } - } - - @Override - public int getIntrinsicWidth() { - return mState.mDrawable.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mState.mDrawable.getIntrinsicHeight(); - } - - @Override - public ConstantState getConstantState() { - if (mState.canConstantState()) { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; + d.setBounds(r.left, r.top, r.right, r.bottom); } - return null; } @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState.mDrawable.mutate(); - mMutated = true; - } - return this; + DrawableWrapperState mutateConstantState() { + mState = new ScaleState(mState); + return mState; } - /** - * @hide - */ - public void clearMutated() { - super.clearMutated(); - mState.mDrawable.clearMutated(); - mMutated = false; - } - - final static class ScaleState extends ConstantState { + static final class ScaleState extends DrawableWrapper.DrawableWrapperState { /** Constant used to disable scaling for a particular dimension. */ private static final float DO_NOT_SCALE = -1.0f; - int[] mThemeAttrs; - int mChangingConfigurations; - - Drawable mDrawable; - float mScaleWidth = DO_NOT_SCALE; float mScaleHeight = DO_NOT_SCALE; int mGravity = Gravity.LEFT; boolean mUseIntrinsicSizeAsMin = false; - private boolean mCheckedConstantState; - private boolean mCanConstantState; + ScaleState(ScaleState orig) { + super(orig); - ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) { if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mChangingConfigurations = orig.mChangingConfigurations; - if (res != null) { - mDrawable = orig.mDrawable.getConstantState().newDrawable(res); - } else { - mDrawable = orig.mDrawable.getConstantState().newDrawable(); - } - mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); - mDrawable.setBounds(orig.mDrawable.getBounds()); - mDrawable.setLevel(orig.mDrawable.getLevel()); mScaleWidth = orig.mScaleWidth; mScaleHeight = orig.mScaleHeight; mGravity = orig.mGravity; mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin; - mCheckedConstantState = mCanConstantState = true; } } @Override - public boolean canApplyTheme() { - return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) - || super.canApplyTheme(); - } - - @Override - public Drawable newDrawable() { - return new ScaleDrawable(this, null); - } - - @Override public Drawable newDrawable(Resources res) { return new ScaleDrawable(this, res); } - - @Override - public int getChangingConfigurations() { - return mChangingConfigurations; - } - - boolean canConstantState() { - if (!mCheckedConstantState) { - mCanConstantState = mDrawable.getConstantState() != null; - mCheckedConstantState = true; - } - - return mCanConstantState; - } - - @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final ConstantState state = mDrawable.getConstantState(); - if (state != null) { - return state.addAtlasableBitmaps(atlasList); - } - return 0; - } } private ScaleDrawable(ScaleState state, Resources res) { - mState = new ScaleState(state, this, res); + super(state, res); + + mState = state; } } diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 59d0ba6..c83af11 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -309,16 +309,6 @@ public class StateListDrawable extends DrawableContainer { mMutated = false; } - /** @hide */ - @Override - public void setLayoutDirection(int layoutDirection) { - super.setLayoutDirection(layoutDirection); - - // Let the container handle setting its own layout direction. Otherwise, - // we're accessing potentially unused states. - mStateListState.setLayoutDirection(layoutDirection); - } - static class StateListState extends DrawableContainerState { int[] mThemeAttrs; int[][] mStateSets; diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index e753a7c..bfbf028 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -18,8 +18,14 @@ package android.security; import com.android.org.conscrypt.NativeCrypto; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.security.keymaster.ExportResult; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.OperationResult; import android.util.Log; import java.util.Locale; @@ -58,6 +64,8 @@ public class KeyStore { private final IKeystoreService mBinder; + private IBinder mToken; + private KeyStore(IKeystoreService binder) { mBinder = binder; } @@ -68,6 +76,13 @@ public class KeyStore { return new KeyStore(keystore); } + private synchronized IBinder getToken() { + if (mToken == null) { + mToken = new Binder(); + } + return mToken; + } + static int getKeyTypeForAlgorithm(String keyType) { if ("RSA".equalsIgnoreCase(keyType)) { return NativeCrypto.EVP_PKEY_RSA; @@ -363,4 +378,100 @@ public class KeyStore { public int getLastError() { return mError; } + + public boolean addRngEntropy(byte[] data) { + try { + return mBinder.addRngEntropy(data) == NO_ERROR; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + public int generateKey(String alias, KeymasterArguments args, int uid, int flags, + KeyCharacteristics outCharacteristics) { + try { + return mBinder.generateKey(alias, args, uid, flags, outCharacteristics); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } + + public int generateKey(String alias, KeymasterArguments args, int flags, + KeyCharacteristics outCharacteristics) { + return generateKey(alias, args, UID_SELF, flags, outCharacteristics); + } + + public int getKeyCharacteristics(String alias, byte[] clientId, byte[] appId, + KeyCharacteristics outCharacteristics) { + try { + return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } + + public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData, + int uid, int flags, KeyCharacteristics outCharacteristics) { + try { + return mBinder.importKey(alias, args, format, keyData, uid, flags, + outCharacteristics); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } + + public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData, + int flags, KeyCharacteristics outCharacteristics) { + return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics); + } + + public ExportResult exportKey(String alias, int format, byte[] clientId, byte[] appId) { + try { + return mBinder.exportKey(alias, format, clientId, appId); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return null; + } + } + + public OperationResult begin(String alias, int purpose, boolean pruneable, + KeymasterArguments args, KeymasterArguments outArgs) { + try { + return mBinder.begin(getToken(), alias, purpose, pruneable, args, outArgs); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return null; + } + } + + public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) { + try { + return mBinder.update(token, arguments, input); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return null; + } + } + + public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) { + try { + return mBinder.finish(token, arguments, signature); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return null; + } + } + + public int abort(IBinder token) { + try { + return mBinder.abort(token); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } } diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp index 4b3382e..782806e 100644 --- a/libs/androidfw/Asset.cpp +++ b/libs/androidfw/Asset.cpp @@ -532,7 +532,7 @@ off64_t _FileAsset::seek(off64_t offset, int whence) void _FileAsset::close(void) { if (mMap != NULL) { - mMap->release(); + delete mMap; mMap = NULL; } if (mBuf != NULL) { @@ -612,7 +612,7 @@ const void* _FileAsset::getBuffer(bool wordAligned) map = new FileMap; if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) { - map->release(); + delete map; return NULL; } @@ -827,7 +827,7 @@ off64_t _CompressedAsset::seek(off64_t offset, int whence) void _CompressedAsset::close(void) { if (mMap != NULL) { - mMap->release(); + delete mMap; mMap = NULL; } diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index ef0d072..af3d9b3 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -200,7 +200,7 @@ FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const FileMap* newMap = new FileMap(); if (!newMap->create(mFileName, fd, ze.offset, actualLen, true)) { - newMap->release(); + delete newMap; return NULL; } diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index fb4c785..ea93e7f 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -166,7 +166,7 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, GradientInfo info; getGradientInfo(colors, count, info); - Texture* texture = new Texture(); + Texture* texture = new Texture(Caches::getInstance()); texture->width = info.width; texture->height = 2; texture->blend = info.hasAlpha; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 659ef6c..c6fdd3f 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -402,9 +402,10 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) { clipFlags = 0; // all clipping done by saveLayer } - ATRACE_FORMAT("%s alpha caused %ssaveLayer %dx%d", - getName(), clipFlags ? "" : "unclipped ", - (int)layerBounds.getWidth(), (int)layerBounds.getHeight()); + ATRACE_FORMAT("%s alpha caused %ssaveLayer %dx%d", getName(), + (saveFlags & SkCanvas::kClipToLayer_SaveFlag) ? "" : "unclipped ", + static_cast<int>(layerBounds.getWidth()), + static_cast<int>(layerBounds.getHeight())); SaveLayerOp* op = new (handler.allocator()) SaveLayerOp( layerBounds.left, layerBounds.top, layerBounds.right, layerBounds.bottom, diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 512f5cf..593e918 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -24,46 +24,6 @@ namespace android { namespace uirenderer { -Texture::Texture() - : id(0) - , generation(0) - , blend(false) - , width(0) - , height(0) - , cleanup(false) - , bitmapSize(0) - , mipMap(false) - , uvMapper(nullptr) - , isInUse(false) - , mWrapS(GL_CLAMP_TO_EDGE) - , mWrapT(GL_CLAMP_TO_EDGE) - , mMinFilter(GL_NEAREST) - , mMagFilter(GL_NEAREST) - , mFirstFilter(true) - , mFirstWrap(true) - , mCaches(Caches::getInstance()) { -} - -Texture::Texture(Caches& caches) - : id(0) - , generation(0) - , blend(false) - , width(0) - , height(0) - , cleanup(false) - , bitmapSize(0) - , mipMap(false) - , uvMapper(nullptr) - , isInUse(false) - , mWrapS(GL_CLAMP_TO_EDGE) - , mWrapT(GL_CLAMP_TO_EDGE) - , mMinFilter(GL_NEAREST) - , mMagFilter(GL_NEAREST) - , mFirstFilter(true) - , mFirstWrap(true) - , mCaches(caches) { -} - void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, GLenum renderTarget) { diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index d5601f8..dfec462 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -30,8 +30,7 @@ class UvMapper; */ class Texture { public: - Texture(); - Texture(Caches& caches); + Texture(Caches& caches) : mCaches(caches) { } virtual ~Texture() { } @@ -59,62 +58,62 @@ public: /** * Name of the texture. */ - GLuint id; + GLuint id = 0; /** * Generation of the backing bitmap, */ - uint32_t generation; + uint32_t generation = 0; /** * Indicates whether the texture requires blending. */ - bool blend; + bool blend = false; /** * Width of the backing bitmap. */ - uint32_t width; + uint32_t width = 0; /** * Height of the backing bitmap. */ - uint32_t height; + uint32_t height = 0; /** * Indicates whether this texture should be cleaned up after use. */ - bool cleanup; + bool cleanup= false; /** * Optional, size of the original bitmap. */ - uint32_t bitmapSize; + uint32_t bitmapSize = 0; /** * Indicates whether this texture will use trilinear filtering. */ - bool mipMap; + bool mipMap = false; /** * Optional, pointer to a texture coordinates mapper. */ - const UvMapper* uvMapper; + const UvMapper* uvMapper = nullptr; /** * Whether or not the Texture is marked in use and thus not evictable for * the current frame. This is reset at the start of a new frame. */ - bool isInUse; + bool isInUse = false; private: /** - * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE. + * Last wrap modes set on this texture. */ - GLenum mWrapS; - GLenum mWrapT; + GLenum mWrapS = GL_CLAMP_TO_EDGE; + GLenum mWrapT = GL_CLAMP_TO_EDGE; /** - * Last filters set on this texture. Defaults to GL_NEAREST. + * Last filters set on this texture. */ - GLenum mMinFilter; - GLenum mMagFilter; + GLenum mMinFilter = GL_NEAREST; + GLenum mMagFilter = GL_NEAREST; - bool mFirstFilter; - bool mFirstWrap; + bool mFirstFilter = true; + bool mFirstWrap = true; Caches& mCaches; }; // struct Texture diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index f4f8e44..b911a0f 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -168,7 +168,7 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { } if (canCache) { - texture = new Texture(); + texture = new Texture(Caches::getInstance()); texture->bitmapSize = size; generateTexture(bitmap, texture, false); @@ -206,7 +206,7 @@ Texture* TextureCache::get(const SkBitmap* bitmap) { } const uint32_t size = bitmap->rowBytes() * bitmap->height(); - texture = new Texture(); + texture = new Texture(Caches::getInstance()); texture->bitmapSize = size; generateTexture(bitmap, texture, false); texture->cleanup = true; @@ -216,7 +216,7 @@ Texture* TextureCache::get(const SkBitmap* bitmap) { } Texture* TextureCache::getTransient(const SkBitmap* bitmap) { - Texture* texture = new Texture(); + Texture* texture = new Texture(Caches::getInstance()); texture->bitmapSize = bitmap->rowBytes() * bitmap->height(); texture->cleanup = true; diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp index 0431e22..b61d72f 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -89,8 +89,6 @@ public: android::uirenderer::Rect DUMMY; - std::vector< sp<RenderNode> > cards; - DisplayListRenderer* renderer = startRecording(rootNode); animation.createContent(width, height, renderer); endRecording(renderer, rootNode); diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 81d5afe..97b3f63 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -437,10 +437,7 @@ public class MediaRecorder public void setCaptureRate(double fps) { // Make sure that time lapse is enabled when this method is called. setParameter("time-lapse-enable=1"); - - double timeBetweenFrameCapture = 1 / fps; - long timeBetweenFrameCaptureUs = (long) (1000000 * timeBetweenFrameCapture); - setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureUs); + setParameter("time-lapse-fps=" + fps); } /** diff --git a/core/java/android/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl index 31fdbbb..71914ad 100644 --- a/core/java/android/midi/IMidiDeviceServer.aidl +++ b/media/java/android/media/midi/IMidiDeviceServer.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.ParcelFileDescriptor; diff --git a/core/java/android/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiListener.aidl index b650593..a4129e9 100644 --- a/core/java/android/midi/IMidiListener.aidl +++ b/media/java/android/media/midi/IMidiListener.aidl @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; -import android.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceInfo; /** @hide */ oneway interface IMidiListener diff --git a/core/java/android/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index 575b525..bba35f5 100644 --- a/core/java/android/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -14,11 +14,11 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; -import android.midi.IMidiDeviceServer; -import android.midi.IMidiListener; -import android.midi.MidiDeviceInfo; +import android.media.midi.IMidiDeviceServer; +import android.media.midi.IMidiListener; +import android.media.midi.MidiDeviceInfo; import android.os.Bundle; import android.os.IBinder; diff --git a/core/java/android/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java index b91aedf..36710fd 100644 --- a/core/java/android/midi/MidiDevice.java +++ b/media/java/android/media/midi/MidiDevice.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.ParcelFileDescriptor; import android.os.RemoteException; diff --git a/core/java/android/midi/MidiDeviceInfo.aidl b/media/java/android/media/midi/MidiDeviceInfo.aidl index 59be059..f2f37a2 100644 --- a/core/java/android/midi/MidiDeviceInfo.aidl +++ b/media/java/android/media/midi/MidiDeviceInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; parcelable MidiDeviceInfo; diff --git a/core/java/android/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index dde2669..fd35052 100644 --- a/core/java/android/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.Bundle; import android.os.Parcel; diff --git a/core/java/android/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java index 4a1995f..3317baa 100644 --- a/core/java/android/midi/MidiDeviceServer.java +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.ParcelFileDescriptor; import android.os.RemoteException; diff --git a/core/java/android/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java index 735c68a..730d364 100644 --- a/core/java/android/midi/MidiInputPort.java +++ b/media/java/android/media/midi/MidiInputPort.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.ParcelFileDescriptor; diff --git a/core/java/android/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 3a0b064..410120d 100644 --- a/core/java/android/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; @@ -50,18 +51,38 @@ public class MidiManager { // Binder stub for receiving device notifications from MidiService private class DeviceListener extends IMidiListener.Stub { - private DeviceCallback mCallback; + private final DeviceCallback mCallback; + private final Handler mHandler; - public DeviceListener(DeviceCallback callback) { + public DeviceListener(DeviceCallback callback, Handler handler) { mCallback = callback; + mHandler = handler; } public void onDeviceAdded(MidiDeviceInfo device) { - mCallback.onDeviceAdded(device); + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceAdded(deviceF); + } + }); + } else { + mCallback.onDeviceAdded(device); + } } public void onDeviceRemoved(MidiDeviceInfo device) { - mCallback.onDeviceRemoved(device); + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceRemoved(deviceF); + } + }); + } else { + mCallback.onDeviceRemoved(device); + } } } @@ -74,7 +95,7 @@ public class MidiManager { * * @param device a {@link MidiDeviceInfo} for the newly added device */ - void onDeviceAdded(MidiDeviceInfo device) { + public void onDeviceAdded(MidiDeviceInfo device) { } /** @@ -82,7 +103,7 @@ public class MidiManager { * * @param device a {@link MidiDeviceInfo} for the removed device */ - void onDeviceRemoved(MidiDeviceInfo device) { + public void onDeviceRemoved(MidiDeviceInfo device) { } } @@ -98,9 +119,12 @@ public class MidiManager { * Registers a callback to receive notifications when MIDI devices are added and removed. * * @param callback a {@link DeviceCallback} for MIDI device notifications + * @param handler The {@link android.os.Handler Handler} that will be used for delivering the + * device notifications. If handler is null, then the thread used for the + * callback is unspecified. */ - public void registerDeviceCallback(DeviceCallback callback) { - DeviceListener deviceListener = new DeviceListener(callback); + public void registerDeviceCallback(DeviceCallback callback, Handler handler) { + DeviceListener deviceListener = new DeviceListener(callback, handler); try { mService.registerListener(mToken, deviceListener); } catch (RemoteException e) { @@ -143,7 +167,7 @@ public class MidiManager { /** * Opens a MIDI device for reading and writing. * - * @param deviceInfo a {@link android.midi.MidiDeviceInfo} to open + * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open * @return a {@link MidiDevice} object for the device */ public MidiDevice openDevice(MidiDeviceInfo deviceInfo) { diff --git a/core/java/android/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java index b9512fd..83ddeeb 100644 --- a/core/java/android/midi/MidiOutputPort.java +++ b/media/java/android/media/midi/MidiOutputPort.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.os.ParcelFileDescriptor; import android.util.Log; diff --git a/core/java/android/midi/MidiPort.java b/media/java/android/media/midi/MidiPort.java index 7512a90..4d3c91d 100644 --- a/core/java/android/midi/MidiPort.java +++ b/media/java/android/media/midi/MidiPort.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import android.util.Log; @@ -33,12 +33,16 @@ abstract public class MidiPort implements Closeable { private final int mPortNumber; /** - * Maximum size of a packet that can pass through our ParcelFileDescriptor + * Maximum size of a packet that can pass through our ParcelFileDescriptor. + * For internal use only. Implementation details may change in the future. + * @hide */ - protected static final int MAX_PACKET_SIZE = 1024; + public static final int MAX_PACKET_SIZE = 1024; /** * size of message timestamp in bytes + * For internal use only. Implementation details may change in the future. + * @hide */ private static final int TIMESTAMP_SIZE = 8; @@ -65,6 +69,7 @@ abstract public class MidiPort implements Closeable { * Called when an IOExeption occurs while sending or receiving data. * Subclasses can override to be notified of such errors * + * @hide */ public void onIOException() { } @@ -77,8 +82,11 @@ abstract public class MidiPort implements Closeable { * timestamp is message timestamp to pack * dest is buffer to pack into * returns size of packed message + * + * For internal use only. Implementation details may change in the future. + * @hide */ - protected static int packMessage(byte[] message, int offset, int size, long timestamp, + public static int packMessage(byte[] message, int offset, int size, long timestamp, byte[] dest) { if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) { size = MAX_PACKET_SIZE - TIMESTAMP_SIZE; @@ -98,8 +106,11 @@ abstract public class MidiPort implements Closeable { /** * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor * returns the offset of the MIDI message in packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide */ - protected static int getMessageOffset(byte[] buffer, int bufferLength) { + public static int getMessageOffset(byte[] buffer, int bufferLength) { // message is at the beginning return 0; } @@ -107,8 +118,11 @@ abstract public class MidiPort implements Closeable { /** * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor * returns size of MIDI data in packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide */ - protected static int getMessageSize(byte[] buffer, int bufferLength) { + public static int getMessageSize(byte[] buffer, int bufferLength) { // message length is total buffer length minus size of the timestamp return bufferLength - TIMESTAMP_SIZE; } @@ -116,8 +130,11 @@ abstract public class MidiPort implements Closeable { /** * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor * unpacks timestamp from packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide */ - protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) { + public static long getMessageTimeStamp(byte[] buffer, int bufferLength) { // timestamp is at end of the packet int offset = bufferLength; long timestamp = 0; diff --git a/core/java/android/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java index 16c9bbb..64c0c07 100644 --- a/core/java/android/midi/MidiReceiver.java +++ b/media/java/android/media/midi/MidiReceiver.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; import java.io.IOException; /** - * Interface for receiving data from a MIDI device. + * Interface for sending and receiving data to and from a MIDI device. * * CANDIDATE FOR PUBLIC API * @hide diff --git a/core/java/android/midi/MidiSender.java b/media/java/android/media/midi/MidiSender.java index 2b7afad..4550476 100644 --- a/core/java/android/midi/MidiSender.java +++ b/media/java/android/media/midi/MidiSender.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.media.midi; /** * Interface provided by a device to allow attaching diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..6e29d23 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..6110e9e --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..6cca225 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..6445f2a --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..78c0294 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..2fcc3b0 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..70d35bf --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..2d9b75e --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..b6ebe34 --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..8b67b91 --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..1fa0a3d --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..175bd78 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..05b27e8 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..6e9f8ae --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..5f3371f --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..539d77f --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..3216776 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..4f381ba --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..c67127d --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..d3b356b --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..2d38129 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..fb76575 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..d8b68eb --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..02cc3af --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..7805b7a --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..8127774 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..84b8085 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..289d6ac --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..72bc804 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..e31ce2b --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..f23b0e7 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..1e12f96 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..8b547d9 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..03c5033 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..b9a9923 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..989e1ab --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..de8c389 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..2eb8a92 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f055a2c..870afeb 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -67,4 +67,87 @@ <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] --> <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string> + + <!-- Bluetooth settings. Message when a device is disconnected --> + <string name="bluetooth_disconnected">Disconnected</string> + <!-- Bluetooth settings. Message when disconnecting from a device --> + <string name="bluetooth_disconnecting">Disconnecting\u2026</string> + <!-- Bluetooth settings. Message when connecting to a device --> + <string name="bluetooth_connecting">Connecting\u2026</string> + <!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] --> + <string name="bluetooth_connected">Connected</string> + + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. --> + <string name="bluetooth_profile_a2dp">Media audio</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. --> + <string name="bluetooth_profile_headset">Phone audio</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the OPP profile. --> + <string name="bluetooth_profile_opp">File transfer</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. --> + <string name="bluetooth_profile_hid">Input device</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pan">Internet access</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pbap">Contact sharing</string> + <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] --> + <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pan_nap">Internet connection sharing</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. --> + <string name="bluetooth_profile_map">Message Access</string> + + <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. --> + <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference when headset is connected. --> + <string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is connected. --> + <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string> + <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference when map is connected. --> + <string name="bluetooth_map_profile_summary_connected">Connected to map</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is not connected. --> + <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string> + <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. --> + <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string> + <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]--> + <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string> + <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]--> + <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string> + + <!-- Bluetooth settings. Connection options screen. The summary + for the PAN checkbox preference that describes how checking it + will set the PAN profile as preferred. --> + <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string> + <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. --> + <string name="bluetooth_map_profile_summary_use_for">Use for map</string> + <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. --> + <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. --> + <string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. --> + <string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string> + <!-- Bluetooth settings. Connection options screen. The summary + for the HID checkbox preference that describes how checking it + will set the HID profile as preferred. --> + <string name="bluetooth_hid_profile_summary_use_for">Use for input</string> + + <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_accept">Pair</string> + <!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_accept_all_caps">PAIR</string> + <!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_decline">Cancel</string> + + <!-- Message in pairing dialogs. [CHAR LIMIT=NONE] --> + <string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string> + <!-- Message for the error dialog when BT pairing fails generically. --> + <string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string> + + <!-- Message for the error dialog when BT pairing fails because the PIN / + Passkey entered is incorrect. --> + <string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string> + <!-- Message for the error dialog when BT pairing fails because the other device is down. --> + <string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string> + <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. --> + <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java new file mode 100644 index 0000000..58e5e29 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; + +public class TetherUtil { + + // Types of tethering. + public static final int TETHERING_INVALID = -1; + public static final int TETHERING_WIFI = 0; + public static final int TETHERING_USB = 1; + public static final int TETHERING_BLUETOOTH = 2; + + // Extras used for communicating with the TetherService. + public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + /** + * Tells the service to run a provision check now. + */ + public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + /** + * Enables wifi tethering if the provision check is successful. Used by + * QS to enable tethering. + */ + public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether"; + + public static ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources + .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); + + public static boolean setWifiTethering(boolean enable, Context context) { + final WifiManager wifiManager = + (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + final ContentResolver cr = context.getContentResolver(); + /** + * Disable Wifi if enabling tethering + */ + int wifiState = wifiManager.getWifiState(); + if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) || + (wifiState == WifiManager.WIFI_STATE_ENABLED))) { + wifiManager.setWifiEnabled(false); + Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1); + } + + boolean success = wifiManager.setWifiApEnabled(null, enable); + /** + * If needed, restore Wifi on tether disable + */ + if (!enable) { + int wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); + if (wifiSavedState == 1) { + wifiManager.setWifiEnabled(true); + Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); + } + } + return success; + } + + public static boolean isWifiTetherEnabled(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + return wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED; + } + + public static boolean isProvisioningNeeded(Context context) { + // Keep in sync with other usage of config_mobile_hotspot_provision_app. + // ConnectivityManager#enforceTetherChangePermission + String[] provisionApp = context.getResources().getStringArray( + com.android.internal.R.array.config_mobile_hotspot_provision_app); + if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) + || provisionApp == null) { + return false; + } + return (provisionApp.length == 2); + } + + public static boolean isTetheringSupported(Context context) { + final ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER; + return !isSecondaryUser && cm.isTetheringSupported(); + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java new file mode 100755 index 0000000..9608daa --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +public final class A2dpProfile implements LocalBluetoothProfile { + private static final String TAG = "A2dpProfile"; + private static boolean V = false; + + private BluetoothA2dp mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + + static final ParcelUuid[] SINK_UUIDS = { + BluetoothUuid.AudioSink, + BluetoothUuid.AdvAudioDist, + }; + + static final String NAME = "A2DP"; + private final LocalBluetoothProfileManager mProfileManager; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class A2dpServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothA2dp) proxy; + // We just bound to the service, so refresh the UI for any connected A2DP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "A2dpProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + A2dpProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(), + BluetoothProfile.A2DP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){ + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + boolean isA2dpPlaying() { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (!sinks.isEmpty()) { + if (mService.isA2dpPlaying(sinks.get(0))) { + return true; + } + } + return false; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_a2dp; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_a2dp_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_a2dp_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headphones_a2dp; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up A2DP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java new file mode 100644 index 0000000..b802f58 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + + +/** + * BluetoothCallback provides a callback interface for the settings + * UI to receive events from {@link BluetoothEventManager}. + */ +public interface BluetoothCallback { + void onBluetoothStateChanged(int bluetoothState); + void onScanningStateChanged(boolean started); + void onDeviceAdded(CachedBluetoothDevice cachedDevice); + void onDeviceDeleted(CachedBluetoothDevice cachedDevice); + void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java new file mode 100644 index 0000000..8dec86a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +/** + * BluetoothDeviceFilter contains a static method that returns a + * Filter object that returns whether or not the BluetoothDevice + * passed to it matches the specified filter type constant from + * {@link android.bluetooth.BluetoothDevicePicker}. + */ +public final class BluetoothDeviceFilter { + private static final String TAG = "BluetoothDeviceFilter"; + + /** The filter interface to external classes. */ + public interface Filter { + boolean matches(BluetoothDevice device); + } + + /** All filter singleton (referenced directly). */ + public static final Filter ALL_FILTER = new AllFilter(); + + /** Bonded devices only filter (referenced directly). */ + public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter(); + + /** Unbonded devices only filter (referenced directly). */ + public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter(); + + /** Table of singleton filter objects. */ + private static final Filter[] FILTERS = { + ALL_FILTER, // FILTER_TYPE_ALL + new AudioFilter(), // FILTER_TYPE_AUDIO + new TransferFilter(), // FILTER_TYPE_TRANSFER + new PanuFilter(), // FILTER_TYPE_PANU + new NapFilter() // FILTER_TYPE_NAP + }; + + /** Private constructor. */ + private BluetoothDeviceFilter() { + } + + /** + * Returns the singleton {@link Filter} object for the specified type, + * or {@link #ALL_FILTER} if the type value is out of range. + * + * @param filterType a constant from BluetoothDevicePicker + * @return a singleton object implementing the {@link Filter} interface. + */ + public static Filter getFilter(int filterType) { + if (filterType >= 0 && filterType < FILTERS.length) { + return FILTERS[filterType]; + } else { + Log.w(TAG, "Invalid filter type " + filterType + " for device picker"); + return ALL_FILTER; + } + } + + /** Filter that matches all devices. */ + private static final class AllFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return true; + } + } + + /** Filter that matches only bonded devices. */ + private static final class BondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() == BluetoothDevice.BOND_BONDED; + } + } + + /** Filter that matches only unbonded devices. */ + private static final class UnbondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() != BluetoothDevice.BOND_BONDED; + } + } + + /** Parent class of filters based on UUID and/or Bluetooth class. */ + private abstract static class ClassUuidFilter implements Filter { + abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass); + + public boolean matches(BluetoothDevice device) { + return matches(device.getUuids(), device.getBluetoothClass()); + } + } + + /** Filter that matches devices that support AUDIO profiles. */ + private static final class AudioFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) { + return true; + } + if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) { + return true; + } + } else if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || + btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return true; + } + } + return false; + } + } + + /** Filter that matches devices that support Object Transfer. */ + private static final class TransferFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP); + } + } + + /** Filter that matches devices that support PAN User (PANU) profile. */ + private static final class PanuFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU); + } + } + + /** Filter that matches devices that support NAP profile. */ + private static final class NapFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java new file mode 100644 index 0000000..acb7e7a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +/* Required to handle timeout notification when phone is suspended */ +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + + +public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver { + private static final String TAG = "BluetoothDiscoverableTimeoutReceiver"; + + private static final String INTENT_DISCOVERABLE_TIMEOUT = + "android.bluetooth.intent.DISCOVERABLE_TIMEOUT"; + + public static void setDiscoverableAlarm(Context context, long alarmTime) { + Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + if (pending != null) { + // Cancel any previous alarms that do the same thing. + alarmManager.cancel(pending); + Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm"); + } + pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + + alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending); + } + + public static void cancelDiscoverableAlarm(Context context) { + Log.d(TAG, "cancelDiscoverableAlarm(): Enter"); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_NO_CREATE); + if (pending != null) { + // Cancel any previous alarms that do the same thing. + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + alarmManager.cancel(pending); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance(); + + if(localBluetoothAdapter != null && + localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + Log.d(TAG, "Disable discoverable..."); + + localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } else { + Log.e(TAG, "localBluetoothAdapter is NULL!!"); + } + } +}; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java new file mode 100755 index 0000000..7c92368 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth + * API and dispatches the event on the UI thread to the right class in the + * Settings. + */ +public final class BluetoothEventManager { + private static final String TAG = "BluetoothEventManager"; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; + private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter; + private final Map<String, Handler> mHandlerMap; + private Context mContext; + + private final Collection<BluetoothCallback> mCallbacks = + new ArrayList<BluetoothCallback>(); + + interface Handler { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + void addHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mAdapterIntentFilter.addAction(action); + } + + void addProfileHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mProfileIntentFilter.addAction(action); + } + + // Set profile manager after construction due to circular dependency + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + BluetoothEventManager(LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, Context context) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mAdapterIntentFilter = new IntentFilter(); + mProfileIntentFilter = new IntentFilter(); + mHandlerMap = new HashMap<String, Handler>(); + mContext = context; + + // Bluetooth on/off broadcasts + addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); + + // Discovery broadcasts + addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); + addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); + addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); + addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); + addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); + + // Pairing broadcasts + addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); + addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); + + // Fine-grained state broadcasts + addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); + addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); + + // Dock event broadcasts + addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); + + mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter); + } + + void registerProfileIntentReceiver() { + mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter); + } + + /** Register to start receiving callbacks for Bluetooth events. */ + public void registerCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + /** Unregister to stop receiving callbacks for Bluetooth events. */ + public void unregisterCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + Handler handler = mHandlerMap.get(action); + if (handler != null) { + handler.onReceive(context, intent, device); + } + } + }; + + private class AdapterStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + // update local profiles and get paired devices + mLocalAdapter.setBluetoothStateInt(state); + // send callback to update UI and possibly start scanning + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(state); + } + } + // Inform CachedDeviceManager that the adapter state has changed + mDeviceManager.onBluetoothStateChanged(state); + } + } + + private class ScanningStateChangedHandler implements Handler { + private final boolean mStarted; + + ScanningStateChangedHandler(boolean started) { + mStarted = started; + } + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onScanningStateChanged(mStarted); + } + } + mDeviceManager.onScanningStateChanged(mStarted); + } + } + + private class DeviceFoundHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); + BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); + String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); + // TODO Pick up UUID. They should be available for 2.1 devices. + // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + + cachedDevice); + // callback to UI to create Preference for new device + dispatchDeviceAdded(cachedDevice); + } + cachedDevice.setRssi(rssi); + cachedDevice.setBtClass(btClass); + cachedDevice.setNewName(name); + cachedDevice.setVisible(true); + } + } + + private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceAdded(cachedDevice); + } + } + } + + private class DeviceDisappearedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); + return; + } + if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceDeleted(cachedDevice); + } + } + } + } + } + + private class NameChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onDeviceNameUpdated(device); + } + } + + private class BondStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + return; + } + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "CachedBluetoothDevice for device " + device + + " not found, calling readPairedDevices()."); + if (!readPairedDevices()) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but we have no record of that device."); + return; + } + cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but device not added in cache."); + return; + } + } + + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceBondStateChanged(cachedDevice, bondState); + } + } + cachedDevice.onBondingStateChanged(bondState); + + if (bondState == BluetoothDevice.BOND_NONE) { + int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, + BluetoothDevice.ERROR); + + showUnbondMessage(context, cachedDevice.getName(), reason); + } + } + + /** + * Called when we have reached the unbonded state. + * + * @param reason one of the error reasons from + * BluetoothDevice.UNBOND_REASON_* + */ + private void showUnbondMessage(Context context, String name, int reason) { + int errorMsg; + + switch(reason) { + case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: + errorMsg = R.string.bluetooth_pairing_pin_error_message; + break; + case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: + errorMsg = R.string.bluetooth_pairing_rejected_error_message; + break; + case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: + errorMsg = R.string.bluetooth_pairing_device_down_error_message; + break; + case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: + case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: + case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: + case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: + errorMsg = R.string.bluetooth_pairing_error_message; + break; + default: + Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); + return; + } + Utils.showError(context, name, errorMsg); + } + } + + private class ClassChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onBtClassChanged(device); + } + } + + private class UuidChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onUuidChanged(device); + } + } + + private class PairingCancelHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); + return; + } + int errorMsg = R.string.bluetooth_pairing_error_message; + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + Utils.showError(context, cachedDevice.getName(), errorMsg); + } + } + + private class DockEventHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + // Remove if unpair device upon undocking + int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice != null) { + cachedDevice.setVisible(false); + } + } + } + } + } + boolean readPairedDevices() { + Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); + if (bondedDevices == null) { + return false; + } + + boolean deviceAdded = false; + for (BluetoothDevice device : bondedDevices) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + dispatchDeviceAdded(cachedDevice); + deviceAdded = true; + } + } + + return deviceAdded; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java new file mode 100755 index 0000000..ddcc49f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2008 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.ParcelUuid; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; +import android.bluetooth.BluetoothAdapter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * CachedBluetoothDevice represents a remote Bluetooth device. It contains + * attributes of the device (such as the address, name, RSSI, etc.) and + * functionality that can be performed on the device (connect, pair, disconnect, + * etc.). + */ +public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { + private static final String TAG = "CachedBluetoothDevice"; + private static final boolean DEBUG = Utils.V; + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final LocalBluetoothProfileManager mProfileManager; + private final BluetoothDevice mDevice; + private String mName; + private short mRssi; + private BluetoothClass mBtClass; + private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState; + + private final List<LocalBluetoothProfile> mProfiles = + new ArrayList<LocalBluetoothProfile>(); + + // List of profiles that were previously in mProfiles, but have been removed + private final List<LocalBluetoothProfile> mRemovedProfiles = + new ArrayList<LocalBluetoothProfile>(); + + // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP + private boolean mLocalNapRoleConnected; + + private boolean mVisible; + + private int mPhonebookPermissionChoice; + + private int mMessagePermissionChoice; + + private int mMessageRejectionCount; + + private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); + + // Following constants indicate the user's choices of Phone book/message access settings + // User hasn't made any choice or settings app has wiped out the memory + public final static int ACCESS_UNKNOWN = 0; + // User has accepted the connection and let Settings app remember the decision + public final static int ACCESS_ALLOWED = 1; + // User has rejected the connection and let Settings app remember the decision + public final static int ACCESS_REJECTED = 2; + + // How many times user should reject the connection to make the choice persist. + private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2; + + private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject"; + + /** + * When we connect to multiple profiles, we only want to display a single + * error even if they all fail. This tracks that state. + */ + private boolean mIsConnectingErrorPossible; + + /** + * Last time a bt profile auto-connect was attempted. + * If an ACTION_UUID intent comes in within + * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect + * again with the new UUIDs + */ + private long mConnectAttempted; + + // See mConnectAttempted + private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; + + /** Auto-connect after pairing only if locally initiated. */ + private boolean mConnectAfterPairing; + + /** + * Describes the current device and profile for logging. + * + * @param profile Profile to describe + * @return Description of the device and profile + */ + private String describe(LocalBluetoothProfile profile) { + StringBuilder sb = new StringBuilder(); + sb.append("Address:").append(mDevice); + if (profile != null) { + sb.append(" Profile:").append(profile); + } + + return sb.toString(); + } + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (Utils.D) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + + " newProfileState " + newProfileState); + } + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) + { + if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored..."); + return; + } + mProfileConnectionState.put(profile, newProfileState); + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { + if (profile instanceof MapProfile) { + profile.setPreferred(mDevice, true); + } else if (!mProfiles.contains(profile)) { + mRemovedProfiles.remove(profile); + mProfiles.add(profile); + if (profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice)) { + // Device doesn't support NAP, so remove PanProfile on disconnect + mLocalNapRoleConnected = true; + } + } + } else if (profile instanceof MapProfile && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + profile.setPreferred(mDevice, false); + } else if (mLocalNapRoleConnected && profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice) && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "Removing PanProfile from device after NAP disconnect"); + mProfiles.remove(profile); + mRemovedProfiles.add(profile); + mLocalNapRoleConnected = false; + } + } + + CachedBluetoothDevice(Context context, + LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mContext = context; + mLocalAdapter = adapter; + mProfileManager = profileManager; + mDevice = device; + mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>(); + fillData(); + } + + public void disconnect() { + for (LocalBluetoothProfile profile : mProfiles) { + disconnect(profile); + } + // Disconnect PBAP server in case its connected + // This is to ensure all the profiles are disconnected as some CK/Hs do not + // disconnect PBAP connection when HF connection is brought down + PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); + if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED) + { + PbapProfile.disconnect(mDevice); + } + } + + public void disconnect(LocalBluetoothProfile profile) { + if (profile.disconnect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); + } + } + } + + public void connect(boolean connectAllProfiles) { + if (!ensurePaired()) { + return; + } + + mConnectAttempted = SystemClock.elapsedRealtime(); + connectWithoutResettingTimer(connectAllProfiles); + } + + void onBondingDockConnect() { + // Attempt to connect if UUIDs are available. Otherwise, + // we will connect when the ACTION_UUID intent arrives. + connect(false); + } + + private void connectWithoutResettingTimer(boolean connectAllProfiles) { + // Try to initialize the profiles if they were not. + if (mProfiles.isEmpty()) { + // if mProfiles is empty, then do not invoke updateProfiles. This causes a race + // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated + // from bluetooth stack but ACTION.uuid is not sent yet. + // Eventually ACTION.uuid will be received which shall trigger the connection of the + // various profiles + // If UUIDs are not available yet, connect will be happen + // upon arrival of the ACTION_UUID intent. + Log.d(TAG, "No profiles. Maybe we will connect later"); + return; + } + + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + int preferredProfiles = 0; + for (LocalBluetoothProfile profile : mProfiles) { + if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { + if (profile.isPreferred(mDevice)) { + ++preferredProfiles; + connectInt(profile); + } + } + } + if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); + + if (preferredProfiles == 0) { + connectAutoConnectableProfiles(); + } + } + + private void connectAutoConnectableProfiles() { + if (!ensurePaired()) { + return; + } + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isAutoConnectable()) { + profile.setPreferred(mDevice, true); + connectInt(profile); + } + } + } + + /** + * Connect this device to the specified profile. + * + * @param profile the profile to use with the remote device + */ + public void connectProfile(LocalBluetoothProfile profile) { + mConnectAttempted = SystemClock.elapsedRealtime(); + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + connectInt(profile); + // Refresh the UI based on profile.connect() call + refresh(); + } + + synchronized void connectInt(LocalBluetoothProfile profile) { + if (!ensurePaired()) { + return; + } + if (profile.connect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); + } + return; + } + Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); + } + + private boolean ensurePaired() { + if (getBondState() == BluetoothDevice.BOND_NONE) { + startPairing(); + return false; + } else { + return true; + } + } + + public boolean startPairing() { + // Pairing is unreliable while scanning, so cancel discovery + if (mLocalAdapter.isDiscovering()) { + mLocalAdapter.cancelDiscovery(); + } + + if (!mDevice.createBond()) { + return false; + } + + mConnectAfterPairing = true; // auto-connect after pairing + return true; + } + + /** + * Return true if user initiated pairing on this device. The message text is + * slightly different for local vs. remote initiated pairing dialogs. + */ + boolean isUserInitiatedPairing() { + return mConnectAfterPairing; + } + + public void unpair() { + int state = getBondState(); + + if (state == BluetoothDevice.BOND_BONDING) { + mDevice.cancelBondProcess(); + } + + if (state != BluetoothDevice.BOND_NONE) { + final BluetoothDevice dev = mDevice; + if (dev != null) { + final boolean successful = dev.removeBond(); + if (successful) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); + } + } else if (Utils.V) { + Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + + describe(null)); + } + } + } + } + + public int getProfileConnectionState(LocalBluetoothProfile profile) { + if (mProfileConnectionState == null || + mProfileConnectionState.get(profile) == null) { + // If cache is empty make the binder call to get the state + int state = profile.getConnectionStatus(mDevice); + mProfileConnectionState.put(profile, state); + } + return mProfileConnectionState.get(profile); + } + + public void clearProfileConnectionState () + { + if (Utils.D) { + Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName()); + } + for (LocalBluetoothProfile profile :getProfiles()) { + mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED); + } + } + + // TODO: do any of these need to run async on a background thread? + private void fillData() { + fetchName(); + fetchBtClass(); + updateProfiles(); + migratePhonebookPermissionChoice(); + migrateMessagePermissionChoice(); + fetchMessageRejectionCount(); + + mVisible = false; + dispatchAttributesChanged(); + } + + public BluetoothDevice getDevice() { + return mDevice; + } + + public String getName() { + return mName; + } + + /** + * Populate name from BluetoothDevice.ACTION_FOUND intent + */ + void setNewName(String name) { + if (mName == null) { + mName = name; + if (mName == null || TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + } + dispatchAttributesChanged(); + } + } + + /** + * user changes the device name + */ + public void setName(String name) { + if (!mName.equals(name)) { + mName = name; + mDevice.setAlias(name); + dispatchAttributesChanged(); + } + } + + void refreshName() { + fetchName(); + dispatchAttributesChanged(); + } + + private void fetchName() { + mName = mDevice.getAliasName(); + + if (TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); + } + } + + void refresh() { + dispatchAttributesChanged(); + } + + public boolean isVisible() { + return mVisible; + } + + public void setVisible(boolean visible) { + if (mVisible != visible) { + mVisible = visible; + dispatchAttributesChanged(); + } + } + + public int getBondState() { + return mDevice.getBondState(); + } + + void setRssi(short rssi) { + if (mRssi != rssi) { + mRssi = rssi; + dispatchAttributesChanged(); + } + } + + /** + * Checks whether we are connected to this device (any profile counts). + * + * @return Whether it is connected. + */ + public boolean isConnected() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + return true; + } + } + + return false; + } + + public boolean isConnectedProfile(LocalBluetoothProfile profile) { + int status = getProfileConnectionState(profile); + return status == BluetoothProfile.STATE_CONNECTED; + + } + + public boolean isBusy() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTING + || status == BluetoothProfile.STATE_DISCONNECTING) { + return true; + } + } + return getBondState() == BluetoothDevice.BOND_BONDING; + } + + /** + * Fetches a new value for the cached BT class. + */ + private void fetchBtClass() { + mBtClass = mDevice.getBluetoothClass(); + } + + private boolean updateProfiles() { + ParcelUuid[] uuids = mDevice.getUuids(); + if (uuids == null) return false; + + ParcelUuid[] localUuids = mLocalAdapter.getUuids(); + if (localUuids == null) return false; + + /** + * Now we know if the device supports PBAP, update permissions... + */ + processPhonebookAccess(); + + mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles, + mLocalNapRoleConnected, mDevice); + + if (DEBUG) { + Log.e(TAG, "updating profiles for " + mDevice.getAliasName()); + BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); + + if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); + Log.v(TAG, "UUID:"); + for (ParcelUuid uuid : uuids) { + Log.v(TAG, " " + uuid); + } + } + return true; + } + + /** + * Refreshes the UI for the BT class, including fetching the latest value + * for the class. + */ + void refreshBtClass() { + fetchBtClass(); + dispatchAttributesChanged(); + } + + /** + * Refreshes the UI when framework alerts us of a UUID change. + */ + void onUuidChanged() { + updateProfiles(); + + if (DEBUG) { + Log.e(TAG, "onUuidChanged: Time since last connect" + + (SystemClock.elapsedRealtime() - mConnectAttempted)); + } + + /* + * If a connect was attempted earlier without any UUID, we will do the + * connect now. + */ + if (!mProfiles.isEmpty() + && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock + .elapsedRealtime()) { + connectWithoutResettingTimer(false); + } + dispatchAttributesChanged(); + } + + void onBondingStateChanged(int bondState) { + if (bondState == BluetoothDevice.BOND_NONE) { + mProfiles.clear(); + mConnectAfterPairing = false; // cancel auto-connect + setPhonebookPermissionChoice(ACCESS_UNKNOWN); + setMessagePermissionChoice(ACCESS_UNKNOWN); + mMessageRejectionCount = 0; + saveMessageRejectionCount(); + } + + refresh(); + + if (bondState == BluetoothDevice.BOND_BONDED) { + if (mDevice.isBluetoothDock()) { + onBondingDockConnect(); + } else if (mConnectAfterPairing) { + connect(false); + } + mConnectAfterPairing = false; + } + } + + void setBtClass(BluetoothClass btClass) { + if (btClass != null && mBtClass != btClass) { + mBtClass = btClass; + dispatchAttributesChanged(); + } + } + + public BluetoothClass getBtClass() { + return mBtClass; + } + + public List<LocalBluetoothProfile> getProfiles() { + return Collections.unmodifiableList(mProfiles); + } + + public List<LocalBluetoothProfile> getConnectableProfiles() { + List<LocalBluetoothProfile> connectableProfiles = + new ArrayList<LocalBluetoothProfile>(); + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isConnectable()) { + connectableProfiles.add(profile); + } + } + return connectableProfiles; + } + + public List<LocalBluetoothProfile> getRemovedProfiles() { + return mRemovedProfiles; + } + + public void registerCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + public void unregisterCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private void dispatchAttributesChanged() { + synchronized (mCallbacks) { + for (Callback callback : mCallbacks) { + callback.onDeviceAttributesChanged(); + } + } + } + + @Override + public String toString() { + return mDevice.toString(); + } + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof CachedBluetoothDevice)) { + return false; + } + return mDevice.equals(((CachedBluetoothDevice) o).mDevice); + } + + @Override + public int hashCode() { + return mDevice.getAddress().hashCode(); + } + + // This comparison uses non-final fields so the sort order may change + // when device attributes change (such as bonding state). Settings + // will completely refresh the device list when this happens. + public int compareTo(CachedBluetoothDevice another) { + // Connected above not connected + int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); + if (comparison != 0) return comparison; + + // Paired above not paired + comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - + (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); + if (comparison != 0) return comparison; + + // Visible above not visible + comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0); + if (comparison != 0) return comparison; + + // Stronger signal above weaker signal + comparison = another.mRssi - mRssi; + if (comparison != 0) return comparison; + + // Fallback on name + return mName.compareTo(another.mName); + } + + public interface Callback { + void onDeviceAttributesChanged(); + } + + public int getPhonebookPermissionChoice() { + int permission = mDevice.getPhonebookAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setPhonebookPermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setPhonebookAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migratePhonebookPermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_phonebook_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + public int getMessagePermissionChoice() { + int permission = mDevice.getMessageAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setMessagePermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setMessageAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migrateMessagePermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_message_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + /** + * @return Whether this rejection should persist. + */ + public boolean checkAndIncreaseMessageRejectionCount() { + if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) { + mMessageRejectionCount++; + saveMessageRejectionCount(); + } + return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST; + } + + private void fetchMessageRejectionCount() { + SharedPreferences preference = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE); + mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0); + } + + private void saveMessageRejectionCount() { + SharedPreferences.Editor editor = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit(); + if (mMessageRejectionCount == 0) { + editor.remove(mDevice.getAddress()); + } else { + editor.putInt(mDevice.getAddress(), mMessageRejectionCount); + } + editor.commit(); + } + + private void processPhonebookAccess() { + if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return; + + ParcelUuid[] uuids = mDevice.getUuids(); + if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) { + // The pairing dialog now warns of phone-book access for paired devices. + // No separate prompt is displayed after pairing. + setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java new file mode 100755 index 0000000..65db95f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. + */ +public final class CachedBluetoothDeviceManager { + private static final String TAG = "CachedBluetoothDeviceManager"; + private static final boolean DEBUG = Utils.D; + + private Context mContext; + private final List<CachedBluetoothDevice> mCachedDevices = + new ArrayList<CachedBluetoothDevice>(); + + CachedBluetoothDeviceManager(Context context) { + mContext = context; + } + + public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { + return new ArrayList<CachedBluetoothDevice>(mCachedDevices); + } + + public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { + cachedDevice.setVisible(false); + return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE; + } + + public void onDeviceNameUpdated(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshName(); + } + } + + /** + * Search for existing {@link CachedBluetoothDevice} or return null + * if this device isn't in the cache. Use {@link #addDevice} + * to create and return a new {@link CachedBluetoothDevice} for + * a newly discovered {@link BluetoothDevice}. + * + * @param device the address of the Bluetooth device + * @return the cached device object for this device, or null if it has + * not been previously seen + */ + public CachedBluetoothDevice findDevice(BluetoothDevice device) { + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + if (cachedDevice.getDevice().equals(device)) { + return cachedDevice; + } + } + return null; + } + + /** + * Create and return a new {@link CachedBluetoothDevice}. This assumes + * that {@link #findDevice} has already been called and returned null. + * @param device the address of the new Bluetooth device + * @return the newly created CachedBluetoothDevice object + */ + public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter, + profileManager, device); + synchronized (mCachedDevices) { + mCachedDevices.add(newDevice); + } + return newDevice; + } + + /** + * Attempts to get the name of a remote device, otherwise returns the address. + * + * @param device The remote device. + * @return The name, or if unavailable, the address. + */ + public String getName(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + return cachedDevice.getName(); + } + + String name = device.getAliasName(); + if (name != null) { + return name; + } + + return device.getAddress(); + } + + public synchronized void clearNonBondedDevices() { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + mCachedDevices.remove(i); + } + } + } + + public synchronized void onScanningStateChanged(boolean started) { + if (!started) return; + + // If starting a new scan, clear old visibility + // Iterate in reverse order since devices may be removed. + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + cachedDevice.setVisible(false); + } + } + + public synchronized void onBtClassChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshBtClass(); + } + } + + public synchronized void onUuidChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.onUuidChanged(); + } + } + + public synchronized void onBluetoothStateChanged(int bluetoothState) { + // When Bluetooth is turning off, we need to clear the non-bonded devices + // Otherwise, they end up showing up on the next BT enable + if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + cachedDevice.setVisible(false); + mCachedDevices.remove(i); + } else { + // For bonded devices, we need to clear the connection status so that + // when BT is enabled next time, device connection status shall be retrieved + // by making a binder call. + cachedDevice.clearProfileConnectionState(); + } + } + } + } + private void log(String msg) { + if (DEBUG) { + Log.d(TAG, msg); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java new file mode 100755 index 0000000..5529866 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * HeadsetProfile handles Bluetooth HFP and Headset profiles. + */ +public final class HeadsetProfile implements LocalBluetoothProfile { + private static final String TAG = "HeadsetProfile"; + private static boolean V = true; + + private BluetoothHeadset mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + }; + + static final String NAME = "HEADSET"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class HeadsetServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothHeadset) proxy; + // We just bound to the service, so refresh the UI for any connected HFP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HeadsetProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HeadsetProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + HeadsetProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(), + BluetoothProfile.HEADSET); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + Log.d(TAG,"Not disconnecting device = " + sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()) { + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + if (V) Log.d(TAG,"Downgrade priority as user" + + "is disconnecting the headset"); + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + } + } + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()){ + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + return mService.getConnectionState(device); + } + } + } + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_headset; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_headset_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_headset_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headset_hfp; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java new file mode 100755 index 0000000..a9e8db5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.List; + +/** + * HidProfile handles Bluetooth HID profile. + */ +public final class HidProfile implements LocalBluetoothProfile { + private static final String TAG = "HidProfile"; + private static boolean V = true; + + private BluetoothInputDevice mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final String NAME = "HID"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 3; + + // These callbacks run on the main thread. + private final class InputDeviceServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothInputDevice) proxy; + // We just bound to the service, so refresh the UI for any connected HID devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HidProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + HidProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + adapter.getProfileProxy(context, new InputDeviceServiceListener(), + BluetoothProfile.INPUT_DEVICE); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + // TODO: distinguish between keyboard and mouse? + return R.string.bluetooth_profile_hid; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_hid_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_hid_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + if (btClass == null) { + return R.drawable.ic_lockscreen_ime; + } + return getHidClassDrawable(btClass); + } + + public static int getHidClassDrawable(BluetoothClass btClass) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.PERIPHERAL_KEYBOARD: + case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: + return R.drawable.ic_lockscreen_ime; + case BluetoothClass.Device.PERIPHERAL_POINTING: + return R.drawable.ic_bt_pointing_hid; + default: + return R.drawable.ic_bt_misc_hid; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java new file mode 100644 index 0000000..0c1adec --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.Set; + +/** + * LocalBluetoothAdapter provides an interface between the Settings app + * and the functionality of the local {@link BluetoothAdapter}, specifically + * those related to state transitions of the adapter itself. + * + * <p>Connection and bonding state changes affecting specific devices + * are handled by {@link CachedBluetoothDeviceManager}, + * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}. + */ +public final class LocalBluetoothAdapter { + private static final String TAG = "LocalBluetoothAdapter"; + + /** This class does not allow direct access to the BluetoothAdapter. */ + private final BluetoothAdapter mAdapter; + + private LocalBluetoothProfileManager mProfileManager; + + private static LocalBluetoothAdapter sInstance; + + private int mState = BluetoothAdapter.ERROR; + + private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins + + private long mLastScan; + + private LocalBluetoothAdapter(BluetoothAdapter adapter) { + mAdapter = adapter; + } + + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + /** + * Get the singleton instance of the LocalBluetoothAdapter. If this device + * doesn't support Bluetooth, then null will be returned. Callers must be + * prepared to handle a null return value. + * @return the LocalBluetoothAdapter object, or null if not supported + */ + static synchronized LocalBluetoothAdapter getInstance() { + if (sInstance == null) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + sInstance = new LocalBluetoothAdapter(adapter); + } + } + + return sInstance; + } + + // Pass-through BluetoothAdapter methods that we can intercept if necessary + + public void cancelDiscovery() { + mAdapter.cancelDiscovery(); + } + + public boolean enable() { + return mAdapter.enable(); + } + + public boolean disable() { + return mAdapter.disable(); + } + + void getProfileProxy(Context context, + BluetoothProfile.ServiceListener listener, int profile) { + mAdapter.getProfileProxy(context, listener, profile); + } + + public Set<BluetoothDevice> getBondedDevices() { + return mAdapter.getBondedDevices(); + } + + public String getName() { + return mAdapter.getName(); + } + + public int getScanMode() { + return mAdapter.getScanMode(); + } + + public int getState() { + return mAdapter.getState(); + } + + public ParcelUuid[] getUuids() { + return mAdapter.getUuids(); + } + + public boolean isDiscovering() { + return mAdapter.isDiscovering(); + } + + public boolean isEnabled() { + return mAdapter.isEnabled(); + } + + public void setDiscoverableTimeout(int timeout) { + mAdapter.setDiscoverableTimeout(timeout); + } + + public void setName(String name) { + mAdapter.setName(name); + } + + public void setScanMode(int mode) { + mAdapter.setScanMode(mode); + } + + public boolean setScanMode(int mode, int duration) { + return mAdapter.setScanMode(mode, duration); + } + + public void startScanning(boolean force) { + // Only start if we're not already scanning + if (!mAdapter.isDiscovering()) { + if (!force) { + // Don't scan more than frequently than SCAN_EXPIRATION_MS, + // unless forced + if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) { + return; + } + + // If we are playing music, don't scan unless forced. + A2dpProfile a2dp = mProfileManager.getA2dpProfile(); + if (a2dp != null && a2dp.isA2dpPlaying()) { + return; + } + } + + if (mAdapter.startDiscovery()) { + mLastScan = System.currentTimeMillis(); + } + } + } + + public void stopScanning() { + if (mAdapter.isDiscovering()) { + mAdapter.cancelDiscovery(); + } + } + + public synchronized int getBluetoothState() { + // Always sync state, in case it changed while paused + syncBluetoothState(); + return mState; + } + + synchronized void setBluetoothStateInt(int state) { + mState = state; + + if (state == BluetoothAdapter.STATE_ON) { + // if mProfileManager hasn't been constructed yet, it will + // get the adapter UUIDs in its constructor when it is. + if (mProfileManager != null) { + mProfileManager.setBluetoothStateOn(); + } + } + } + + // Returns true if the state changed; false otherwise. + boolean syncBluetoothState() { + int currentState = mAdapter.getState(); + if (currentState != mState) { + setBluetoothStateInt(mAdapter.getState()); + return true; + } + return false; + } + + public void setBluetoothEnabled(boolean enabled) { + boolean success = enabled + ? mAdapter.enable() + : mAdapter.disable(); + + if (success) { + setBluetoothStateInt(enabled + ? BluetoothAdapter.STATE_TURNING_ON + : BluetoothAdapter.STATE_TURNING_OFF); + } else { + if (Utils.V) { + Log.v(TAG, "setBluetoothEnabled call, manager didn't return " + + "success for enabled: " + enabled); + } + + syncBluetoothState(); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java new file mode 100644 index 0000000..4adc62e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.content.Context; +import android.util.Log; + +/** + * LocalBluetoothManager provides a simplified interface on top of a subset of + * the Bluetooth API. Note that {@link #getInstance} will return null + * if there is no Bluetooth adapter on this device, and callers must be + * prepared to handle this case. + */ +public final class LocalBluetoothManager { + private static final String TAG = "LocalBluetoothManager"; + + /** Singleton instance. */ + private static LocalBluetoothManager sInstance; + + private final Context mContext; + + /** If a BT-related activity is in the foreground, this will be it. */ + private Context mForegroundActivity; + + private final LocalBluetoothAdapter mLocalAdapter; + + private final CachedBluetoothDeviceManager mCachedDeviceManager; + + /** The Bluetooth profile manager. */ + private final LocalBluetoothProfileManager mProfileManager; + + /** The broadcast receiver event manager. */ + private final BluetoothEventManager mEventManager; + + public static synchronized LocalBluetoothManager getInstance(Context context, + BluetoothManagerCallback onInitCallback) { + if (sInstance == null) { + LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance(); + if (adapter == null) { + return null; + } + // This will be around as long as this process is + Context appContext = context.getApplicationContext(); + sInstance = new LocalBluetoothManager(adapter, appContext); + if (onInitCallback != null) { + onInitCallback.onBluetoothManagerInitialized(appContext, sInstance); + } + } + + return sInstance; + } + + private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) { + mContext = context; + mLocalAdapter = adapter; + + mCachedDeviceManager = new CachedBluetoothDeviceManager(context); + mEventManager = new BluetoothEventManager(mLocalAdapter, + mCachedDeviceManager, context); + mProfileManager = new LocalBluetoothProfileManager(context, + mLocalAdapter, mCachedDeviceManager, mEventManager); + } + + public LocalBluetoothAdapter getBluetoothAdapter() { + return mLocalAdapter; + } + + public Context getContext() { + return mContext; + } + + public Context getForegroundActivity() { + return mForegroundActivity; + } + + public boolean isForegroundActivity() { + return mForegroundActivity != null; + } + + public synchronized void setForegroundActivity(Context context) { + if (context != null) { + Log.d(TAG, "setting foreground activity to non-null context"); + mForegroundActivity = context; + } else { + if (mForegroundActivity != null) { + Log.d(TAG, "setting foreground activity to null"); + mForegroundActivity = null; + } + } + } + + public CachedBluetoothDeviceManager getCachedDeviceManager() { + return mCachedDeviceManager; + } + + public BluetoothEventManager getEventManager() { + return mEventManager; + } + + public LocalBluetoothProfileManager getProfileManager() { + return mProfileManager; + } + + public interface BluetoothManagerCallback { + void onBluetoothManagerInitialized(Context appContext, + LocalBluetoothManager bluetoothManager); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java new file mode 100755 index 0000000..abcb989 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +/** + * LocalBluetoothProfile is an interface defining the basic + * functionality related to a Bluetooth profile. + */ +public interface LocalBluetoothProfile { + + /** + * Returns true if the user can initiate a connection, false otherwise. + */ + boolean isConnectable(); + + /** + * Returns true if the user can enable auto connection for this profile. + */ + boolean isAutoConnectable(); + + boolean connect(BluetoothDevice device); + + boolean disconnect(BluetoothDevice device); + + int getConnectionStatus(BluetoothDevice device); + + boolean isPreferred(BluetoothDevice device); + + int getPreferred(BluetoothDevice device); + + void setPreferred(BluetoothDevice device, boolean preferred); + + boolean isProfileReady(); + + /** Display order for device profile settings. */ + int getOrdinal(); + + /** + * Returns the string resource ID for the localized name for this profile. + * @param device the Bluetooth device (to distinguish between PAN roles) + */ + int getNameResource(BluetoothDevice device); + + /** + * Returns the string resource ID for the summary text for this profile + * for the specified device, e.g. "Use for media audio" or + * "Connected to media audio". + * @param device the device to query for profile connection status + * @return a string resource ID for the profile summary text + */ + int getSummaryResourceForDevice(BluetoothDevice device); + + int getDrawableResource(BluetoothClass btClass); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java new file mode 100644 index 0000000..b0a7b27 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.Intent; +import android.os.ParcelUuid; +import android.util.Log; +import android.os.Handler; +import android.os.Message; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.List; + +/** + * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile + * objects for the available Bluetooth profiles. + */ +public final class LocalBluetoothProfileManager { + private static final String TAG = "LocalBluetoothProfileManager"; + private static final boolean DEBUG = Utils.D; + /** Singleton instance. */ + private static LocalBluetoothProfileManager sInstance; + + /** + * An interface for notifying BluetoothHeadset IPC clients when they have + * been connected to the BluetoothHeadset service. + * Only used by com.android.settings.bluetooth.DockService. + */ + public interface ServiceListener { + /** + * Called to notify the client when this proxy object has been + * connected to the BluetoothHeadset service. Clients must wait for + * this callback before making IPC calls on the BluetoothHeadset + * service. + */ + void onServiceConnected(); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the BluetoothHeadset service. Clients must not + * make IPC calls on the BluetoothHeadset service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothHeadset service, but may be called more often in future. + */ + void onServiceDisconnected(); + } + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothEventManager mEventManager; + + private A2dpProfile mA2dpProfile; + private HeadsetProfile mHeadsetProfile; + private MapProfile mMapProfile; + private final HidProfile mHidProfile; + private OppProfile mOppProfile; + private final PanProfile mPanProfile; + private final PbapServerProfile mPbapProfile; + + /** + * Mapping from profile name, e.g. "HEADSET" to profile object. + */ + private final Map<String, LocalBluetoothProfile> + mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); + + LocalBluetoothProfileManager(Context context, + LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + BluetoothEventManager eventManager) { + mContext = context; + + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mEventManager = eventManager; + // pass this reference to adapter and event manager (circular dependency) + mLocalAdapter.setProfileManager(this); + mEventManager.setProfileManager(this); + + ParcelUuid[] uuids = adapter.getUuids(); + + // uuids may be null if Bluetooth is turned off + if (uuids != null) { + updateLocalProfiles(uuids); + } + + // Always add HID and PAN profiles + mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this); + addProfile(mHidProfile, HidProfile.NAME, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); + + mPanProfile = new PanProfile(context); + addPanProfile(mPanProfile, PanProfile.NAME, + BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + + if(DEBUG) Log.d(TAG, "Adding local MAP profile"); + mMapProfile = new MapProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mMapProfile, MapProfile.NAME, + BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); + + //Create PBAP server profile, but do not add it to list of profiles + // as we do not need to monitor the profile as part of profile list + mPbapProfile = new PbapServerProfile(context); + + if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); + } + + /** + * Initialize or update the local profile objects. If a UUID was previously + * present but has been removed, we print a warning but don't remove the + * profile object as it might be referenced elsewhere, or the UUID might + * come back and we don't want multiple copies of the profile objects. + * @param uuids + */ + void updateLocalProfiles(ParcelUuid[] uuids) { + // A2DP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { + if (mA2dpProfile == null) { + if(DEBUG) Log.d(TAG, "Adding local A2DP profile"); + mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this); + addProfile(mA2dpProfile, A2dpProfile.NAME, + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + } + } else if (mA2dpProfile != null) { + Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); + } + + // Headset / Handsfree + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { + if (mHeadsetProfile == null) { + if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); + mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mHeadsetProfile, HeadsetProfile.NAME, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + } + } else if (mHeadsetProfile != null) { + Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); + } + + // OPP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + if (mOppProfile == null) { + if(DEBUG) Log.d(TAG, "Adding local OPP profile"); + mOppProfile = new OppProfile(); + // Note: no event handler for OPP, only name map. + mProfileNameMap.put(OppProfile.NAME, mOppProfile); + } + } else if (mOppProfile != null) { + Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); + } + mEventManager.registerProfileIntentReceiver(); + + // There is no local SDP record for HID and Settings app doesn't control PBAP + } + + private final Collection<ServiceListener> mServiceListeners = + new ArrayList<ServiceListener>(); + + private void addProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + private void addPanProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, + new PanStateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + public LocalBluetoothProfile getProfileByName(String name) { + return mProfileNameMap.get(name); + } + + // Called from LocalBluetoothAdapter when state changes to ON + void setBluetoothStateOn() { + ParcelUuid[] uuids = mLocalAdapter.getUuids(); + if (uuids != null) { + updateLocalProfiles(uuids); + } + mEventManager.readPairedDevices(); + } + + /** + * Generic handler for connection state change events for the specified profile. + */ + private class StateChangedHandler implements BluetoothEventManager.Handler { + final LocalBluetoothProfile mProfile; + + StateChangedHandler(LocalBluetoothProfile profile) { + mProfile = profile; + } + + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "StateChangedHandler found new device: " + device); + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, + LocalBluetoothProfileManager.this, device); + } + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + if (newState == BluetoothProfile.STATE_DISCONNECTED && + oldState == BluetoothProfile.STATE_CONNECTING) { + Log.i(TAG, "Failed to connect " + mProfile + " device"); + } + + cachedDevice.onProfileStateChanged(mProfile, newState); + cachedDevice.refresh(); + } + } + + /** State change handler for NAP and PANU profiles. */ + private class PanStateChangedHandler extends StateChangedHandler { + + PanStateChangedHandler(LocalBluetoothProfile profile) { + super(profile); + } + + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + PanProfile panProfile = (PanProfile) mProfile; + int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); + panProfile.setLocalRole(device, role); + super.onReceive(context, intent, device); + } + } + + // called from DockService + public void addServiceListener(ServiceListener l) { + mServiceListeners.add(l); + } + + // called from DockService + public void removeServiceListener(ServiceListener l) { + mServiceListeners.remove(l); + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceConnectedListeners() { + for (ServiceListener l : mServiceListeners) { + l.onServiceConnected(); + } + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceDisconnectedListeners() { + for (ServiceListener listener : mServiceListeners) { + listener.onServiceDisconnected(); + } + } + + // This is called by DockService, so check Headset and A2DP. + public synchronized boolean isManagerReady() { + // Getting just the headset profile is fine for now. Will need to deal with A2DP + // and others if they aren't always in a ready state. + LocalBluetoothProfile profile = mHeadsetProfile; + if (profile != null) { + return profile.isProfileReady(); + } + profile = mA2dpProfile; + if (profile != null) { + return profile.isProfileReady(); + } + return false; + } + + public A2dpProfile getA2dpProfile() { + return mA2dpProfile; + } + + public HeadsetProfile getHeadsetProfile() { + return mHeadsetProfile; + } + + public PbapServerProfile getPbapProfile(){ + return mPbapProfile; + } + + public MapProfile getMapProfile(){ + return mMapProfile; + } + + /** + * Fill in a list of LocalBluetoothProfile objects that are supported by + * the local device and the remote device. + * + * @param uuids of the remote device + * @param localUuids UUIDs of the local device + * @param profiles The list of profiles to fill + * @param removedProfiles list of profiles that were removed + */ + synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, + Collection<LocalBluetoothProfile> profiles, + Collection<LocalBluetoothProfile> removedProfiles, + boolean isPanNapConnected, BluetoothDevice device) { + // Copy previous profile list into removedProfiles + removedProfiles.clear(); + removedProfiles.addAll(profiles); + profiles.clear(); + + if (uuids == null) { + return; + } + + if (mHeadsetProfile != null) { + if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || + (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { + profiles.add(mHeadsetProfile); + removedProfiles.remove(mHeadsetProfile); + } + } + + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && + mA2dpProfile != null) { + profiles.add(mA2dpProfile); + removedProfiles.remove(mA2dpProfile); + } + + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && + mOppProfile != null) { + profiles.add(mOppProfile); + removedProfiles.remove(mOppProfile); + } + + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && + mHidProfile != null) { + profiles.add(mHidProfile); + removedProfiles.remove(mHidProfile); + } + + if(isPanNapConnected) + if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && + mPanProfile != null) || isPanNapConnected) { + profiles.add(mPanProfile); + removedProfiles.remove(mPanProfile); + } + + if ((mMapProfile != null) && + (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { + profiles.add(mMapProfile); + removedProfiles.remove(mMapProfile); + mMapProfile.setPreferred(device, true); + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java new file mode 100644 index 0000000..e6a152f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * MapProfile handles Bluetooth MAP profile. + */ +public final class MapProfile implements LocalBluetoothProfile { + private static final String TAG = "MapProfile"; + private static boolean V = true; + + private BluetoothMap mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.MAP, + BluetoothUuid.MNS, + BluetoothUuid.MAS, + }; + + static final String NAME = "MAP"; + + // Order of this profile in device profiles list + + // These callbacks run on the main thread. + private final class MapServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothMap) proxy; + // We just bound to the service, so refresh the UI for any connected MAP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "MapProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(MapProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady); + return mIsProfileReady; + } + + MapProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new MapServiceListener(), + BluetoothProfile.MAP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if(V)Log.d(TAG,"connect() - should not get called"); + return false; // MAP never connects out + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } else { + return false; + } + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device)); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return BluetoothProfile.MAP; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_map; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_map_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_map_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up MAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java new file mode 100755 index 0000000..31e675c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import com.android.settingslib.R; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +/** + * OppProfile handles Bluetooth OPP. + */ +final class OppProfile implements LocalBluetoothProfile { + + static final String NAME = "OPP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 2; + + public boolean isConnectable() { + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_opp; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; // OPP profile not displayed in UI + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; // no icon for OPP + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java new file mode 100755 index 0000000..3af89e6 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PanProfile handles Bluetooth PAN profile (NAP and PANU). + */ +final class PanProfile implements LocalBluetoothProfile { + private static final String TAG = "PanProfile"; + private static boolean V = true; + + private BluetoothPan mService; + private boolean mIsProfileReady; + + // Tethering direction for each device + private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap = + new HashMap<BluetoothDevice, Integer>(); + + static final String NAME = "PAN"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 4; + + // These callbacks run on the main thread. + private final class PanServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPan) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + PanProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new PanServiceListener(), + BluetoothProfile.PAN); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + // return current connection status so profile checkbox is set correctly + return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PAN + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + if (isLocalRoleNap(device)) { + return R.string.bluetooth_profile_pan_nap; + } else { + return R.string.bluetooth_profile_pan; + } + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_pan_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + if (isLocalRoleNap(device)) { + return R.string.bluetooth_pan_nap_profile_summary_connected; + } else { + return R.string.bluetooth_pan_user_profile_summary_connected; + } + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_network_pan; + } + + // Tethering direction determines UI strings. + void setLocalRole(BluetoothDevice device, int role) { + mDeviceRoleMap.put(device, role); + } + + boolean isLocalRoleNap(BluetoothDevice device) { + if (mDeviceRoleMap.containsKey(device)) { + return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE; + } else { + return false; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PAN proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java new file mode 100755 index 0000000..a552b24a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2011 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PBAPServer Profile + */ +public final class PbapServerProfile implements LocalBluetoothProfile { + private static final String TAG = "PbapServerProfile"; + private static boolean V = true; + + private BluetoothPbap mService; + private boolean mIsProfileReady; + + static final String NAME = "PBAP Server"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 6; + + // The UUIDs indicate that remote device might access pbap server + static final ParcelUuid[] PBAB_CLIENT_UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + BluetoothUuid.PBAP_PCE + }; + + // These callbacks run on the main thread. + private final class PbapServiceListener + implements BluetoothPbap.ServiceListener { + + public void onServiceConnected(BluetoothPbap proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPbap) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected() { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + PbapServerProfile(Context context) { + BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener()); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + /*Can't connect from server */ + return false; + + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + if (mService.isConnected(device)) + return BluetoothProfile.STATE_CONNECTED; + else + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PBAP + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap_summary; + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + mService.close(); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PBAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java new file mode 100644 index 0000000..c919426 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java @@ -0,0 +1,43 @@ +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settingslib.R; + +public class Utils { + public static final boolean V = false; // verbose logging + public static final boolean D = true; // regular logging + + private static ErrorListener sErrorListener; + + public static int getConnectionStateSummary(int connectionState) { + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_connected; + case BluetoothProfile.STATE_CONNECTING: + return R.string.bluetooth_connecting; + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_disconnected; + case BluetoothProfile.STATE_DISCONNECTING: + return R.string.bluetooth_disconnecting; + default: + return 0; + } + } + + static void showError(Context context, String name, int messageResId) { + if (sErrorListener != null) { + sErrorListener.onShowError(context, name, messageResId); + } + } + + public static void setErrorListener(ErrorListener listener) { + sErrorListener = listener; + } + + public interface ErrorListener { + void onShowError(Context context, String name, int messageResId); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 25bab17..c23f45d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -114,7 +114,9 @@ public class MediaProjectionPermissionActivity extends Activity @Override protected void onDestroy() { super.onDestroy(); - mDialog.dismiss(); + if (mDialog != null) { + mDialog.dismiss(); + } } @Override @@ -128,7 +130,9 @@ public class MediaProjectionPermissionActivity extends Activity Log.e(TAG, "Error granting projection permission", e); setResult(RESULT_CANCELED); } finally { - mDialog.dismiss(); + if (mDialog != null) { + mDialog.dismiss(); + } finish(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 0863c86..7ca91a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -22,7 +22,6 @@ public interface HotspotController { boolean isHotspotEnabled(); boolean isHotspotSupported(); void setHotspotEnabled(boolean enabled); - boolean isProvisioningNeeded(); public interface Callback { void onHotspotChanged(boolean enabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index 5eff5a6..4bfd528 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -16,45 +16,38 @@ package com.android.systemui.statusbar.policy; -import android.app.ActivityManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.os.SystemProperties; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; +import com.android.settingslib.TetherUtil; + import java.util.ArrayList; public class HotspotControllerImpl implements HotspotController { private static final String TAG = "HotspotController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // Keep these in sync with Settings TetherService.java - public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; - public static final String EXTRA_SET_ALARM = "extraSetAlarm"; - public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; - public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether"; - // Keep this in sync with Settings TetherSettings.java - public static final int WIFI_TETHERING = 0; + private static final Intent TETHER_SERVICE_INTENT = new Intent() + .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI) + .putExtra(TetherUtil.EXTRA_SET_ALARM, true) + .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true) + .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true) + .setComponent(TetherUtil.TETHER_SERVICE); private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final Receiver mReceiver = new Receiver(); private final Context mContext; private final WifiManager mWifiManager; - private final ConnectivityManager mConnectivityManager; public HotspotControllerImpl(Context context) { mContext = context; mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - mConnectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); } public void addCallback(Callback callback) { @@ -78,54 +71,17 @@ public class HotspotControllerImpl implements HotspotController { @Override public boolean isHotspotSupported() { - final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER; - return !isSecondaryUser && mConnectivityManager.isTetheringSupported(); - } - - @Override - public boolean isProvisioningNeeded() { - // Keep in sync with other usage of config_mobile_hotspot_provision_app. - // TetherSettings#isProvisioningNeeded and - // ConnectivityManager#enforceTetherChangePermission - String[] provisionApp = mContext.getResources().getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app); - if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) - || provisionApp == null) { - return false; - } - return (provisionApp.length == 2); + return TetherUtil.isTetheringSupported(mContext); } @Override public void setHotspotEnabled(boolean enabled) { final ContentResolver cr = mContext.getContentResolver(); // Call provisioning app which is called when enabling Tethering from Settings - if (enabled) { - if (isProvisioningNeeded()) { - String tetherEnable = mContext.getResources().getString( - com.android.internal.R.string.config_wifi_tether_enable); - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true); - intent.setComponent(ComponentName.unflattenFromString(tetherEnable)); - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } else { - int wifiState = mWifiManager.getWifiState(); - if ((wifiState == WifiManager.WIFI_STATE_ENABLING) || - (wifiState == WifiManager.WIFI_STATE_ENABLED)) { - mWifiManager.setWifiEnabled(false); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1); - } - mWifiManager.setWifiApEnabled(null, true); - } + if (enabled && TetherUtil.isProvisioningNeeded(mContext)) { + mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT); } else { - mWifiManager.setWifiApEnabled(null, false); - if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) { - mWifiManager.setWifiEnabled(true); - Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); - } + TetherUtil.setWifiTethering(enabled, mContext); } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 4d7ebed..acf4d39 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -2778,7 +2778,22 @@ public class BackupManagerService { @Override public void operationComplete() { - // Okay, the agent successfully reported back to us! + // The agent reported back to us! + + if (mBackupData == null) { + // This callback was racing with our timeout, so we've cleaned up the + // agent state already and are on to the next thing. We have nothing + // further to do here: agent state having been cleared means that we've + // initiated the appropriate next operation. + final String pkg = (mCurrentPackage != null) + ? mCurrentPackage.packageName : "[none]"; + if (DEBUG) { + Slog.i(TAG, "Callback after agent teardown: " + pkg); + } + addBackupTrace("late opComplete; curPkg = " + pkg); + return; + } + final String pkgName = mCurrentPackage.packageName; final long filepos = mBackupDataName.length(); FileDescriptor fd = mBackupData.getFileDescriptor(); diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 8bd7132..2e84fbe 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -318,6 +318,8 @@ public class Trampoline extends IBackupManager.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + BackupManagerService svc = mService; if (svc != null) { svc.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java index 38f1cb8..04911fa 100644 --- a/services/core/java/com/android/server/MidiService.java +++ b/services/core/java/com/android/server/MidiService.java @@ -17,10 +17,10 @@ package com.android.server; import android.content.Context; -import android.midi.IMidiDeviceServer; -import android.midi.IMidiListener; -import android.midi.IMidiManager; -import android.midi.MidiDeviceInfo; +import android.media.midi.IMidiDeviceServer; +import android.media.midi.IMidiListener; +import android.media.midi.IMidiManager; +import android.media.midi.MidiDeviceInfo; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6229778..008d718 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2398,8 +2398,7 @@ public final class ActivityManagerService extends ActivityManagerNative } else { finishRunningVoiceLocked(); } - mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity"); - if (r != null) { + if (r != null && mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) { mWindowManager.setFocusedApp(r.appToken, true); } applyUpdateLockStateLocked(r); @@ -2423,6 +2422,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityRecord r = stack.topRunningActivityLocked(null); if (r != null) { setFocusedActivityLocked(r, "setFocusedStack"); + mStackSupervisor.resumeTopActivitiesLocked(stack, null, null); } } } @@ -7909,6 +7909,25 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void resizeTask(int taskId, Rect bounds) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "resizeTask()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, true); + if (task == null) { + Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); + return; + } + mStackSupervisor.resizeTaskLocked(task, bounds); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public Bitmap getTaskDescriptionIcon(String filename) { if (!FileUtils.isValidExtFilename(filename) || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 7afe23a..c073df6 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -152,25 +152,25 @@ final class ActivityStack { * The back history of all previous (and possibly still * running) activities. It contains #TaskRecord objects. */ - private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>(); + private ArrayList<TaskRecord> mTaskHistory = new ArrayList<>(); /** * Used for validating app tokens with window manager. */ - final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>(); + final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>(); /** * List of running activities, sorted by recent usage. * The first entry in the list is the least recently used. * It contains HistoryRecord objects. */ - final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); /** * Animations that for the current transition have requested not to * be considered for the transition animation. */ - final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>(); /** * When we are in the process of pausing an activity, before starting the @@ -346,6 +346,10 @@ final class ActivityStack { return count; } + int numTasks() { + return mTaskHistory.size(); + } + ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer, RecentTasks recentTasks) { mActivityContainer = activityContainer; @@ -492,11 +496,19 @@ final class ActivityStack { final void moveToFront(String reason) { if (isAttached()) { + final boolean homeStack = isHomeStack() + || (mActivityContainer.mParentActivity != null + && mActivityContainer.mParentActivity.isHomeActivity()); + + if (!homeStack) { + // Need to move this stack to the front before calling + // {@link ActivityStackSupervisor#moveHomeStack} below. + mStacks.remove(this); + mStacks.add(this); + } if (isOnHomeDisplay()) { - mStackSupervisor.moveHomeStack(isHomeStack(), reason); + mStackSupervisor.moveHomeStack(homeStack, reason); } - mStacks.remove(this); - mStacks.add(this); final TaskRecord task = topTask(); if (task != null) { mWindowManager.moveTaskToTop(task.taskId); @@ -1154,6 +1166,23 @@ final class ActivityStack { return null; } + private ActivityStack getNextVisibleStackLocked() { + ArrayList<ActivityStack> stacks = mStacks; + final ActivityRecord parent = mActivityContainer.mParentActivity; + if (parent != null) { + stacks = parent.task.stack.mStacks; + } + if (stacks != null) { + for (int i = stacks.size() - 1; i >= 0; --i) { + ActivityStack stack = stacks.get(i); + if (stack != this && stack.isStackVisibleLocked()) { + return stack; + } + } + } + return null; + } + // Checks if any of the stacks above this one has a fullscreen activity behind it. // If so, this stack is hidden, otherwise it is visible. private boolean isStackVisibleLocked() { @@ -1482,7 +1511,7 @@ final class ActivityStack { return result; } - final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { + private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); if (!mService.mBooting && !mService.mBooted) { @@ -1510,8 +1539,17 @@ final class ActivityStack { final TaskRecord prevTask = prev != null ? prev.task : null; if (next == null) { - // There are no more activities! Let's just start up the - // Launcher... + // There are no more activities! + final String reason = "noMoreActivities"; + if (!mFullscreen) { + // Try to move focus to the next visible stack with a running activity if this + // stack is not covering the entire screen. + final ActivityStack stack = getNextVisibleStackLocked(); + if (adjustFocusToNextVisibleStackLocked(stack, reason)) { + return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null); + } + } + // Let's just start up the Launcher... ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); @@ -1519,7 +1557,7 @@ final class ActivityStack { final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities"); + mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason); } next.delayedResume = false; @@ -2516,20 +2554,44 @@ final class ActivityStack { private void adjustFocusedActivityLocked(ActivityRecord r, String reason) { if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) { ActivityRecord next = topRunningActivityLocked(null); + final String myReason = reason + " adjustFocus"; if (next != r) { final TaskRecord task = r.task; if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), - reason + " adjustFocus"); + // For non-fullscreen stack, we want to move the focus to the next visible + // stack to prevent the home screen from moving to the top and obscuring + // other visible stacks. + if (!mFullscreen + && adjustFocusToNextVisibleStackLocked(null, myReason)) { + return; + } + // Move the home stack to the top if this stack is fullscreen or there is no + // other visible stack. + mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), myReason); } } - ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); + + final ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); if (top != null) { - mService.setFocusedActivityLocked(top, reason + " adjustTopFocus"); + mService.setFocusedActivityLocked(top, myReason); } } } + private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) { + final ActivityStack stack = (inStack != null) ? inStack : getNextVisibleStackLocked(); + final String myReason = reason + " adjustFocusToNextVisibleStack"; + if (stack == null) { + return false; + } + final ActivityRecord top = stack.topRunningActivityLocked(null); + if (top == null) { + return false; + } + mService.setFocusedActivityLocked(top, myReason); + return true; + } + final void stopActivityLocked(ActivityRecord r) { if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 @@ -2999,6 +3061,8 @@ final class ActivityStack { * representation) and cleaning things up as a result of its hosting * processing going away, in which case there is no remaining client-side * state to destroy so only the cleanup here is needed. + * + * Note: Call before #removeActivityFromHistoryLocked. */ final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) { @@ -3410,7 +3474,7 @@ final class ActivityStack { if (DEBUG_CLEANUP) Slog.v( TAG, "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { - boolean remove; + final boolean remove; if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { // Don't currently have state for the activity, or // it is finishing -- always remove it. @@ -3444,8 +3508,6 @@ final class ActivityStack { mService.updateUsageStats(r, false); } } - removeActivityFromHistoryLocked(r, "appDied"); - } else { // We have the current state for this activity, so // it can be restarted later when needed. @@ -3464,8 +3526,10 @@ final class ActivityStack { r.icicle = null; } } - cleanUpActivityLocked(r, true, true); + if (remove) { + removeActivityFromHistoryLocked(r, "appDied"); + } } } } @@ -3599,8 +3663,7 @@ final class ActivityStack { } } - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to back transition: task=" + taskId); + if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to back transition: task=" + taskId); boolean prevIsHome = false; if (tr.isOverHomeStack()) { @@ -4126,8 +4189,14 @@ final class ActivityStack { } void removeTask(TaskRecord task, String reason) { + removeTask(task, reason, true); + } + + void removeTask(TaskRecord task, String reason, boolean removeFromWindowManager) { mStackSupervisor.endLockTaskModeIfTaskEnding(task); - mWindowManager.removeTask(task.taskId); + if (removeFromWindowManager) { + mWindowManager.removeTask(task.taskId); + } final ActivityRecord r = mResumedActivity; if (r != null && r.task == task) { mResumedActivity = null; @@ -4161,15 +4230,21 @@ final class ActivityStack { } if (mTaskHistory.isEmpty()) { - if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this); + if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack=" + this); + final boolean notHomeStack = !isHomeStack(); if (isOnHomeDisplay()) { - mStackSupervisor.moveHomeStack(!isHomeStack(), reason + " leftTaskHistoryEmpty"); + String myReason = reason + " leftTaskHistoryEmpty"; + if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) { + mStackSupervisor.moveHomeStack(notHomeStack, myReason); + } } if (mStacks != null) { mStacks.remove(this); mStacks.add(0, this); } - mActivityContainer.onTaskListEmptyLocked(); + if (notHomeStack) { + mActivityContainer.onTaskListEmptyLocked(); + } } task.stack = null; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 9fe3c48..f6ef295 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -386,8 +386,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return mLastFocusedStack; } - // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the - // top of all visible stacks. + /** Top of all visible stacks. Use {@link ActivityStack#isStackVisibleLocked} to determine if a + * specific stack is visible or not. */ boolean isFrontStack(ActivityStack stack) { final ActivityRecord parent = stack.mActivityContainer.mParentActivity; if (parent != null) { @@ -535,7 +535,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityRecord resumedAppLocked() { - ActivityStack stack = getFocusedStack(); + ActivityStack stack = mFocusedStack; if (stack == null) { return null; } @@ -739,7 +739,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityRecord topRunningActivityLocked() { - final ActivityStack focusedStack = getFocusedStack(); + final ActivityStack focusedStack = mFocusedStack; ActivityRecord r = focusedStack.topRunningActivityLocked(null); if (r != null) { return r; @@ -885,7 +885,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack stack; if (container == null || container.mStack.isOnHomeDisplay()) { - stack = getFocusedStack(); + stack = mFocusedStack; } else { stack = container.mStack; } @@ -1502,7 +1502,7 @@ public final class ActivityStackSupervisor implements DisplayListener { outActivity[0] = r; } - final ActivityStack stack = getFocusedStack(); + final ActivityStack stack = mFocusedStack; if (voiceSession == null && (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, @@ -1541,25 +1541,27 @@ public final class ActivityStackSupervisor implements DisplayListener { return err; } - ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) { + ActivityStack computeStackFocus(ActivityRecord r, boolean newTask) { final TaskRecord task = r.task; // On leanback only devices we should keep all activities in the same stack. if (!mLeanbackOnlyDevice && (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { + + ActivityStack stack; + if (task != null) { - final ActivityStack taskStack = task.stack; - if (taskStack.isOnHomeDisplay()) { - if (mFocusedStack != taskStack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting " + + stack = task.stack; + if (stack.isOnHomeDisplay()) { + if (mFocusedStack != stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: Setting " + "focused stack to r=" + r + " task=" + task); - mFocusedStack = taskStack; } else { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Focused stack already=" + mFocusedStack); + "computeStackFocus: Focused stack already=" + mFocusedStack); } } - return taskStack; + return stack; } final ActivityContainer container = r.mInitialActivityContainer; @@ -1572,43 +1574,41 @@ public final class ActivityStackSupervisor implements DisplayListener { if (mFocusedStack != mHomeStack && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Have a focused stack=" + mFocusedStack); + "computeStackFocus: Have a focused stack=" + mFocusedStack); return mFocusedStack; } final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = homeDisplayStacks.get(stackNdx); + stack = homeDisplayStacks.get(stackNdx); if (!stack.isHomeStack()) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Setting focused stack=" + stack); - mFocusedStack = stack; - return mFocusedStack; + "computeStackFocus: Setting focused stack=" + stack); + return stack; } } // Need to create an app stack for this user. - mFocusedStack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r + - " stackId=" + mFocusedStack.mStackId); - return mFocusedStack; + stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: New stack r=" + r + + " stackId=" + stack.mStackId); + return stack; } return mHomeStack; } - void setFocusedStack(ActivityRecord r, String reason) { - if (r != null) { - final TaskRecord task = r.task; - boolean isHomeActivity = !r.isApplicationActivity(); - if (!isHomeActivity && task != null) { - isHomeActivity = !task.isApplicationTask(); - } - if (!isHomeActivity && task != null) { - final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity; - isHomeActivity = parent != null && parent.isHomeActivity(); - } - moveHomeStack(isHomeActivity, reason); + boolean setFocusedStack(ActivityRecord r, String reason) { + if (r == null) { + // Not sure what you are trying to do, but it is not going to work... + return false; + } + final TaskRecord task = r.task; + if (task == null || task.stack == null) { + Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task); + return false; } + task.stack.moveToFront(reason); + return true; } final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, @@ -1706,7 +1706,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { ActivityRecord checkedCaller = sourceRecord; if (checkedCaller == null) { - checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop); + checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop); } if (!checkedCaller.realActivity.equals(r.realActivity)) { // Caller is not the same as launcher, so always needed. @@ -2030,7 +2030,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // If the activity being launched is the same as the one currently // at the top, then we need to check if it should only be launched // once. - ActivityStack topStack = getFocusedStack(); + ActivityStack topStack = mFocusedStack; ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { @@ -2082,10 +2082,9 @@ public final class ActivityStackSupervisor implements DisplayListener { return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } newTask = true; - targetStack = adjustStackFocus(r, newTask); - if (!launchTaskBehind) { - targetStack.moveToFront("startingNewTask"); - } + targetStack = computeStackFocus(r, newTask); + targetStack.moveToFront("startingNewTask"); + if (reuseTask == null) { r.setTask(targetStack.createTaskRecord(getNextTaskId(), newTaskInfo != null ? newTaskInfo : r.info, @@ -2206,7 +2205,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // This not being started from an existing activity, and not part // of a new task... just put it in the top task, though these days // this case should never happen. - targetStack = adjustStackFocus(r, newTask); + targetStack = computeStackFocus(r, newTask); targetStack.moveToFront("addingToTopTask"); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(), @@ -2482,13 +2481,14 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target, Bundle targetOptions) { if (targetStack == null) { - targetStack = getFocusedStack(); + targetStack = mFocusedStack; } // Do targetStack first. boolean result = false; if (isFrontStack(targetStack)) { result = targetStack.resumeTopActivityLocked(target, targetOptions); } + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { @@ -2640,6 +2640,48 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + /** Makes sure the input task is in a stack with the specified bounds by either resizing the + * current task stack if it only has one entry, moving the task to a stack that matches the + * bounds, or creating a new stack with the required bounds. Also, makes the task resizeable.*/ + void resizeTaskLocked(TaskRecord task, Rect bounds) { + task.mResizeable = true; + final ActivityStack currentStack = task.stack; + if (currentStack.isHomeStack()) { + // Can't move task off the home stack. Sorry! + return; + } + + final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds); + if (matchingStackId != -1) { + // There is already a stack with the right bounds! + if (currentStack != null && currentStack.mStackId == matchingStackId) { + // Nothing to do here. Already in the right stack... + return; + } + // Move task to stack with matching bounds. + moveTaskToStackLocked(task.taskId, matchingStackId, true); + return; + } + + if (currentStack != null && currentStack.numTasks() == 1) { + // Just resize the current stack since this is the task in it. + resizeStackLocked(currentStack.mStackId, bounds); + return; + } + + // Create new stack and move the task to it. + final int displayId = (currentStack != null && currentStack.mDisplayId != -1) + ? currentStack.mDisplayId : Display.DEFAULT_DISPLAY; + ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId); + + if (newStack == null) { + Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task); + return; + } + moveTaskToStackLocked(task.taskId, newStack.mStackId, true); + resizeStackLocked(newStack.mStackId, bounds); + } + ActivityStack createStackOnDisplay(int stackId, int displayId) { ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); if (activityDisplay == null) { @@ -2718,6 +2760,7 @@ public final class ActivityStackSupervisor implements DisplayListener { void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) { final TaskRecord task = anyTaskForIdLocked(taskId); if (task == null) { + Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId); return; } final ActivityStack stack = getStack(stackId); @@ -2725,9 +2768,11 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); return; } - task.stack.removeTask(task, "moveTaskToStack"); + mWindowManager.moveTaskToStack(taskId, stackId, toTop); + if (task.stack != null) { + task.stack.removeTask(task, "moveTaskToStack", false); + } stack.addTask(task, toTop, true); - mWindowManager.addTask(taskId, stackId, toTop); resumeTopActivitiesLocked(); } @@ -3068,7 +3113,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } boolean switchUserLocked(int userId, UserStartedState uss) { - mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId()); + mUserStackInFront.put(mCurrentUser, mFocusedStack.getStackId()); final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID); mCurrentUser = userId; @@ -3192,7 +3237,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { - return getFocusedStack().getDumpActivitiesLocked(name); + return mFocusedStack.getDumpActivitiesLocked(name); } static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage, diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 7ab3794..6a29d85 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -187,10 +187,12 @@ public final class BroadcastQueue { public void enqueueParallelBroadcastLocked(BroadcastRecord r) { mParallelBroadcasts.add(r); + r.enqueueClockTime = System.currentTimeMillis(); } public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { mOrderedBroadcasts.add(r); + r.enqueueClockTime = System.currentTimeMillis(); } public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index b2cfd7a..9a4d7a0 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -52,6 +52,7 @@ final class BroadcastRecord extends Binder { final int appOp; // an app op that is associated with this broadcast final List receivers; // contains BroadcastFilter and ResolveInfo IIntentReceiver resultTo; // who receives final result if non-null + long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers long dispatchClockTime; // the clock time the dispatch started long receiverTime; // when current receiver started for timeouts. @@ -102,7 +103,9 @@ final class BroadcastRecord extends Binder { pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission); pw.print(" appOp="); pw.println(appOp); } - pw.print(prefix); pw.print("dispatchClockTime="); + pw.print(prefix); pw.print("enqueueClockTime="); + pw.print(new Date(enqueueClockTime)); + pw.print(" dispatchClockTime="); pw.println(new Date(dispatchClockTime)); pw.print(prefix); pw.print("dispatchTime="); TimeUtils.formatDuration(dispatchTime, now, pw); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 9786b42..66c2f5f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -128,6 +128,7 @@ public final class PowerManagerService extends SystemService private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4; private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake private static final int WAKE_LOCK_DOZE = 1 << 6; + private static final int WAKE_LOCK_DRAW = 1 << 7; // Summarizes the user activity state. private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0; @@ -1398,12 +1399,15 @@ public final class PowerManagerService extends SystemService case PowerManager.DOZE_WAKE_LOCK: mWakeLockSummary |= WAKE_LOCK_DOZE; break; + case PowerManager.DRAW_WAKE_LOCK: + mWakeLockSummary |= WAKE_LOCK_DRAW; + break; } } // Cancel wake locks that make no sense based on the current state. if (mWakefulness != WAKEFULNESS_DOZING) { - mWakeLockSummary &= ~WAKE_LOCK_DOZE; + mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW); } if (mWakefulness == WAKEFULNESS_ASLEEP || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) { @@ -1422,6 +1426,9 @@ public final class PowerManagerService extends SystemService mWakeLockSummary |= WAKE_LOCK_CPU; } } + if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) { + mWakeLockSummary |= WAKE_LOCK_CPU; + } if (DEBUG_SPEW) { Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness=" @@ -1845,6 +1852,10 @@ public final class PowerManagerService extends SystemService if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; + if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND + && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) { + mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE; + } mDisplayPowerRequest.dozeScreenBrightness = mDozeScreenBrightnessOverrideFromDreamManager; } else { @@ -2712,6 +2723,8 @@ public final class PowerManagerService extends SystemService return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; case PowerManager.DOZE_WAKE_LOCK: return "DOZE_WAKE_LOCK "; + case PowerManager.DRAW_WAKE_LOCK: + return "DRAW_WAKE_LOCK "; default: return "??? "; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index d68c056..487483e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -461,6 +461,16 @@ final class Session extends IWindowSession.Stub return mService.getWindowId(window); } + @Override + public void pokeDrawLock(IBinder window) { + final long identity = Binder.clearCallingIdentity(); + try { + mService.pokeDrawLock(this, window); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a9b26e2..b8f26c9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -72,6 +72,19 @@ class Task { mService.mTaskIdToTask.delete(mTaskId); } + void moveTaskToStack(TaskStack stack, boolean toTop) { + if (stack == mStack) { + return; + } + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId + + " from stack=" + mStack); + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask"); + if (mStack != null) { + mStack.removeTask(this); + } + stack.addTask(this, toTop); + } + boolean removeAppToken(AppWindowToken wtoken) { boolean removed = mAppTokens.remove(wtoken); if (mAppTokens.size() == 0) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index de8a2fc..fde0bd5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -189,6 +190,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_TASK_MOVEMENT = false; static final boolean DEBUG_STACK = false; static final boolean DEBUG_DISPLAY = false; + static final boolean DEBUG_POWER = false; static final boolean SHOW_SURFACE_ALLOC = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS; @@ -327,6 +329,7 @@ public class WindowManagerService extends IWindowManager.Stub final boolean mHaveInputMethods; final boolean mHasPermanentDpad; + final long mDrawLockTimeoutMillis; final boolean mAllowBootMessages; @@ -680,11 +683,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowAnimator mAnimator; - SparseArray<Task> mTaskIdToTask = new SparseArray<Task>(); + SparseArray<Task> mTaskIdToTask = new SparseArray<>(); /** All of the TaskStacks in the window manager, unordered. For an ordered list call * DisplayContent.getStacks(). */ - SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>(); + SparseArray<TaskStack> mStackIdToStack = new SparseArray<>(); private final PointerEventDispatcher mPointerEventDispatcher; @@ -846,6 +849,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_hasPermanentDpad); mInTouchMode = context.getResources().getBoolean( com.android.internal.R.bool.config_defaultInTouchMode); + mDrawLockTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_drawLockTimeoutMillis); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mDisplaySettings = new DisplaySettings(); @@ -2960,6 +2965,15 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void pokeDrawLock(Session session, IBinder token) { + synchronized (mWindowMap) { + WindowState window = windowForClientLocked(session, token, false); + if (window != null) { + window.pokeDrawLockLw(mDrawLockTimeoutMillis); + } + } + } + public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, @@ -5075,6 +5089,7 @@ public class WindowManagerService extends IWindowManager.Stub + " to " + (toTop ? "top" : "bottom")); Task task = mTaskIdToTask.get(taskId); if (task == null) { + if (DEBUG_STACK) Slog.i(TAG, "addTask: could not find taskId=" + taskId); return; } TaskStack stack = mStackIdToStack.get(stackId); @@ -5085,6 +5100,27 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + synchronized (mWindowMap) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: moving taskId=" + taskId + + " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom")); + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find taskId=" + taskId); + return; + } + TaskStack stack = mStackIdToStack.get(stackId); + if (stack == null) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find stackId=" + stackId); + return; + } + task.moveTaskToStack(stack, toTop); + final DisplayContent displayContent = stack.getDisplayContent(); + displayContent.layoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + } + /** * Re-sizes the specified stack and its containing windows. * Returns a {@link Configuration} object that contains configurations settings @@ -5115,6 +5151,24 @@ public class WindowManagerService extends IWindowManager.Stub bounds.setEmpty(); } + /** Returns the id of an application (non-home stack) stack that match the input bounds. + * -1 if no stack matches.*/ + public int getStackIdWithBounds(Rect bounds) { + Rect stackBounds = new Rect(); + synchronized (mWindowMap) { + for (int i = mStackIdToStack.size() - 1; i >= 0; --i) { + TaskStack stack = mStackIdToStack.valueAt(i); + if (stack.mStackId != HOME_STACK_ID) { + stack.getBounds(stackBounds); + if (stackBounds.equals(bounds)) { + return stack.mStackId; + } + } + } + } + return -1; + } + /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen. * Returns a {@link Configuration} object that contains configurations settings * that should be overridden due to the operation. @@ -9957,7 +10011,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mAllowTheaterModeWakeFromLayout || Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 0) { - if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); + if (DEBUG_VISIBILITY || DEBUG_POWER) { + Slog.v(TAG, "Turning screen on after layout!"); + } mPowerManager.wakeUp(SystemClock.uptimeMillis()); } mTurnOnScreen = false; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d58b2b0..04aea84 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -19,9 +19,9 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; +import static com.android.server.wm.WindowManagerService.DEBUG_POWER; import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; - import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; @@ -34,12 +34,15 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import android.app.AppOpsManager; import android.os.Debug; +import android.os.PowerManager; import android.os.RemoteCallbackList; import android.os.SystemClock; +import android.os.WorkSource; import android.util.TimeUtils; import android.view.Display; import android.view.IWindowFocusObserver; import android.view.IWindowId; + import com.android.server.input.InputWindowHandle; import android.content.Context; @@ -343,6 +346,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { /** When true this window can be displayed on screens owther than mOwnerUid's */ private boolean mShowToOwnerOnly; + /** + * Wake lock for drawing. + * Even though it's slightly more expensive to do so, we will use a separate wake lock + * for each app that is requesting to draw while dozing so that we can accurately track + * who is preventing the system from suspending. + * This lock is only acquired on first use. + */ + PowerManager.WakeLock mDrawLock; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, final DisplayContent displayContent) { @@ -1269,6 +1281,33 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } + public void pokeDrawLockLw(long timeout) { + if (isVisibleOrAdding()) { + if (mDrawLock == null) { + // We want the tag name to be somewhat stable so that it is easier to correlate + // in wake lock statistics. So in particular, we don't want to include the + // window's hash code as in toString(). + CharSequence tag = mAttrs.getTitle(); + if (tag == null) { + tag = mAttrs.packageName; + } + mDrawLock = mService.mPowerManager.newWakeLock( + PowerManager.DRAW_WAKE_LOCK, "Window:" + tag); + mDrawLock.setReferenceCounted(false); + mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName)); + } + // Each call to acquire resets the timeout. + if (DEBUG_POWER) { + Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by " + + mAttrs.packageName); + } + mDrawLock.acquire(timeout); + } else if (DEBUG_POWER) { + Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window " + + "owned by " + mAttrs.packageName); + } + } + @Override public boolean isAlive() { return mClient.asBinder().isBinderAlive(); @@ -1642,6 +1681,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(" mWallpaperDisplayOffsetY="); pw.println(mWallpaperDisplayOffsetY); } + if (mDrawLock != null) { + pw.println("mDrawLock=" + mDrawLock); + } } String makeInputChannelName() { diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 009d25d..0aa8862 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -26,7 +26,7 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbInterface; import android.media.AudioSystem; import android.media.IAudioService; -import android.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceInfo; import android.os.FileObserver; import android.os.Bundle; import android.os.IBinder; diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 2fb6dbf..43c7336 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -31,7 +31,6 @@ import android.database.ContentObserver; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.media.AudioManager; -import android.midi.MidiDeviceInfo; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -732,6 +731,8 @@ public class UsbDeviceManager { id = com.android.internal.R.string.usb_mtp_notification_title; } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) { id = com.android.internal.R.string.usb_ptp_notification_title; + } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MIDI)) { + id = com.android.internal.R.string.usb_midi_notification_title; } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MASS_STORAGE)) { id = com.android.internal.R.string.usb_cd_installer_notification_title; diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index 396ed38..e17abc0 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -17,12 +17,12 @@ package com.android.server.usb; import android.content.Context; -import android.midi.MidiDeviceInfo; -import android.midi.MidiDeviceServer; -import android.midi.MidiManager; -import android.midi.MidiPort; -import android.midi.MidiReceiver; -import android.midi.MidiSender; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiManager; +import android.media.midi.MidiPort; +import android.media.midi.MidiReceiver; +import android.media.midi.MidiSender; import android.os.Bundle; import android.system.ErrnoException; import android.system.Os; diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 36102f1..7e4ff69 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -179,7 +179,7 @@ public final class BridgeInflater extends LayoutInflater { XmlPullParser parser = ParserFactory.create(f); BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( - parser, bridgeContext, false); + parser, bridgeContext, value.isFramework()); return inflate(bridgeParser, root); } catch (Exception e) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 62a03e1..4289689 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -206,4 +206,9 @@ public final class BridgeWindowSession implements IWindowSession { // pass for now. return null; } + + @Override + public void pokeDrawLock(IBinder window) { + // pass for now. + } } |