diff options
284 files changed, 10417 insertions, 4898 deletions
@@ -78,6 +78,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/IServiceConnection.aidl \ core/java/android/app/IStopUserCallback.aidl \ core/java/android/app/task/ITaskCallback.aidl \ + core/java/android/app/task/ITaskManager.aidl \ core/java/android/app/task/ITaskService.aidl \ core/java/android/app/IThumbnailRetriever.aidl \ core/java/android/app/ITransientNotification.aidl \ @@ -216,6 +217,8 @@ LOCAL_SRC_FILES += \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ core/java/android/tv/ITvInputClient.aidl \ + core/java/android/tv/ITvInputHardware.aidl \ + core/java/android/tv/ITvInputHardwareCallback.aidl \ core/java/android/tv/ITvInputManager.aidl \ core/java/android/tv/ITvInputService.aidl \ core/java/android/tv/ITvInputServiceCallback.aidl \ @@ -326,6 +329,7 @@ LOCAL_SRC_FILES += \ telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl \ telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \ telecomm/java/com/android/internal/telecomm/IInCallService.aidl \ + telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index c841338..48a20a4 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -191,6 +191,7 @@ $(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/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/tv/) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/media/java/android/media/) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/app) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/app/wearable) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/api/current.txt b/api/current.txt index a829bdf..df908e0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1399,8 +1399,6 @@ package android { field public static final int l_resource_pad9 = 17104904; // 0x1050008 field public static final int notification_large_icon_height = 17104902; // 0x1050006 field public static final int notification_large_icon_width = 17104901; // 0x1050005 - field public static final int recents_thumbnail_height = 17104913; // 0x1050011 - field public static final int recents_thumbnail_width = 17104914; // 0x1050012 field public static final int thumbnail_height = 17104897; // 0x1050001 field public static final int thumbnail_width = 17104898; // 0x1050002 } @@ -4538,13 +4536,22 @@ package android.app { ctor public Notification.Action.Builder(android.app.Notification.Action); method public android.app.Notification.Action.Builder addExtras(android.os.Bundle); method public android.app.Notification.Action.Builder addRemoteInput(android.app.RemoteInput); - method public android.app.Notification.Action.Builder apply(android.app.Notification.Action.Builder.Extender); method public android.app.Notification.Action build(); + method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender); method public android.os.Bundle getExtras(); } - public static abstract interface Notification.Action.Builder.Extender { - method public abstract android.app.Notification.Action.Builder applyTo(android.app.Notification.Action.Builder); + public static abstract interface Notification.Action.Extender { + method public abstract android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); + } + + public static final class Notification.Action.WearableExtender implements android.app.Notification.Action.Extender { + ctor public Notification.Action.WearableExtender(); + ctor public Notification.Action.WearableExtender(android.app.Notification.Action); + method public android.app.Notification.Action.WearableExtender clone(); + method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); + method public boolean isAvailableOffline(); + method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean); } public static class Notification.BigPictureStyle extends android.app.Notification.Style { @@ -4570,8 +4577,8 @@ package android.app { method public android.app.Notification.Builder addAction(android.app.Notification.Action); method public android.app.Notification.Builder addExtras(android.os.Bundle); method public android.app.Notification.Builder addPerson(java.lang.String); - method public android.app.Notification.Builder apply(android.app.Notification.Builder.Extender); method public android.app.Notification build(); + method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); method public android.app.Notification.Builder setAutoCancel(boolean); @@ -4613,8 +4620,8 @@ package android.app { method public android.app.Notification.Builder setWhen(long); } - public static abstract interface Notification.Builder.Extender { - method public abstract android.app.Notification.Builder applyTo(android.app.Notification.Builder); + public static abstract interface Notification.Extender { + method public abstract android.app.Notification.Builder extend(android.app.Notification.Builder); } public static class Notification.InboxStyle extends android.app.Notification.Style { @@ -4644,6 +4651,52 @@ package android.app { field protected android.app.Notification.Builder mBuilder; } + public static final class Notification.WearableExtender implements android.app.Notification.Extender { + ctor public Notification.WearableExtender(); + ctor public Notification.WearableExtender(android.app.Notification); + method public android.app.Notification.WearableExtender addAction(android.app.Notification.Action); + method public android.app.Notification.WearableExtender addActions(java.util.List<android.app.Notification.Action>); + method public android.app.Notification.WearableExtender addPage(android.app.Notification); + method public android.app.Notification.WearableExtender addPages(java.util.List<android.app.Notification>); + method public android.app.Notification.WearableExtender clearActions(); + method public android.app.Notification.WearableExtender clearPages(); + method public android.app.Notification.WearableExtender clone(); + method public android.app.Notification.Builder extend(android.app.Notification.Builder); + method public java.util.List<android.app.Notification.Action> getActions(); + method public android.graphics.Bitmap getBackground(); + method public int getContentAction(); + method public int getContentIcon(); + method public int getContentIconGravity(); + method public boolean getContentIntentAvailableOffline(); + method public int getCustomContentHeight(); + method public int getCustomSizePreset(); + method public android.app.PendingIntent getDisplayIntent(); + method public int getGravity(); + method public boolean getHintHideIcon(); + method public boolean getHintShowBackgroundOnly(); + method public java.util.List<android.app.Notification> getPages(); + method public boolean getStartScrollBottom(); + method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap); + method public android.app.Notification.WearableExtender setContentAction(int); + method public android.app.Notification.WearableExtender setContentIcon(int); + method public android.app.Notification.WearableExtender setContentIconGravity(int); + method public android.app.Notification.WearableExtender setContentIntentAvailableOffline(boolean); + method public android.app.Notification.WearableExtender setCustomContentHeight(int); + method public android.app.Notification.WearableExtender setCustomSizePreset(int); + method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent); + method public android.app.Notification.WearableExtender setGravity(int); + method public android.app.Notification.WearableExtender setHintHideIcon(boolean); + method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean); + method public android.app.Notification.WearableExtender setStartScrollBottom(boolean); + field public static final int SIZE_DEFAULT = 0; // 0x0 + field public static final int SIZE_FULL_SCREEN = 5; // 0x5 + field public static final int SIZE_LARGE = 4; // 0x4 + field public static final int SIZE_MEDIUM = 3; // 0x3 + field public static final int SIZE_SMALL = 2; // 0x2 + field public static final int SIZE_XSMALL = 1; // 0x1 + field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff + } + public class NotificationManager { method public void cancel(int); method public void cancel(java.lang.String, int); @@ -5298,95 +5351,72 @@ package android.app.maintenance { package android.app.task { - public class TaskParams implements android.os.Parcelable { + public class Task implements android.os.Parcelable { method public int describeContents(); + method public int getBackoffPolicy(); method public android.os.Bundle getExtras(); + method public long getInitialBackoffMillis(); + method public long getIntervalMillis(); + method public long getMaxExecutionDelayMillis(); + method public long getMinLatencyMillis(); + method public int getNetworkCapabilities(); + method public android.content.ComponentName getService(); method public int getTaskId(); + method public boolean isPeriodic(); + method public boolean isRequireCharging(); + method public boolean isRequireDeviceIdle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } - public abstract class TaskService extends android.app.Service { - ctor public TaskService(); - method public final android.os.IBinder onBind(android.content.Intent); - method public abstract boolean onStartTask(android.app.task.TaskParams); - method public abstract boolean onStopTask(android.app.task.TaskParams); - method public final void taskFinished(android.app.task.TaskParams, boolean); - field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_TASK_SERVICE"; + public static abstract interface Task.BackoffPolicy { + field public static final int EXPONENTIAL = 1; // 0x1 + field public static final int LINEAR = 0; // 0x0 } -} - -package android.app.wearable { + public final class Task.Builder { + ctor public Task.Builder(int, android.content.ComponentName); + method public android.app.task.Task build(); + method public android.app.task.Task.Builder setBackoffCriteria(long, int); + method public android.app.task.Task.Builder setExtras(android.os.Bundle); + method public android.app.task.Task.Builder setMinimumLatency(long); + method public android.app.task.Task.Builder setOverrideDeadline(long); + method public android.app.task.Task.Builder setPeriodic(long); + method public android.app.task.Task.Builder setRequiredNetworkCapabilities(int); + method public android.app.task.Task.Builder setRequiresCharging(boolean); + method public android.app.task.Task.Builder setRequiresDeviceIdle(boolean); + } - public final class WearableActionExtensions implements android.app.Notification.Action.Builder.Extender android.os.Parcelable { - method public android.app.Notification.Action.Builder applyTo(android.app.Notification.Action.Builder); - method public int describeContents(); - method public static android.app.wearable.WearableActionExtensions from(android.app.Notification.Action); - method public boolean isAvailableOffline(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; + public static abstract interface Task.NetworkType { + field public static final int ANY = 0; // 0x0 + field public static final int UNMETERED = 1; // 0x1 } - public static final class WearableActionExtensions.Builder { - ctor public WearableActionExtensions.Builder(); - ctor public WearableActionExtensions.Builder(android.app.wearable.WearableActionExtensions); - method public android.app.wearable.WearableActionExtensions build(); - method public android.app.wearable.WearableActionExtensions.Builder setAvailableOffline(boolean); + public abstract class TaskManager { + ctor public TaskManager(); + method public abstract void cancel(int); + method public abstract void cancelAll(); + method public abstract java.util.List<android.app.task.Task> getAllPendingTasks(); + method public abstract int schedule(android.app.task.Task); + field public static final int RESULT_INVALID_PARAMETERS = -1; // 0xffffffff + field public static final int RESULT_OVER_QUOTA = -2; // 0xfffffffe } - public final class WearableNotificationExtensions implements android.app.Notification.Builder.Extender android.os.Parcelable { - method public android.app.Notification.Builder applyTo(android.app.Notification.Builder); + public class TaskParams implements android.os.Parcelable { method public int describeContents(); - method public static android.app.wearable.WearableNotificationExtensions from(android.app.Notification); - method public android.app.Notification.Action getAction(int); - method public int getActionCount(); - method public android.app.Notification.Action[] getActions(); - method public android.graphics.Bitmap getBackground(); - method public int getContentAction(); - method public int getContentIcon(); - method public int getContentIconGravity(); - method public boolean getContentIntentAvailableOffline(); - method public int getCustomContentHeight(); - method public int getCustomSizePreset(); - method public android.app.PendingIntent getDisplayIntent(); - method public int getGravity(); - method public boolean getHintHideIcon(); - method public boolean getHintShowBackgroundOnly(); - method public android.app.Notification[] getPages(); - method public boolean getStartScrollBottom(); + method public android.os.Bundle getExtras(); + method public int getTaskId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; - field public static final int SIZE_DEFAULT = 0; // 0x0 - field public static final int SIZE_LARGE = 4; // 0x4 - field public static final int SIZE_MEDIUM = 3; // 0x3 - field public static final int SIZE_SMALL = 2; // 0x2 - field public static final int SIZE_XSMALL = 1; // 0x1 - field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff } - public static final class WearableNotificationExtensions.Builder { - ctor public WearableNotificationExtensions.Builder(); - ctor public WearableNotificationExtensions.Builder(android.app.wearable.WearableNotificationExtensions); - method public android.app.wearable.WearableNotificationExtensions.Builder addAction(android.app.Notification.Action); - method public android.app.wearable.WearableNotificationExtensions.Builder addActions(java.util.List<android.app.Notification.Action>); - method public android.app.wearable.WearableNotificationExtensions.Builder addPage(android.app.Notification); - method public android.app.wearable.WearableNotificationExtensions.Builder addPages(java.util.List<android.app.Notification>); - method public android.app.wearable.WearableNotificationExtensions build(); - method public android.app.wearable.WearableNotificationExtensions.Builder clearActions(); - method public android.app.wearable.WearableNotificationExtensions.Builder clearPages(); - method public android.app.wearable.WearableNotificationExtensions.Builder setBackground(android.graphics.Bitmap); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentAction(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIcon(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIconGravity(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIntentAvailableOffline(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setCustomContentHeight(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setCustomSizePreset(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setDisplayIntent(android.app.PendingIntent); - method public android.app.wearable.WearableNotificationExtensions.Builder setGravity(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setHintHideIcon(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setHintShowBackgroundOnly(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setStartScrollBottom(boolean); + public abstract class TaskService extends android.app.Service { + ctor public TaskService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract boolean onStartTask(android.app.task.TaskParams); + method public abstract boolean onStopTask(android.app.task.TaskParams); + method public final void taskFinished(android.app.task.TaskParams, boolean); + field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_TASK_SERVICE"; } } @@ -6995,6 +7025,7 @@ package android.content { field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String STORAGE_SERVICE = "storage"; + field public static final java.lang.String TASK_SERVICE = "task"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field public static final java.lang.String TV_INPUT_SERVICE = "tv_input"; @@ -7933,55 +7964,6 @@ package android.content { method public abstract void onStatusChanged(int); } - public class Task implements android.os.Parcelable { - method public int describeContents(); - method public int getBackoffPolicy(); - method public android.os.Bundle getExtras(); - method public long getInitialBackoffMillis(); - method public long getIntervalMillis(); - method public long getMaxExecutionDelayMillis(); - method public long getMinLatencyMillis(); - method public int getNetworkCapabilities(); - method public android.content.ComponentName getService(); - method public int getTaskId(); - method public boolean isPeriodic(); - method public boolean isRequireCharging(); - method public boolean isRequireDeviceIdle(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public static abstract interface Task.BackoffPolicy { - field public static final int EXPONENTIAL = 1; // 0x1 - field public static final int LINEAR = 0; // 0x0 - } - - public final class Task.Builder { - ctor public Task.Builder(int, android.content.ComponentName); - method public android.content.Task build(); - method public android.content.Task.Builder setBackoffCriteria(long, int); - method public android.content.Task.Builder setExtras(android.os.Bundle); - method public android.content.Task.Builder setMinimumLatency(long); - method public android.content.Task.Builder setOverrideDeadline(long); - method public android.content.Task.Builder setPeriodic(long); - method public android.content.Task.Builder setRequiredNetworkCapabilities(int); - method public android.content.Task.Builder setRequiresCharging(boolean); - method public android.content.Task.Builder setRequiresDeviceIdle(boolean); - } - - public static abstract interface Task.NetworkType { - field public static final int ANY = 0; // 0x0 - field public static final int UNMETERED = 1; // 0x1 - } - - public abstract class TaskManager { - ctor public TaskManager(); - method public abstract void cancel(int); - method public abstract void cancelAll(); - method public abstract java.util.List<android.content.Task> getAllPendingTasks(); - method public abstract int schedule(android.content.Task); - } - public class UriMatcher { ctor public UriMatcher(int); method public void addURI(java.lang.String, java.lang.String, int); @@ -11570,16 +11552,6 @@ package android.graphics.drawable { method public void startTransition(int); } - public class VectorDrawable extends android.graphics.drawable.Drawable { - ctor public VectorDrawable(); - method public void draw(android.graphics.Canvas); - method public int getOpacity(); - method public void setAlpha(int); - method public void setColorFilter(android.graphics.ColorFilter); - method public void setPadding(android.graphics.Rect); - method public void setPadding(int, int, int, int); - } - } package android.graphics.drawable.shapes { @@ -12150,9 +12122,11 @@ package android.hardware.camera2 { public static abstract class CameraCaptureSession.CaptureListener { ctor public CameraCaptureSession.CaptureListener(); - method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); + method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.TotalCaptureResult); method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureFailure); - method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraDevice, int, int); + method public void onCaptureProgressed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); + method public void onCaptureSequenceAborted(android.hardware.camera2.CameraDevice, int); + method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraDevice, int, long); method public void onCaptureStarted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, long); } @@ -12179,7 +12153,9 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AVAILABLE_SCENE_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AWB_AVAILABLE_MODES; - field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_MAX_REGIONS; + field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_MAX_REGIONS_AE; + field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_MAX_REGIONS_AF; + field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_MAX_REGIONS_AWB; field public static final android.hardware.camera2.CameraCharacteristics.Key EDGE_AVAILABLE_EDGE_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_INFO_AVAILABLE; field public static final android.hardware.camera2.CameraCharacteristics.Key HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES; @@ -12193,11 +12169,12 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_INFO_FOCUS_DISTANCE_CALIBRATION; field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_INFO_HYPERFOCAL_DISTANCE; field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_INFO_MINIMUM_FOCUS_DISTANCE; - field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_INFO_SHADING_MAP_SIZE; field public static final android.hardware.camera2.CameraCharacteristics.Key NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_AVAILABLE_CAPABILITIES; field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_INPUT_STREAMS; - field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_STREAMS; + field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_PROC; + field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_PROC_STALLING; + field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_RAW; field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_PARTIAL_RESULT_COUNT; field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_PIPELINE_MAX_DEPTH; field public static final android.hardware.camera2.CameraCharacteristics.Key SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; @@ -12259,9 +12236,11 @@ package android.hardware.camera2 { public static abstract deprecated class CameraDevice.CaptureListener { ctor public CameraDevice.CaptureListener(); - method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); + method public void onCaptureCompleted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.TotalCaptureResult); method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureFailure); - method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraDevice, int, int); + method public void onCaptureProgressed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); + method public void onCaptureSequenceAborted(android.hardware.camera2.CameraDevice, int); + method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraDevice, int, long); method public void onCaptureStarted(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, long); } @@ -12415,6 +12394,7 @@ package android.hardware.camera2 { field public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int NOISE_REDUCTION_MODE_OFF = 0; // 0x0 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; // 0x5 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; // 0x3 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; // 0x4 field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0 @@ -12508,9 +12488,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureRequest.Key EDGE_MODE; field public static final android.hardware.camera2.CaptureRequest.Key FLASH_MODE; field public static final android.hardware.camera2.CaptureRequest.Key HOT_PIXEL_MODE; - field public static final android.hardware.camera2.CaptureRequest.Key JPEG_GPS_COORDINATES; - field public static final android.hardware.camera2.CaptureRequest.Key JPEG_GPS_PROCESSING_METHOD; - field public static final android.hardware.camera2.CaptureRequest.Key JPEG_GPS_TIMESTAMP; + field public static final android.hardware.camera2.CaptureRequest.Key JPEG_GPS_LOCATION; field public static final android.hardware.camera2.CaptureRequest.Key JPEG_ORIENTATION; field public static final android.hardware.camera2.CaptureRequest.Key JPEG_QUALITY; field public static final android.hardware.camera2.CaptureRequest.Key JPEG_THUMBNAIL_QUALITY; @@ -12531,9 +12509,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_FACE_DETECT_MODE; field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_HOT_PIXEL_MAP_MODE; field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_LENS_SHADING_MAP_MODE; - field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_CURVE_BLUE; - field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_CURVE_GREEN; - field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_CURVE_RED; + field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_MODE; } @@ -12552,7 +12528,7 @@ package android.hardware.camera2 { method public final int hashCode(); } - public final class CaptureResult extends android.hardware.camera2.CameraMetadata { + public class CaptureResult extends android.hardware.camera2.CameraMetadata { method public T get(android.hardware.camera2.CaptureResult.Key<T>); method public int getFrameNumber(); method public android.hardware.camera2.CaptureRequest getRequest(); @@ -12586,9 +12562,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureResult.Key FLASH_MODE; field public static final android.hardware.camera2.CaptureResult.Key FLASH_STATE; field public static final android.hardware.camera2.CaptureResult.Key HOT_PIXEL_MODE; - field public static final android.hardware.camera2.CaptureResult.Key JPEG_GPS_COORDINATES; - field public static final android.hardware.camera2.CaptureResult.Key JPEG_GPS_PROCESSING_METHOD; - field public static final android.hardware.camera2.CaptureResult.Key JPEG_GPS_TIMESTAMP; + field public static final android.hardware.camera2.CaptureResult.Key JPEG_GPS_LOCATION; field public static final android.hardware.camera2.CaptureResult.Key JPEG_ORIENTATION; field public static final android.hardware.camera2.CaptureResult.Key JPEG_QUALITY; field public static final android.hardware.camera2.CaptureResult.Key JPEG_THUMBNAIL_QUALITY; @@ -12617,12 +12591,10 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_FACE_DETECT_MODE; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_HOT_PIXEL_MAP; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_HOT_PIXEL_MAP_MODE; - field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_MAP; + field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_CORRECTION_MAP; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_MAP_MODE; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_SCENE_FLICKER; - field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_CURVE_BLUE; - field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_CURVE_GREEN; - field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_CURVE_RED; + field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_MODE; } @@ -12645,6 +12617,10 @@ package android.hardware.camera2 { method public void writeInputStream(java.io.OutputStream, android.util.Size, java.io.InputStream, long) throws java.io.IOException; } + public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { + method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults(); + } + } package android.hardware.camera2.params { @@ -13741,45 +13717,6 @@ package android.media { method public void stop(); } - public final class AudioAttributes { - method public int getContentType(); - method public int getFlags(); - method public java.util.Set<java.lang.String> getTags(); - method public int getUsage(); - field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3 - field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2 - field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4 - field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1 - field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0 - field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1 - field public static final int USAGE_ALARM = 4; // 0x4 - field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb - field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc - field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd - field public static final int USAGE_GAME = 14; // 0xe - field public static final int USAGE_MEDIA = 1; // 0x1 - field public static final int USAGE_NOTIFICATION = 5; // 0x5 - field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9 - field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8 - field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7 - field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa - field public static final int USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6; // 0x6 - field public static final int USAGE_UNKNOWN = 0; // 0x0 - field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2 - field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3 - } - - public static class AudioAttributes.Builder { - ctor public AudioAttributes.Builder(); - ctor public AudioAttributes.Builder(android.media.AudioAttributes); - method public android.media.AudioAttributes.Builder addTag(java.lang.String); - method public android.media.AudioAttributes build(); - method public android.media.AudioAttributes.Builder setContentType(int); - method public android.media.AudioAttributes.Builder setFlags(int); - method public android.media.AudioAttributes.Builder setLegacyStreamType(int); - method public android.media.AudioAttributes.Builder setUsage(int); - } - public class AudioFormat { ctor public AudioFormat(); field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1 @@ -21693,9 +21630,7 @@ package android.os { method public abstract void cancel(); method public abstract boolean hasVibrator(); method public void vibrate(long); - method public void vibrate(long, int); method public void vibrate(long[], int); - method public void vibrate(long[], int, int); } public class WorkSource implements android.os.Parcelable { @@ -21733,10 +21668,6 @@ package android.os.storage { method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); - field public static final int CRYPT_TYPE_DEFAULT = 1; // 0x1 - field public static final int CRYPT_TYPE_PASSWORD = 0; // 0x0 - field public static final int CRYPT_TYPE_PATTERN = 2; // 0x2 - field public static final int CRYPT_TYPE_PIN = 3; // 0x3 } } @@ -26412,7 +26343,7 @@ package android.service.voice { field public static final int TOUCHABLE_INSETS_CONTENT = 1; // 0x1 field public static final int TOUCHABLE_INSETS_FRAME = 0; // 0x0 field public static final int TOUCHABLE_INSETS_REGION = 3; // 0x3 - field public int contentTopInsets; + field public final android.graphics.Rect contentInsets; field public int touchableInsets; field public final android.graphics.Region touchableRegion; } @@ -27491,306 +27422,6 @@ package android.system { } -package android.telecomm { - - public final class CallAudioState implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static int ROUTE_ALL; - field public static int ROUTE_BLUETOOTH; - field public static int ROUTE_EARPIECE; - field public static int ROUTE_SPEAKER; - field public static int ROUTE_WIRED_HEADSET; - field public static int ROUTE_WIRED_OR_EARPIECE; - field public final boolean isMuted; - field public final int route; - field public final int supportedRouteMask; - } - - public class CallCapabilities { - ctor public CallCapabilities(); - field public static final int ADD_CALL = 16; // 0x10 - field public static final int ALL = 511; // 0x1ff - field public static final int CONNECTION_HANDOFF = 256; // 0x100 - field public static final int GENERIC_CONFERENCE = 128; // 0x80 - field public static final int HOLD = 1; // 0x1 - field public static final int MERGE_CALLS = 4; // 0x4 - field public static final int MUTE = 64; // 0x40 - field public static final int RESPOND_VIA_TEXT = 32; // 0x20 - field public static final int SUPPORT_HOLD = 2; // 0x2 - field public static final int SWAP_CALLS = 8; // 0x8 - } - - public final class CallInfo implements android.os.Parcelable { - ctor public CallInfo(java.lang.String, android.telecomm.CallState, android.net.Uri); - method public int describeContents(); - method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor(); - method public android.os.Bundle getExtras(); - method public android.telecomm.GatewayInfo getGatewayInfo(); - method public android.net.Uri getHandle(); - method public java.lang.String getId(); - method public android.net.Uri getOriginalHandle(); - method public android.telecomm.CallState getState(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public final class CallNumberPresentation extends java.lang.Enum { - method public static android.telecomm.CallNumberPresentation valueOf(java.lang.String); - method public static final android.telecomm.CallNumberPresentation[] values(); - enum_constant public static final android.telecomm.CallNumberPresentation ALLOWED; - enum_constant public static final android.telecomm.CallNumberPresentation PAYPHONE; - enum_constant public static final android.telecomm.CallNumberPresentation RESTRICTED; - enum_constant public static final android.telecomm.CallNumberPresentation UNKNOWN; - } - - public abstract class CallService extends android.app.Service { - ctor public CallService(); - method public abstract void abort(java.lang.String); - method public abstract void answer(java.lang.String); - method public abstract void call(android.telecomm.CallInfo); - method public abstract void disconnect(java.lang.String); - method protected final android.telecomm.CallServiceAdapter getAdapter(); - method public final android.os.IBinder getBinder(); - method public abstract void hold(java.lang.String); - method public abstract void isCompatibleWith(android.telecomm.CallInfo); - method protected void onAdapterAttached(android.telecomm.CallServiceAdapter); - method public abstract void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState); - method public final android.os.IBinder onBind(android.content.Intent); - method public abstract void playDtmfTone(java.lang.String, char); - method public abstract void reject(java.lang.String); - method public abstract void setIncomingCallId(java.lang.String, android.os.Bundle); - method public abstract void stopDtmfTone(java.lang.String); - method public abstract void unhold(java.lang.String); - } - - public final class CallServiceAdapter { - method public void handleFailedOutgoingCall(java.lang.String, java.lang.String); - method public void handleSuccessfulOutgoingCall(java.lang.String); - method public void notifyIncomingCall(android.telecomm.CallInfo); - method public void setActive(java.lang.String); - method public void setDialing(java.lang.String); - method public void setDisconnected(java.lang.String, int, java.lang.String); - method public void setIsCompatibleWith(java.lang.String, boolean); - method public void setOnHold(java.lang.String); - method public void setRinging(java.lang.String); - } - - public final class CallServiceDescriptor implements android.os.Parcelable { - method public int describeContents(); - method public java.lang.String getCallServiceId(); - method public int getNetworkType(); - method public android.content.ComponentName getServiceComponent(); - method public static android.telecomm.CallServiceDescriptor.Builder newBuilder(android.content.Context); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final int FLAG_MOBILE = 4; // 0x4 - field public static final int FLAG_PSTN = 2; // 0x2 - field public static final int FLAG_WIFI = 1; // 0x1 - } - - public static class CallServiceDescriptor.Builder { - method public android.telecomm.CallServiceDescriptor build(); - method public android.telecomm.CallServiceDescriptor.Builder setCallService(java.lang.Class<? extends android.telecomm.CallService>); - method public android.telecomm.CallServiceDescriptor.Builder setNetworkType(int); - } - - public final class CallServiceLookupResponse { - method public void setCallServiceDescriptors(java.util.List<android.telecomm.CallServiceDescriptor>); - } - - public abstract class CallServiceProvider extends android.app.Service { - ctor protected CallServiceProvider(); - method public abstract void lookupCallServices(android.telecomm.CallServiceLookupResponse); - method public android.os.IBinder onBind(android.content.Intent); - } - - public abstract class CallServiceSelector extends android.app.Service { - ctor protected CallServiceSelector(); - method protected final void cancelOutgoingCall(android.telecomm.CallInfo); - method protected final android.telecomm.CallServiceSelectorAdapter getAdapter(); - method protected final java.util.Collection<android.telecomm.CallInfo> getCalls(); - method protected void onAdapterAttached(android.telecomm.CallServiceSelectorAdapter); - method public final android.os.IBinder onBind(android.content.Intent); - method protected abstract void select(android.telecomm.CallInfo, java.util.List<android.telecomm.CallServiceDescriptor>); - } - - public final class CallServiceSelectorAdapter { - method public void cancelOutgoingCall(java.lang.String); - method public void setHandoffInfo(java.lang.String, android.net.Uri, android.os.Bundle); - method public void setSelectedCallServices(java.lang.String, java.util.List<android.telecomm.CallServiceDescriptor>); - } - - public final class CallState extends java.lang.Enum { - method public static android.telecomm.CallState valueOf(java.lang.String); - method public static final android.telecomm.CallState[] values(); - enum_constant public static final android.telecomm.CallState ACTIVE; - enum_constant public static final android.telecomm.CallState DIALING; - enum_constant public static final android.telecomm.CallState DISCONNECTED; - enum_constant public static final android.telecomm.CallState NEW; - enum_constant public static final android.telecomm.CallState ON_HOLD; - enum_constant public static final android.telecomm.CallState POST_DIAL; - enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT; - enum_constant public static final android.telecomm.CallState RINGING; - } - - public abstract class Connection { - ctor protected Connection(); - method public final android.telecomm.CallAudioState getCallAudioState(); - method public final android.net.Uri getHandle(); - method protected void onAbort(); - method protected void onAnswer(); - method protected void onDisconnect(); - method protected void onHold(); - method protected void onPlayDtmfTone(char); - method protected void onReject(); - method protected void onSetAudioState(android.telecomm.CallAudioState); - method protected void onSetSignal(android.os.Bundle); - method protected void onStopDtmfTone(); - method protected void onUnhold(); - method protected void setActive(); - method public void setAudioState(android.telecomm.CallAudioState); - method protected void setDialing(); - method protected void setDisconnected(int, java.lang.String); - method protected void setHandle(android.net.Uri); - method protected void setOnHold(); - method protected void setRinging(); - method public static java.lang.String stateToString(int); - } - - public static abstract interface Connection.Listener { - method public abstract void onAudioStateChanged(android.telecomm.Connection, android.telecomm.CallAudioState); - method public abstract void onDestroyed(android.telecomm.Connection); - method public abstract void onDisconnected(android.telecomm.Connection, int, java.lang.String); - method public abstract void onHandleChanged(android.telecomm.Connection, android.net.Uri); - method public abstract void onSignalChanged(android.telecomm.Connection, android.os.Bundle); - method public abstract void onStateChanged(android.telecomm.Connection, int); - } - - public static class Connection.ListenerBase implements android.telecomm.Connection.Listener { - ctor public Connection.ListenerBase(); - method public void onAudioStateChanged(android.telecomm.Connection, android.telecomm.CallAudioState); - method public void onDestroyed(android.telecomm.Connection); - method public void onDisconnected(android.telecomm.Connection, int, java.lang.String); - method public void onHandleChanged(android.telecomm.Connection, android.net.Uri); - method public void onSignalChanged(android.telecomm.Connection, android.os.Bundle); - method public void onStateChanged(android.telecomm.Connection, int); - } - - public final class Connection.State { - field public static final int ACTIVE = 3; // 0x3 - field public static final int DIALING = 2; // 0x2 - field public static final int DISCONNECTED = 5; // 0x5 - field public static final int HOLDING = 4; // 0x4 - field public static final int NEW = 0; // 0x0 - field public static final int RINGING = 1; // 0x1 - } - - public final class ConnectionRequest { - ctor public ConnectionRequest(android.net.Uri, android.os.Bundle); - method public android.os.Bundle getExtras(); - method public android.net.Uri getHandle(); - } - - public abstract class ConnectionService extends android.telecomm.CallService { - ctor public ConnectionService(); - method public final void abort(java.lang.String); - method public final void answer(java.lang.String); - method public final void call(android.telecomm.CallInfo); - method public final void disconnect(java.lang.String); - method public final void hold(java.lang.String); - method public final void isCompatibleWith(android.telecomm.CallInfo); - method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState); - method public void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>); - method public void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>); - method public void onFindSubscriptions(android.net.Uri, android.telecomm.Response<android.net.Uri, android.telecomm.Subscription>); - method public final void playDtmfTone(java.lang.String, char); - method public final void reject(java.lang.String); - method public final void setIncomingCallId(java.lang.String, android.os.Bundle); - method public final void stopDtmfTone(java.lang.String); - method public final void unhold(java.lang.String); - } - - public class GatewayInfo implements android.os.Parcelable { - method public int describeContents(); - method public android.net.Uri getGatewayHandle(); - method public java.lang.String getGatewayProviderPackageName(); - method public android.net.Uri getOriginalHandle(); - method public boolean isEmpty(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public final class InCallAdapter { - method public void answerCall(java.lang.String); - method public void disconnectCall(java.lang.String); - method public void handoffCall(java.lang.String); - method public void holdCall(java.lang.String); - method public void mute(boolean); - method public void playDtmfTone(java.lang.String, char); - method public void postDialContinue(java.lang.String); - method public void rejectCall(java.lang.String); - method public void setAudioRoute(int); - method public void stopDtmfTone(java.lang.String); - method public void unholdCall(java.lang.String); - } - - public final class InCallCall implements android.os.Parcelable { - method public int describeContents(); - method public int getCapabilities(); - method public long getConnectTimeMillis(); - method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor(); - method public int getDisconnectCause(); - method public android.telecomm.GatewayInfo getGatewayInfo(); - method public android.net.Uri getHandle(); - method public android.telecomm.CallServiceDescriptor getHandoffCallServiceDescriptor(); - method public java.lang.String getId(); - method public android.telecomm.CallState getState(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public abstract class InCallService extends android.app.Service { - ctor protected InCallService(); - method protected abstract void addCall(android.telecomm.InCallCall); - method protected final android.telecomm.InCallAdapter getAdapter(); - method protected void onAdapterAttached(android.telecomm.InCallAdapter); - method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState); - method public final android.os.IBinder onBind(android.content.Intent); - method protected abstract void setPostDial(java.lang.String, java.lang.String); - method protected abstract void setPostDialWait(java.lang.String, java.lang.String); - method protected abstract void updateCall(android.telecomm.InCallCall); - } - - public abstract interface Response { - method public abstract void onError(IN, java.lang.String); - method public abstract void onResult(IN, OUT...); - } - - public class Subscription implements android.os.Parcelable { - ctor public Subscription(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public final class TelecommConstants { - ctor public TelecommConstants(); - field public static final java.lang.String ACTION_CALL_SERVICE; - field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER; - field public static final java.lang.String ACTION_CALL_SERVICE_SELECTOR; - field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL"; - field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ',' - field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';' - field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecomm.extra.CALL_DISCONNECT_CAUSE"; - field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE"; - field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR"; - field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS"; - } - -} - package android.telephony { public final class CellIdentityCdma implements android.os.Parcelable { @@ -35913,10 +35544,14 @@ package android.widget { public class ActionMenuView extends android.widget.LinearLayout { ctor public ActionMenuView(android.content.Context); ctor public ActionMenuView(android.content.Context, android.util.AttributeSet); + method public void dismissPopupMenus(); method public android.view.Menu getMenu(); + method public boolean hideOverflowMenu(); + method public boolean isOverflowMenuShowing(); method public void onConfigurationChanged(android.content.res.Configuration); method public void onDetachedFromWindow(); method public void setOnMenuItemClickListener(android.widget.ActionMenuView.OnMenuItemClickListener); + method public boolean showOverflowMenu(); } public static class ActionMenuView.LayoutParams extends android.widget.LinearLayout.LayoutParams { @@ -38094,6 +37729,8 @@ package android.widget { ctor public Toolbar(android.content.Context, android.util.AttributeSet); ctor public Toolbar(android.content.Context, android.util.AttributeSet, int); ctor public Toolbar(android.content.Context, android.util.AttributeSet, int, int); + method public void collapseActionView(); + method public void dismissPopupMenus(); method public int getContentInsetEnd(); method public int getContentInsetLeft(); method public int getContentInsetRight(); @@ -38104,7 +37741,10 @@ package android.widget { method public android.graphics.drawable.Drawable getNavigationIcon(); method public java.lang.CharSequence getSubtitle(); method public java.lang.CharSequence getTitle(); + method public boolean hasExpandedActionView(); + method public boolean hideOverflowMenu(); method public void inflateMenu(int); + method public boolean isOverflowMenuShowing(); method protected void onLayout(boolean, int, int, int, int); method public void setContentInsetsAbsolute(int, int); method public void setContentInsetsRelative(int, int); @@ -38112,6 +37752,8 @@ package android.widget { method public void setLogo(android.graphics.drawable.Drawable); method public void setLogoDescription(int); method public void setLogoDescription(java.lang.CharSequence); + method public void setNavigationContentDescription(java.lang.CharSequence); + method public void setNavigationContentDescription(int); method public void setNavigationDescription(int); method public void setNavigationDescription(java.lang.CharSequence); method public void setNavigationIcon(int); @@ -38122,17 +37764,18 @@ package android.widget { method public void setSubtitle(java.lang.CharSequence); method public void setTitle(int); method public void setTitle(java.lang.CharSequence); + method public boolean showOverflowMenu(); } - public static class Toolbar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams { + public static class Toolbar.LayoutParams extends android.app.ActionBar.LayoutParams { ctor public Toolbar.LayoutParams(android.content.Context, android.util.AttributeSet); ctor public Toolbar.LayoutParams(int, int); ctor public Toolbar.LayoutParams(int, int, int); ctor public Toolbar.LayoutParams(int); ctor public Toolbar.LayoutParams(android.widget.Toolbar.LayoutParams); + ctor public Toolbar.LayoutParams(android.app.ActionBar.LayoutParams); ctor public Toolbar.LayoutParams(android.view.ViewGroup.MarginLayoutParams); ctor public Toolbar.LayoutParams(android.view.ViewGroup.LayoutParams); - field public int gravity; } public static abstract interface Toolbar.OnMenuItemClickListener { diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 3c3df01..f05f4c7 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -1291,6 +1291,7 @@ public abstract class ActionBar { public LayoutParams(int width, int height) { super(width, height); + this.gravity = Gravity.CENTER_VERTICAL | Gravity.START; } public LayoutParams(int width, int height, int gravity) { @@ -1305,6 +1306,7 @@ public abstract class ActionBar { public LayoutParams(LayoutParams source) { super(source); + this.gravity = source.gravity; } public LayoutParams(ViewGroup.LayoutParams source) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 07de85c..b5281ff 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4194,7 +4194,11 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode) { - startActivityFromFragment(fragment, intent, requestCode, null); + Bundle options = null; + if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle(); + } + startActivityFromFragment(fragment, intent, requestCode, options); } /** @@ -4219,6 +4223,9 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) { + if (options != null) { + mActivityTransitionState.startExitOutTransition(this, options); + } Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, fragment, diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5fd288f..d9adba3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3010,8 +3010,8 @@ public final class ActivityThread { int h; if (w < 0) { Resources res = r.activity.getResources(); - int wId = com.android.internal.R.dimen.recents_thumbnail_width; - int hId = com.android.internal.R.dimen.recents_thumbnail_height; + int wId = com.android.internal.R.dimen.thumbnail_width; + int hId = com.android.internal.R.dimen.thumbnail_height; mThumbnailWidth = w = res.getDimensionPixelSize(wId); mThumbnailHeight = h = res.getDimensionPixelSize(hId); } else { diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 7617886..d08978b 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -189,15 +189,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { final protected SharedElementListener mListener; protected ResultReceiver mResultReceiver; final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + final protected boolean mIsReturning; public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, ArrayList<String> accepted, ArrayList<String> localNames, - SharedElementListener listener) { + SharedElementListener listener, boolean isReturning) { super(new Handler()); mWindow = window; mListener = listener; mAllSharedElementNames = allSharedElementNames; + mIsReturning = isReturning; setSharedElements(accepted, localNames); if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1634d11..ff8688d 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -133,10 +133,12 @@ import android.view.textservice.TextServicesManager; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.admin.DevicePolicyManager; +import android.app.task.ITaskManager; import android.app.trust.TrustManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; +import com.android.internal.appwidget.IAppWidgetService.Stub; import com.android.internal.os.IDropBoxManagerService; import java.io.File; @@ -693,6 +695,12 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { return new UsageStatsManager(ctx.getOuterContext()); }}); + + registerService(TASK_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(TASK_SERVICE); + return new TaskManagerImpl(ITaskManager.Stub.asInterface(b)); + }}); } static ContextImpl getImpl(Context context) { diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 4cca355..bc97852 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -55,16 +55,14 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mHasStopped; private Handler mHandler; private boolean mIsCanceled; - private boolean mIsReturning; private ObjectAnimator mBackgroundAnimator; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, ArrayList<String> acceptedNames, ArrayList<String> mappedNames) { super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames, - getListener(activity, acceptedNames)); + getListener(activity, acceptedNames), acceptedNames != null); mActivity = activity; - mIsReturning = acceptedNames != null; setResultReceiver(resultReceiver); prepareEnter(); Bundle resultReceiverBundle = new Bundle(); diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index f36c36a..93eb53e 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -58,16 +58,14 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private Handler mHandler; - private boolean mIsReturning; - private ObjectAnimator mBackgroundAnimator; private boolean mIsHidden; public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { - super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning)); - mIsReturning = isReturning; + super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), + isReturning); mIsBackgroundReady = !isReturning; mActivity = activity; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dfd927f..90aeaae 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; +import android.view.Gravity; import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -46,7 +47,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; /** * A class that represents how a persistent notification is to be presented to @@ -767,7 +770,7 @@ public class Notification implements Parcelable */ public static class Action implements Parcelable { private final Bundle mExtras; - private RemoteInput[] mRemoteInputs; + private final RemoteInput[] mRemoteInputs; /** * Small icon representing the action. @@ -910,25 +913,12 @@ public class Notification implements Parcelable * Apply an extender to this action builder. Extenders may be used to add * metadata or change options on this builder. */ - public Builder apply(Extender extender) { - extender.applyTo(this); + public Builder extend(Extender extender) { + extender.extend(this); return this; } /** - * Extender interface for use with {@link #apply}. Extenders may be used to add - * metadata or change options on this builder. - */ - public interface Extender { - /** - * Apply this extender to a notification action builder. - * @param builder the builder to be modified. - * @return the build object for chaining. - */ - public Builder applyTo(Builder builder); - } - - /** * Combine all of the options that have been set and return a new {@link Action} * object. * @return the built action @@ -975,6 +965,120 @@ public class Notification implements Parcelable return new Action[size]; } }; + + /** + * Extender interface for use with {@link Builder#extend}. Extenders may be used to add + * metadata or change options on an action builder. + */ + public interface Extender { + /** + * Apply this extender to a notification action builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder extend(Builder builder); + } + + /** + * Wearable extender for notification actions. To add extensions to an action, + * create a new {@link android.app.Notification.Action.WearableExtender} object using + * the {@code WearableExtender()} constructor and apply it to a + * {@link android.app.Notification.Action.Builder} using + * {@link android.app.Notification.Action.Builder#extend}. + * + * <pre class="prettyprint"> + * Notification.Action action = new Notification.Action.Builder( + * R.drawable.archive_all, "Archive all", actionIntent) + * .extend(new Notification.Action.WearableExtender() + * .setAvailableOffline(false)) + * .build();</pre> + */ + public static final class WearableExtender implements Extender { + /** Notification action extra which contains wearable extensions */ + private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; + + private static final String KEY_FLAGS = "flags"; + + // Flags bitwise-ored to mFlags + private static final int FLAG_AVAILABLE_OFFLINE = 0x1; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; + + private int mFlags = DEFAULT_FLAGS; + + /** + * Create a {@link android.app.Notification.Action.WearableExtender} with default + * options. + */ + public WearableExtender() { + } + + /** + * Create a {@link android.app.Notification.Action.WearableExtender} by reading + * wearable options present in an existing notification action. + * @param action the notification action to inspect. + */ + public WearableExtender(Action action) { + Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); + if (wearableBundle != null) { + mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); + } + } + + /** + * Apply wearable extensions to a notification action that is being built. This is + * typically called by the {@link android.app.Notification.Action.Builder#extend} + * method of {@link android.app.Notification.Action.Builder}. + */ + @Override + public Action.Builder extend(Action.Builder builder) { + Bundle wearableBundle = new Bundle(); + + if (mFlags != DEFAULT_FLAGS) { + wearableBundle.putInt(KEY_FLAGS, mFlags); + } + + builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); + return builder; + } + + @Override + public WearableExtender clone() { + WearableExtender that = new WearableExtender(); + that.mFlags = this.mFlags; + return that; + } + + /** + * Set whether this action is available when the wearable device is not connected to + * a companion device. The user can still trigger this action when the wearable device is + * offline, but a visual hint will indicate that the action may not be available. + * Defaults to true. + */ + public WearableExtender setAvailableOffline(boolean availableOffline) { + setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); + return this; + } + + /** + * Get whether this action is available when the wearable device is not connected to + * a companion device. The user can still trigger this action when the wearable device is + * offline, but a visual hint will indicate that the action may not be available. + * Defaults to true. + */ + public boolean isAvailableOffline() { + return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; + } + + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + } } /** @@ -2169,24 +2273,11 @@ public class Notification implements Parcelable * Apply an extender to this notification builder. Extenders may be used to add * metadata or change options on this builder. */ - public Builder apply(Extender extender) { - extender.applyTo(this); + public Builder extend(Extender extender) { + extender.extend(this); return this; } - /** - * Extender interface for use with {@link #apply}. Extenders may be used to add - * metadata or change options on this builder. - */ - public interface Extender { - /** - * Apply this extender to a notification builder. - * @param builder the builder to be modified. - * @return the build object for chaining. - */ - public Builder applyTo(Builder builder); - } - private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; @@ -3163,4 +3254,670 @@ public class Notification implements Parcelable return big; } } + + /** + * Extender interface for use with {@link Builder#extend}. Extenders may be used to add + * metadata or change options on a notification builder. + */ + public interface Extender { + /** + * Apply this extender to a notification builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder extend(Builder builder); + } + + /** + * Helper class to add wearable extensions to notifications. + * <p class="note"> See + * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications + * for Android Wear</a> for more information on how to use this class. + * <p> + * To create a notification with wearable extensions: + * <ol> + * <li>Create a {@link android.app.Notification.Builder}, setting any desired + * properties. + * <li>Create a {@link android.app.Notification.WearableExtender}. + * <li>Set wearable-specific properties using the + * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. + * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a + * notification. + * <li>Post the notification to the notification system with the + * {@code NotificationManager.notify(...)} methods. + * </ol> + * + * <pre class="prettyprint"> + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New mail from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .extend(new Notification.WearableExtender() + * .setContentIcon(R.drawable.new_mail)) + * .build(); + * NotificationManager notificationManger = + * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + * notificationManger.notify(0, notif);</pre> + * + * <p>Wearable extensions can be accessed on an existing notification by using the + * {@code WearableExtender(Notification)} constructor, + * and then using the {@code get} methods to access values. + * + * <pre class="prettyprint"> + * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( + * notification); + * List<Notification> pages = wearableExtender.getPages();</pre> + */ + public static final class WearableExtender implements Extender { + /** + * Sentinel value for an action index that is unset. + */ + public static final int UNSET_ACTION_INDEX = -1; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification with + * default sizing. + * <p>For custom display notifications created using {@link #setDisplayIntent}, + * the default is {@link #SIZE_LARGE}. All other notifications size automatically based + * on their content. + */ + public static final int SIZE_DEFAULT = 0; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with an extra small size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_XSMALL = 1; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a small size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_SMALL = 2; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a medium size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_MEDIUM = 3; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a large size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_LARGE = 4; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * full screen. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_FULL_SCREEN = 5; + + /** Notification extra which contains wearable extensions */ + private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; + + // Keys within EXTRA_WEARABLE_OPTIONS for wearable options. + private static final String KEY_ACTIONS = "actions"; + private static final String KEY_FLAGS = "flags"; + private static final String KEY_DISPLAY_INTENT = "displayIntent"; + private static final String KEY_PAGES = "pages"; + private static final String KEY_BACKGROUND = "background"; + private static final String KEY_CONTENT_ICON = "contentIcon"; + private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; + private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; + private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; + private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; + private static final String KEY_GRAVITY = "gravity"; + + // Flags bitwise-ored to mFlags + private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; + private static final int FLAG_HINT_HIDE_ICON = 1 << 1; + private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; + private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; + + private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; + private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; + + private ArrayList<Action> mActions = new ArrayList<Action>(); + private int mFlags = DEFAULT_FLAGS; + private PendingIntent mDisplayIntent; + private ArrayList<Notification> mPages = new ArrayList<Notification>(); + private Bitmap mBackground; + private int mContentIcon; + private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; + private int mContentActionIndex = UNSET_ACTION_INDEX; + private int mCustomSizePreset = SIZE_DEFAULT; + private int mCustomContentHeight; + private int mGravity = DEFAULT_GRAVITY; + + /** + * Create a {@link android.app.Notification.WearableExtender} with default + * options. + */ + public WearableExtender() { + } + + public WearableExtender(Notification notif) { + Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); + if (wearableBundle != null) { + List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); + if (actions != null) { + mActions.addAll(actions); + } + + mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); + mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); + + Notification[] pages = getNotificationArrayFromBundle( + wearableBundle, KEY_PAGES); + if (pages != null) { + Collections.addAll(mPages, pages); + } + + mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); + mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); + mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, + DEFAULT_CONTENT_ICON_GRAVITY); + mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, + UNSET_ACTION_INDEX); + mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, + SIZE_DEFAULT); + mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); + mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); + } + } + + /** + * Apply wearable extensions to a notification that is being built. This is typically + * called by the {@link android.app.Notification.Builder#extend} method of + * {@link android.app.Notification.Builder}. + */ + @Override + public Notification.Builder extend(Notification.Builder builder) { + Bundle wearableBundle = new Bundle(); + + if (!mActions.isEmpty()) { + wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); + } + if (mFlags != DEFAULT_FLAGS) { + wearableBundle.putInt(KEY_FLAGS, mFlags); + } + if (mDisplayIntent != null) { + wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); + } + if (!mPages.isEmpty()) { + wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( + new Notification[mPages.size()])); + } + if (mBackground != null) { + wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); + } + if (mContentIcon != 0) { + wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); + } + if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { + wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); + } + if (mContentActionIndex != UNSET_ACTION_INDEX) { + wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, + mContentActionIndex); + } + if (mCustomSizePreset != SIZE_DEFAULT) { + wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); + } + if (mCustomContentHeight != 0) { + wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); + } + if (mGravity != DEFAULT_GRAVITY) { + wearableBundle.putInt(KEY_GRAVITY, mGravity); + } + + builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); + return builder; + } + + @Override + public WearableExtender clone() { + WearableExtender that = new WearableExtender(); + that.mActions = new ArrayList<Action>(this.mActions); + that.mFlags = this.mFlags; + that.mDisplayIntent = this.mDisplayIntent; + that.mPages = new ArrayList<Notification>(this.mPages); + that.mBackground = this.mBackground; + that.mContentIcon = this.mContentIcon; + that.mContentIconGravity = this.mContentIconGravity; + that.mContentActionIndex = this.mContentActionIndex; + that.mCustomSizePreset = this.mCustomSizePreset; + that.mCustomContentHeight = this.mCustomContentHeight; + that.mGravity = this.mGravity; + return that; + } + + /** + * Add a wearable action to this notification. + * + * <p>When wearable actions are added using this method, the set of actions that + * show on a wearable device splits from devices that only show actions added + * using {@link android.app.Notification.Builder#addAction}. This allows for customization + * of which actions display on different devices. + * + * @param action the action to add to this notification + * @return this object for method chaining + * @see android.app.Notification.Action + */ + public WearableExtender addAction(Action action) { + mActions.add(action); + return this; + } + + /** + * Adds wearable actions to this notification. + * + * <p>When wearable actions are added using this method, the set of actions that + * show on a wearable device splits from devices that only show actions added + * using {@link android.app.Notification.Builder#addAction}. This allows for customization + * of which actions display on different devices. + * + * @param actions the actions to add to this notification + * @return this object for method chaining + * @see android.app.Notification.Action + */ + public WearableExtender addActions(List<Action> actions) { + mActions.addAll(actions); + return this; + } + + /** + * Clear all wearable actions present on this builder. + * @return this object for method chaining. + * @see #addAction + */ + public WearableExtender clearActions() { + mActions.clear(); + return this; + } + + /** + * Get the wearable actions present on this notification. + */ + public List<Action> getActions() { + return mActions; + } + + /** + * Set an intent to launch inside of an activity view when displaying + * this notification. The {@link PendingIntent} provided should be for an activity. + * + * <pre class="prettyprint"> + * Intent displayIntent = new Intent(context, MyDisplayActivity.class); + * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, + * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); + * Notification notif = new Notification.Builder(context) + * .extend(new Notification.WearableExtender() + * .setDisplayIntent(displayPendingIntent) + * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) + * .build();</pre> + * + * <p>The activity to launch needs to allow embedding, must be exported, and + * should have an empty task affinity. + * + * <p>Example AndroidManifest.xml entry: + * <pre class="prettyprint"> + * <activity android:name="com.example.MyDisplayActivity" + * android:exported="true" + * android:allowEmbedded="true" + * android:taskAffinity="" /></pre> + * + * @param intent the {@link PendingIntent} for an activity + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getDisplayIntent + */ + public WearableExtender setDisplayIntent(PendingIntent intent) { + mDisplayIntent = intent; + return this; + } + + /** + * Get the intent to launch inside of an activity view when displaying this + * notification. This {@code PendingIntent} should be for an activity. + */ + public PendingIntent getDisplayIntent() { + return mDisplayIntent; + } + + /** + * Add an additional page of content to display with this notification. The current + * notification forms the first page, and pages added using this function form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * + * @param page the notification to add as another page + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getPages + */ + public WearableExtender addPage(Notification page) { + mPages.add(page); + return this; + } + + /** + * Add additional pages of content to display with this notification. The current + * notification forms the first page, and pages added using this function form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * + * @param pages a list of notifications + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getPages + */ + public WearableExtender addPages(List<Notification> pages) { + mPages.addAll(pages); + return this; + } + + /** + * Clear all additional pages present on this builder. + * @return this object for method chaining. + * @see #addPage + */ + public WearableExtender clearPages() { + mPages.clear(); + return this; + } + + /** + * Get the array of additional pages of content for displaying this notification. The + * current notification forms the first page, and elements within this array form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * @return the pages for this notification + */ + public List<Notification> getPages() { + return mPages; + } + + /** + * Set a background image to be displayed behind the notification content. + * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background + * will work with any notification style. + * + * @param background the background bitmap + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getBackground + */ + public WearableExtender setBackground(Bitmap background) { + mBackground = background; + return this; + } + + /** + * Get a background image to be displayed behind the notification content. + * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background + * will work with any notification style. + * + * @return the background image + * @see android.app.Notification.WearableExtender#setBackground + */ + public Bitmap getBackground() { + return mBackground; + } + + /** + * Set an icon that goes with the content of this notification. + */ + public WearableExtender setContentIcon(int icon) { + mContentIcon = icon; + return this; + } + + /** + * Get an icon that goes with the content of this notification. + */ + public int getContentIcon() { + return mContentIcon; + } + + /** + * Set the gravity that the content icon should have within the notification display. + * Supported values include {@link android.view.Gravity#START} and + * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. + * @see #setContentIcon + */ + public WearableExtender setContentIconGravity(int contentIconGravity) { + mContentIconGravity = contentIconGravity; + return this; + } + + /** + * Get the gravity that the content icon should have within the notification display. + * Supported values include {@link android.view.Gravity#START} and + * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. + * @see #getContentIcon + */ + public int getContentIconGravity() { + return mContentIconGravity; + } + + /** + * Set an action from this notification's actions to be clickable with the content of + * this notification. This action will no longer display separately from the + * notification's content. + * + * <p>For notifications with multiple pages, child pages can also have content actions + * set, although the list of available actions comes from the main notification and not + * from the child page's notification. + * + * @param actionIndex The index of the action to hoist onto the current notification page. + * If wearable actions were added to the main notification, this index + * will apply to that list, otherwise it will apply to the regular + * actions list. + */ + public WearableExtender setContentAction(int actionIndex) { + mContentActionIndex = actionIndex; + return this; + } + + /** + * Get the index of the notification action, if any, that was specified as being clickable + * with the content of this notification. This action will no longer display separately + * from the notification's content. + * + * <p>For notifications with multiple pages, child pages can also have content actions + * set, although the list of available actions comes from the main notification and not + * from the child page's notification. + * + * <p>If wearable specific actions were added to the main notification, this index will + * apply to that list, otherwise it will apply to the regular actions list. + * + * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. + */ + public int getContentAction() { + return mContentActionIndex; + } + + /** + * Set the gravity that this notification should have within the available viewport space. + * Supported values include {@link android.view.Gravity#TOP}, + * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. + * The default value is {@link android.view.Gravity#BOTTOM}. + */ + public WearableExtender setGravity(int gravity) { + mGravity = gravity; + return this; + } + + /** + * Get the gravity that this notification should have within the available viewport space. + * Supported values include {@link android.view.Gravity#TOP}, + * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. + * The default value is {@link android.view.Gravity#BOTTOM}. + */ + public int getGravity() { + return mGravity; + } + + /** + * Set the custom size preset for the display of this notification out of the available + * presets found in {@link android.app.Notification.WearableExtender}, e.g. + * {@link #SIZE_LARGE}. + * <p>Some custom size presets are only applicable for custom display notifications created + * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the + * documentation for the preset in question. See also + * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. + */ + public WearableExtender setCustomSizePreset(int sizePreset) { + mCustomSizePreset = sizePreset; + return this; + } + + /** + * Get the custom size preset for the display of this notification out of the available + * presets found in {@link android.app.Notification.WearableExtender}, e.g. + * {@link #SIZE_LARGE}. + * <p>Some custom size presets are only applicable for custom display notifications created + * using {@link #setDisplayIntent}. Check the documentation for the preset in question. + * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. + */ + public int getCustomSizePreset() { + return mCustomSizePreset; + } + + /** + * Set the custom height in pixels for the display of this notification's content. + * <p>This option is only available for custom display notifications created + * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also + * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and + * {@link #getCustomContentHeight}. + */ + public WearableExtender setCustomContentHeight(int height) { + mCustomContentHeight = height; + return this; + } + + /** + * Get the custom height in pixels for the display of this notification's content. + * <p>This option is only available for custom display notifications created + * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and + * {@link #setCustomContentHeight}. + */ + public int getCustomContentHeight() { + return mCustomContentHeight; + } + + /** + * Set whether the scrolling position for the contents of this notification should start + * at the bottom of the contents instead of the top when the contents are too long to + * display within the screen. Default is false (start scroll at the top). + */ + public WearableExtender setStartScrollBottom(boolean startScrollBottom) { + setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); + return this; + } + + /** + * Get whether the scrolling position for the contents of this notification should start + * at the bottom of the contents instead of the top when the contents are too long to + * display within the screen. Default is false (start scroll at the top). + */ + public boolean getStartScrollBottom() { + return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; + } + + /** + * Set whether the content intent is available when the wearable device is not connected + * to a companion device. The user can still trigger this intent when the wearable device + * is offline, but a visual hint will indicate that the content intent may not be available. + * Defaults to true. + */ + public WearableExtender setContentIntentAvailableOffline( + boolean contentIntentAvailableOffline) { + setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); + return this; + } + + /** + * Get whether the content intent is available when the wearable device is not connected + * to a companion device. The user can still trigger this intent when the wearable device + * is offline, but a visual hint will indicate that the content intent may not be available. + * Defaults to true. + */ + public boolean getContentIntentAvailableOffline() { + return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; + } + + /** + * Set a hint that this notification's icon should not be displayed. + * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. + * @return this object for method chaining + */ + public WearableExtender setHintHideIcon(boolean hintHideIcon) { + setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); + return this; + } + + /** + * Get a hint that this notification's icon should not be displayed. + * @return {@code true} if this icon should not be displayed, false otherwise. + * The default value is {@code false} if this was never set. + */ + public boolean getHintHideIcon() { + return (mFlags & FLAG_HINT_HIDE_ICON) != 0; + } + + /** + * Set a visual hint that only the background image of this notification should be + * displayed, and other semantic content should be hidden. This hint is only applicable + * to sub-pages added using {@link #addPage}. + */ + public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { + setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); + return this; + } + + /** + * Get a visual hint that only the background image of this notification should be + * displayed, and other semantic content should be hidden. This hint is only applicable + * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. + */ + public boolean getHintShowBackgroundOnly() { + return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; + } + + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + } + + /** + * Get an array of Notification objects from a parcelable array bundle field. + * Update the bundle to have a typed array so fetches in the future don't need + * to do an array copy. + */ + private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { + Parcelable[] array = bundle.getParcelableArray(key); + if (array instanceof Notification[] || array == null) { + return (Notification[]) array; + } + Notification[] typedArray = Arrays.copyOf(array, array.length, + Notification[].class); + bundle.putParcelableArray(key, typedArray); + return typedArray; + } } diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 9cfc541..11420c5 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -64,18 +64,24 @@ public final class RemoteInput implements Parcelable { /** Extra added to a clip data intent object to hold the results bundle. */ public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; + // Flags bitwise-ored to mFlags + private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_ALLOW_FREE_FORM_INPUT; + private final String mResultKey; private final CharSequence mLabel; private final CharSequence[] mChoices; - private final boolean mAllowFreeFormInput; + private final int mFlags; private final Bundle mExtras; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - boolean allowFreeFormInput, Bundle extras) { + int flags, Bundle extras) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; - this.mAllowFreeFormInput = allowFreeFormInput; + this.mFlags = flags; this.mExtras = extras; } @@ -108,7 +114,7 @@ public final class RemoteInput implements Parcelable { * if you set this to false and {@link #getChoices} returns {@code null} or empty. */ public boolean getAllowFreeFormInput() { - return mAllowFreeFormInput; + return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0; } /** @@ -125,7 +131,7 @@ public final class RemoteInput implements Parcelable { private final String mResultKey; private CharSequence mLabel; private CharSequence[] mChoices; - private boolean mAllowFreeFormInput = true; + private int mFlags = DEFAULT_FLAGS; private Bundle mExtras = new Bundle(); /** @@ -178,7 +184,7 @@ public final class RemoteInput implements Parcelable { * @return this object for method chaining */ public Builder setAllowFreeFormInput(boolean allowFreeFormInput) { - mAllowFreeFormInput = allowFreeFormInput; + setFlag(mFlags, allowFreeFormInput); return this; } @@ -205,12 +211,20 @@ public final class RemoteInput implements Parcelable { return mExtras; } + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + /** * Combine all of the options that have been set and return a new {@link RemoteInput} * object. */ public RemoteInput build() { - return new RemoteInput(mResultKey, mLabel, mChoices, mAllowFreeFormInput, mExtras); + return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras); } } @@ -218,7 +232,7 @@ public final class RemoteInput implements Parcelable { mResultKey = in.readString(); mLabel = in.readCharSequence(); mChoices = in.readCharSequenceArray(); - mAllowFreeFormInput = in.readInt() != 0; + mFlags = in.readInt(); mExtras = in.readBundle(); } @@ -279,7 +293,7 @@ public final class RemoteInput implements Parcelable { out.writeString(mResultKey); out.writeCharSequence(mLabel); out.writeCharSequenceArray(mChoices); - out.writeInt(mAllowFreeFormInput ? 1 : 0); + out.writeInt(mFlags); out.writeBundle(mExtras); } diff --git a/core/java/android/app/TaskManagerImpl.java b/core/java/android/app/TaskManagerImpl.java new file mode 100644 index 0000000..f42839e --- /dev/null +++ b/core/java/android/app/TaskManagerImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// in android.app so ContextImpl has package access +package android.app; + +import android.app.task.ITaskManager; +import android.app.task.Task; +import android.app.task.TaskManager; + +import java.util.List; + + +/** + * Concrete implementation of the TaskManager interface + * @hide + */ +public class TaskManagerImpl extends TaskManager { + ITaskManager mBinder; + + /* package */ TaskManagerImpl(ITaskManager binder) { + mBinder = binder; + } + + @Override + public int schedule(Task task) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void cancel(int taskId) { + // TODO Auto-generated method stub + + } + + @Override + public void cancelAll() { + // TODO Auto-generated method stub + + } + + @Override + public List<Task> getAllPendingTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/core/java/android/app/task/ITaskManager.aidl b/core/java/android/app/task/ITaskManager.aidl new file mode 100644 index 0000000..b56c78a --- /dev/null +++ b/core/java/android/app/task/ITaskManager.aidl @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.task; + +import android.app.task.Task; + + /** + * IPC interface that supports the app-facing {@link #TaskManager} api. + * {@hide} + */ +interface ITaskManager { + int schedule(in Task task); + void cancel(int taskId); + void cancelAll(); + List<Task> getAllPendingTasks(); +} diff --git a/core/java/android/app/task/Task.aidl b/core/java/android/app/task/Task.aidl new file mode 100644 index 0000000..1f25439 --- /dev/null +++ b/core/java/android/app/task/Task.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.task; + +parcelable Task; +
\ No newline at end of file diff --git a/core/java/android/content/Task.java b/core/java/android/app/task/Task.java index 407880f..dd184a5 100644 --- a/core/java/android/content/Task.java +++ b/core/java/android/app/task/Task.java @@ -14,15 +14,15 @@ * limitations under the License */ -package android.content; +package android.app.task; -import android.app.task.TaskService; +import android.content.ComponentName; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; /** - * Container of data passed to the {@link android.content.TaskManager} fully encapsulating the + * Container of data passed to the {@link android.app.task.TaskManager} fully encapsulating the * parameters required to schedule work against the calling application. These are constructed * using the {@link Task.Builder}. */ @@ -92,7 +92,7 @@ public class Task implements Parcelable { } /** - * See {@link android.content.Task.NetworkType} for a description of this value. + * See {@link android.app.task.Task.NetworkType} for a description of this value. */ public int getNetworkCapabilities() { return networkCapabilities; @@ -139,7 +139,7 @@ public class Task implements Parcelable { } /** - * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field + * See {@link android.app.task.Task.BackoffPolicy} for an explanation of the values this field * can take. This defaults to exponential. */ public int getBackoffPolicy() { @@ -255,7 +255,7 @@ public class Task implements Parcelable { /** * Set some description of the kind of network capabilities you would like to have. This - * will be a parameter defined in {@link android.content.Task.NetworkType}. + * will be a parameter defined in {@link android.app.task.Task.NetworkType}. * Not calling this function means the network is not necessary. * Bear in mind that calling this function defines network as a strict requirement for your * task if the network requested is not available your task will never run. See @@ -314,7 +314,7 @@ public class Task implements Parcelable { * Specify that this task should be delayed by the provided amount of time. * Because it doesn't make sense setting this property on a periodic task, doing so will * throw an {@link java.lang.IllegalArgumentException} when - * {@link android.content.Task.Builder#build()} is called. + * {@link android.app.task.Task.Builder#build()} is called. * @param minLatencyMillis Milliseconds before which this task will not be considered for * execution. */ @@ -328,7 +328,7 @@ public class Task implements Parcelable { * deadline even if other requirements are not met. Because it doesn't make sense setting * this property on a periodic task, doing so will throw an * {@link java.lang.IllegalArgumentException} when - * {@link android.content.Task.Builder#build()} is called. + * {@link android.app.task.Task.Builder#build()} is called. */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { mMaxExecutionDelayMillis = maxExecutionDelayMillis; diff --git a/core/java/android/content/TaskManager.java b/core/java/android/app/task/TaskManager.java index d28d78a..0fbe37d 100644 --- a/core/java/android/content/TaskManager.java +++ b/core/java/android/app/task/TaskManager.java @@ -14,14 +14,19 @@ * limitations under the License */ -package android.content; +package android.app.task; import java.util.List; +import android.content.Context; + /** * Class for scheduling various types of tasks with the scheduling framework on the device. * - * Get an instance of this class through {@link Context#getSystemService(String)}. + * <p>You do not + * instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * Context.getSystemService(Context.TASK_SERVICE)}. */ public abstract class TaskManager { /* @@ -29,18 +34,20 @@ public abstract class TaskManager { * if the run-time for your task is too short, or perhaps the system can't resolve the * requisite {@link TaskService} in your package. */ - static final int RESULT_INVALID_PARAMETERS = -1; + public static final int RESULT_INVALID_PARAMETERS = -1; + /** * Returned from {@link #schedule(Task)} if this application has made too many requests for * work over too short a time. */ // TODO: Determine if this is necessary. - static final int RESULT_OVER_QUOTA = -2; + public static final int RESULT_OVER_QUOTA = -2; - /* - * @param task The task you wish scheduled. See {@link Task#TaskBuilder} for more detail on - * the sorts of tasks you can schedule. - * @return If >0, this int corresponds to the taskId of the successfully scheduled task. + /** + * @param task The task you wish scheduled. See + * {@link android.app.task.Task.Builder Task.Builder} for more detail on the sorts of tasks + * you can schedule. + * @return If >0, this int returns the taskId of the successfully scheduled task. * Otherwise you have to compare the return value to the error codes defined in this class. */ public abstract int schedule(Task task); diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java index 0351082..dacb348 100644 --- a/core/java/android/app/task/TaskParams.java +++ b/core/java/android/app/task/TaskParams.java @@ -47,7 +47,7 @@ public class TaskParams implements Parcelable { /** * @return The extras you passed in when constructing this task with - * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will + * {@link android.app.task.Task.Builder#setExtras(android.os.Bundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. */ public Bundle getExtras() { diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java index ab1a565..8ce4484 100644 --- a/core/java/android/app/task/TaskService.java +++ b/core/java/android/app/task/TaskService.java @@ -28,7 +28,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; /** - * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p> + * <p>Entry point for the callback from the {@link android.app.task.TaskManager}.</p> * <p>This is the base class that handles asynchronous requests that were previously scheduled. You * are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where * you will implement your task logic.</p> @@ -215,9 +215,9 @@ public abstract class TaskService extends Service { * * <p>This will happen if the requirements specified at schedule time are no longer met. For * example you may have requested WiFi with - * {@link android.content.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your + * {@link android.app.task.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your * task was executing the user toggled WiFi. Another example is if you had specified - * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its + * {@link android.app.task.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its * idle maintenance window. You are solely responsible for the behaviour of your application * upon receipt of this message; your app will likely start to misbehave if you ignore it. One * immediate repercussion is that the system will cease holding a wakelock for you.</p> @@ -237,7 +237,7 @@ public abstract class TaskService extends Service { * You can specify post-execution behaviour to the scheduler here with * <code>needsReschedule </code>. This will apply a back-off timer to your task based on * the default, or what was set with - * {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The original + * {@link android.app.task.Task.Builder#setBackoffCriteria(long, int)}. The original * requirements are always honoured even for a backed-off task. Note that a task running in * idle mode will not be backed-off. Instead what will happen is the task will be re-added * to the queue and re-executed within a future idle maintenance window. diff --git a/core/java/android/app/wearable/WearableActionExtensions.java b/core/java/android/app/wearable/WearableActionExtensions.java deleted file mode 100644 index c296ef2..0000000 --- a/core/java/android/app/wearable/WearableActionExtensions.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.wearable; - -import android.app.Notification; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Wearable extensions to notification actions. To add extensions to an action, - * create a new {@link WearableActionExtensions} object using - * {@link WearableActionExtensions.Builder} and apply it to a - * {@link android.app.Notification.Action.Builder}. - * - * <pre class="prettyprint"> - * Notification.Action action = new Notification.Action.Builder( - * R.drawable.archive_all, "Archive all", actionIntent) - * .apply(new WearableActionExtensions.Builder() - * .setAvailableOffline(false) - * .build()) - * .build(); - * </pre> - */ -public final class WearableActionExtensions implements Notification.Action.Builder.Extender, - Parcelable { - /** Notification action extra which contains wearable extensions */ - private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; - - // Flags bitwise-ored to mFlags - private static final int FLAG_AVAILABLE_OFFLINE = 1 << 0; - - // Default value for flags integer - private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; - - private final int mFlags; - - private WearableActionExtensions(int flags) { - mFlags = flags; - } - - private WearableActionExtensions(Parcel in) { - mFlags = in.readInt(); - } - - /** - * Create a {@link WearableActionExtensions} by reading wearable extensions present on an - * existing notification action. - * @param action the notification action to inspect. - * @return a new {@link WearableActionExtensions} object. - */ - public static WearableActionExtensions from(Notification.Action action) { - WearableActionExtensions extensions = action.getExtras().getParcelable( - EXTRA_WEARABLE_EXTENSIONS); - if (extensions != null) { - return extensions; - } else { - // Return a WearableActionExtensions with default values. - return new Builder().build(); - } - } - - /** - * Get whether this action is available when the wearable device is not connected to - * a companion device. The user can still trigger this action when the wearable device is - * offline, but a visual hint will indicate that the action may not be available. - * Defaults to true. - */ - public boolean isAvailableOffline() { - return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; - } - - @Override - public Notification.Action.Builder applyTo(Notification.Action.Builder builder) { - builder.getExtras().putParcelable(EXTRA_WEARABLE_EXTENSIONS, this); - return builder; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mFlags); - } - - /** - * Builder for {@link WearableActionExtensions} objects, which adds wearable extensions to - * notification actions. To extend an action, create an instance of this class, call the set - * methods present, call {@link #build}, and finally apply the options to a - * {@link Notification.Action.Builder} using its - * {@link android.app.Notification.Action.Builder#apply} method. - */ - public static final class Builder { - private int mFlags = DEFAULT_FLAGS; - - /** - * Construct a builder to be used for adding wearable extensions to notification actions. - * - * <pre class="prettyprint"> - * Notification.Action action = new Notification.Action.Builder( - * R.drawable.archive_all, "Archive all", actionIntent) - * .apply(new WearableActionExtensions.Builder() - * .setAvailableOffline(false) - * .build()) - * .build();</pre> - */ - public Builder() { - } - - /** - * Create a {@link Builder} by reading wearable extensions present on an - * existing {@code WearableActionExtensions} object. - * @param other the existing extensions to inspect. - */ - public Builder(WearableActionExtensions other) { - mFlags = other.mFlags; - } - - /** - * Set whether this action is available when the wearable device is not connected to - * a companion device. The user can still trigger this action when the wearable device is - * offline, but a visual hint will indicate that the action may not be available. - * Defaults to true. - */ - public Builder setAvailableOffline(boolean availableOffline) { - setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); - return this; - } - - /** - * Build a new {@link WearableActionExtensions} object with the extensions - * currently present on this builder. - * @return the extensions object. - */ - public WearableActionExtensions build() { - return new WearableActionExtensions(mFlags); - } - - private void setFlag(int mask, boolean value) { - if (value) { - mFlags |= mask; - } else { - mFlags &= ~mask; - } - } - } - - public static final Creator<WearableActionExtensions> CREATOR = - new Creator<WearableActionExtensions>() { - @Override - public WearableActionExtensions createFromParcel(Parcel in) { - return new WearableActionExtensions(in); - } - - @Override - public WearableActionExtensions[] newArray(int size) { - return new WearableActionExtensions[size]; - } - }; -} diff --git a/core/java/android/app/wearable/WearableNotificationExtensions.java b/core/java/android/app/wearable/WearableNotificationExtensions.java deleted file mode 100644 index d433613..0000000 --- a/core/java/android/app/wearable/WearableNotificationExtensions.java +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.wearable; - -import android.app.Notification; -import android.app.PendingIntent; -import android.graphics.Bitmap; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.Gravity; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Helper class that contains wearable extensions for notifications. - * <p class="note"> See - * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications - * for Android Wear</a> for more information on how to use this class. - * <p> - * To create a notification with wearable extensions: - * <ol> - * <li>Create a {@link Notification.Builder}, setting any desired - * properties. - * <li>Create a {@link WearableNotificationExtensions.Builder}. - * <li>Set wearable-specific properties using the - * {@code add} and {@code set} methods of {@link WearableNotificationExtensions.Builder}. - * <li>Call {@link WearableNotificationExtensions.Builder#build} to build the extensions - * object. - * <li>Call {@link Notification.Builder#apply} to apply the extensions to a notification. - * <li>Post the notification to the notification system with the - * {@code NotificationManager.notify(...)} methods. - * </ol> - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .apply(new new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManger.notify(0, notif);</pre> - * - * <p>Wearable extensions can be accessed on an existing notification by using the - * {@link WearableNotificationExtensions#from} function. - * - * <pre class="prettyprint"> - * WearableNotificationExtensions wearableExtensions = WearableNotificationExtensions.from( - * notification); - * Notification[] pages = wearableExtensions.getPages(); - * </pre> - */ -public final class WearableNotificationExtensions implements Notification.Builder.Extender, - Parcelable { - /** - * Sentinel value for an action index that is unset. - */ - public static final int UNSET_ACTION_INDEX = -1; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification with - * default sizing. - * <p>For custom display notifications created using {@link Builder#setDisplayIntent}, - * the default is {@link #SIZE_LARGE}. All other notifications size automatically based - * on their content. - */ - public static final int SIZE_DEFAULT = 0; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with an extra small size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_XSMALL = 1; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a small size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_SMALL = 2; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a medium size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_MEDIUM = 3; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a large size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_LARGE = 4; - - /** Notification extra which contains wearable extensions */ - static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; - - // Flags bitwise-ored to mFlags - static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 1 << 0; - static final int FLAG_HINT_HIDE_ICON = 1 << 1; - static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; - static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; - - // Default value for flags integer - static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; - - private final Notification.Action[] mActions; - private final int mFlags; - private final PendingIntent mDisplayIntent; - private final Notification[] mPages; - private final Bitmap mBackground; - private final int mContentIcon; - private final int mContentIconGravity; - private final int mContentActionIndex; - private final int mCustomSizePreset; - private final int mCustomContentHeight; - private final int mGravity; - - private WearableNotificationExtensions(Notification.Action[] actions, int flags, - PendingIntent displayIntent, Notification[] pages, Bitmap background, - int contentIcon, int contentIconGravity, int contentActionIndex, - int customSizePreset, int customContentHeight, int gravity) { - mActions = actions; - mFlags = flags; - mDisplayIntent = displayIntent; - mPages = pages; - mBackground = background; - mContentIcon = contentIcon; - mContentIconGravity = contentIconGravity; - mContentActionIndex = contentActionIndex; - mCustomSizePreset = customSizePreset; - mCustomContentHeight = customContentHeight; - mGravity = gravity; - } - - private WearableNotificationExtensions(Parcel in) { - mActions = in.createTypedArray(Notification.Action.CREATOR); - mFlags = in.readInt(); - mDisplayIntent = in.readParcelable(PendingIntent.class.getClassLoader()); - mPages = in.createTypedArray(Notification.CREATOR); - mBackground = in.readParcelable(Bitmap.class.getClassLoader()); - mContentIcon = in.readInt(); - mContentIconGravity = in.readInt(); - mContentActionIndex = in.readInt(); - mCustomSizePreset = in.readInt(); - mCustomContentHeight = in.readInt(); - mGravity = in.readInt(); - } - - /** - * Create a {@link WearableNotificationExtensions} by reading wearable extensions present on an - * existing notification. - * @param notif the notification to inspect. - * @return a new {@link WearableNotificationExtensions} object. - */ - public static WearableNotificationExtensions from(Notification notif) { - WearableNotificationExtensions extensions = notif.extras.getParcelable( - EXTRA_WEARABLE_EXTENSIONS); - if (extensions != null) { - return extensions; - } else { - // Return a WearableNotificationExtensions with default values. - return new Builder().build(); - } - } - - /** - * Apply wearable extensions to a notification that is being built. This is typically - * called by {@link Notification.Builder#apply} method of {@link Notification.Builder}. - */ - @Override - public Notification.Builder applyTo(Notification.Builder builder) { - builder.getExtras().putParcelable(EXTRA_WEARABLE_EXTENSIONS, this); - return builder; - } - - /** - * Get the number of wearable actions present on this notification. - * - * @return the number of wearable actions for this notification - */ - public int getActionCount() { - return mActions.length; - } - - /** - * Get a {@link Notification.Action} for the wearable action at {@code actionIndex}. - * @param actionIndex the index of the desired wearable action - */ - public Notification.Action getAction(int actionIndex) { - return mActions[actionIndex]; - } - - /** - * Get the wearable actions present on this notification. - */ - public Notification.Action[] getActions() { - return mActions; - } - - /** - * Get the intent to launch inside of an activity view when displaying this - * notification. This {@code PendingIntent} should be for an activity. - */ - public PendingIntent getDisplayIntent() { - return mDisplayIntent; - } - - /** - * Get the array of additional pages of content for displaying this notification. The - * current notification forms the first page, and elements within this array form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * @return the pages for this notification - */ - public Notification[] getPages() { - return mPages; - } - - /** - * Get a background image to be displayed behind the notification content. - * Contrary to the {@link Notification.BigPictureStyle}, this background - * will work with any notification style. - * - * @return the background image - * @see Builder#setBackground - */ - public Bitmap getBackground() { - return mBackground; - } - - /** - * Get an icon that goes with the content of this notification. - */ - public int getContentIcon() { - return mContentIcon; - } - - /** - * Get the gravity that the content icon should have within the notification display. - * Supported values include {@link Gravity#START} and {@link Gravity#END}. The default - * value is {@link android.view.Gravity#END}. - * @see #getContentIcon - */ - public int getContentIconGravity() { - return mContentIconGravity; - } - - /** - * Get the action index of an action from this notification to show as clickable with - * the content of this notification page. When the user clicks this notification page, - * this action will trigger. This action will no longer display separately from the - * notification content. The action's icon will display with optional subtext provided - * by the action's title. - * - * <p>If wearable specific actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. - */ - public int getContentAction() { - return mContentActionIndex; - } - - /** - * Get the gravity that this notification should have within the available viewport space. - * Supported values include {@link Gravity#TOP}, {@link Gravity#CENTER_VERTICAL} and - * {@link android.view.Gravity#BOTTOM}. The default value is - * {@link android.view.Gravity#BOTTOM}. - */ - public int getGravity() { - return mGravity; - } - - /** - * Get the custom size preset for the display of this notification out of the available - * presets found in {@link WearableNotificationExtensions}, e.g. {@link #SIZE_LARGE}. - * <p>Some custom size presets are only applicable for custom display notifications created - * using {@link Builder#setDisplayIntent}. Check the documentation for the preset in question. - * See also {@link Builder#setCustomContentHeight} and {@link Builder#setCustomSizePreset}. - */ - public int getCustomSizePreset() { - return mCustomSizePreset; - } - - /** - * Get the custom height in pixels for the display of this notification's content. - * <p>This option is only available for custom display notifications created - * using {@link Builder#setDisplayIntent}. See also {@link Builder#setCustomSizePreset} and - * {@link Builder#setCustomContentHeight}. - */ - public int getCustomContentHeight() { - return mCustomContentHeight; - } - - /** - * Get whether the scrolling position for the contents of this notification should start - * at the bottom of the contents instead of the top when the contents are too long to - * display within the screen. Default is false (start scroll at the top). - */ - public boolean getStartScrollBottom() { - return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; - } - - /** - * Get whether the content intent is available when the wearable device is not connected - * to a companion device. The user can still trigger this intent when the wearable device is - * offline, but a visual hint will indicate that the content intent may not be available. - * Defaults to true. - */ - public boolean getContentIntentAvailableOffline() { - return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; - } - - /** - * Get a hint that this notification's icon should not be displayed. - * @return {@code true} if this icon should not be displayed, false otherwise. - * The default value is {@code false} if this was never set. - */ - public boolean getHintHideIcon() { - return (mFlags & FLAG_HINT_HIDE_ICON) != 0; - } - - /** - * Get a visual hint that only the background image of this notification should be - * displayed, and other semantic content should be hidden. This hint is only applicable - * to sub-pages added using {@link Builder#addPage}. - */ - public boolean getHintShowBackgroundOnly() { - return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeTypedArray(mActions, flags); - out.writeInt(mFlags); - out.writeParcelable(mDisplayIntent, flags); - out.writeTypedArray(mPages, flags); - out.writeParcelable(mBackground, flags); - out.writeInt(mContentIcon); - out.writeInt(mContentIconGravity); - out.writeInt(mContentActionIndex); - out.writeInt(mCustomSizePreset); - out.writeInt(mCustomContentHeight); - out.writeInt(mGravity); - } - - /** - * Builder to apply wearable notification extensions to a {@link Notification.Builder} - * object. - * - * <p>You can chain the "set" methods for this builder in any order, - * but you must call the {@link #build} method and then the {@link Notification.Builder#apply} - * method to apply your extensions to a notification. - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail); - * .apply(new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManager.notify(0, notif);</pre> - */ - public static final class Builder { - private final List<Notification.Action> mActions = - new ArrayList<Notification.Action>(); - private int mFlags = DEFAULT_FLAGS; - private PendingIntent mDisplayIntent; - private final List<Notification> mPages = new ArrayList<Notification>(); - private Bitmap mBackground; - private int mContentIcon; - private int mContentIconGravity = Gravity.END; - private int mContentActionIndex = UNSET_ACTION_INDEX; - private int mCustomContentHeight; - private int mCustomSizePreset = SIZE_DEFAULT; - private int mGravity = Gravity.BOTTOM; - - /** - * Construct a builder to be used for adding wearable extensions to notifications. - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail); - * .apply(new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManager.notify(0, notif);</pre> - */ - public Builder() { - } - - /** - * Create a {@link Builder} by reading wearable extensions present on an - * existing {@code WearableNotificationExtensions} object. - * @param other the existing extensions to inspect. - */ - public Builder(WearableNotificationExtensions other) { - Collections.addAll(mActions, other.mActions); - mFlags = other.mFlags; - mDisplayIntent = other.mDisplayIntent; - Collections.addAll(mPages, other.mPages); - mBackground = other.mBackground; - mContentIcon = other.mContentIcon; - mContentIconGravity = other.mContentIconGravity; - mContentActionIndex = other.mContentActionIndex; - mCustomContentHeight = other.mCustomContentHeight; - mCustomSizePreset = other.mCustomSizePreset; - mGravity = other.mGravity; - } - - /** - * Add a wearable action to this notification. - * - * <p>When wearable actions are added using this method, the set of actions that - * show on a wearable device splits from devices that only show actions added - * using {@link android.app.Notification.Builder#addAction}. This allows for customization - * of which actions display on different devices. - * - * @param action the action to add to this notification - * @return this object for method chaining - * @see Notification.Action - */ - public Builder addAction(Notification.Action action) { - mActions.add(action); - return this; - } - - /** - * Adds wearable actions to this notification. - * - * <p>When wearable actions are added using this method, the set of actions that - * show on a wearable device splits from devices that only show actions added - * using {@link android.app.Notification.Builder#addAction}. This allows for customization - * of which actions display on different devices. - * - * @param actions the actions to add to this notification - * @return this object for method chaining - * @see Notification.Action - */ - public Builder addActions(List<Notification.Action> actions) { - mActions.addAll(actions); - return this; - } - - /** - * Clear all wearable actions present on this builder. - * @return this object for method chaining. - * @see #addAction - */ - public Builder clearActions() { - mActions.clear(); - return this; - } - - /** - * Set an intent to launch inside of an activity view when displaying - * this notification. This {@link android.app.PendingIntent} should be for an activity. - * - * @param intent the {@link android.app.PendingIntent} for an activity - * @return this object for method chaining - * @see WearableNotificationExtensions#getDisplayIntent - */ - public Builder setDisplayIntent(PendingIntent intent) { - mDisplayIntent = intent; - return this; - } - - /** - * Add an additional page of content to display with this notification. The current - * notification forms the first page, and pages added using this function form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * - * @param page the notification to add as another page - * @return this object for method chaining - * @see WearableNotificationExtensions#getPages - */ - public Builder addPage(Notification page) { - mPages.add(page); - return this; - } - - /** - * Add additional pages of content to display with this notification. The current - * notification forms the first page, and pages added using this function form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * - * @param pages a list of notifications - * @return this object for method chaining - * @see WearableNotificationExtensions#getPages - */ - public Builder addPages(List<Notification> pages) { - mPages.addAll(pages); - return this; - } - - /** - * Clear all additional pages present on this builder. - * @return this object for method chaining. - * @see #addPage - */ - public Builder clearPages() { - mPages.clear(); - return this; - } - - /** - * Set a background image to be displayed behind the notification content. - * Contrary to the {@link Notification.BigPictureStyle}, this background - * will work with any notification style. - * - * @param background the background bitmap - * @return this object for method chaining - * @see WearableNotificationExtensions#getBackground - */ - public Builder setBackground(Bitmap background) { - mBackground = background; - return this; - } - - /** - * Set an icon that goes with the content of this notification. - */ - public Builder setContentIcon(int icon) { - mContentIcon = icon; - return this; - } - - /** - * Set the gravity that the content icon should have within the notification display. - * Supported values include {@link Gravity#START} and {@link Gravity#END}. The default - * value is {@link android.view.Gravity#END}. - * @see #setContentIcon - */ - public Builder setContentIconGravity(int contentIconGravity) { - mContentIconGravity = contentIconGravity; - return this; - } - - /** - * Set an action from this notification's actions to be clickable with the content of - * this notification page. This action will no longer display separately from the - * notification content. This action's icon will display with optional subtext provided - * by the action's title. - * @param actionIndex The index of the action to hoist on the current notification page. - * If wearable actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. - */ - public Builder setContentAction(int actionIndex) { - mContentActionIndex = actionIndex; - return this; - } - - /** - * Set the gravity that this notification should have within the available viewport space. - * Supported values include {@link Gravity#TOP}, {@link Gravity#CENTER_VERTICAL} and - * {@link Gravity#BOTTOM}. The default value is {@link Gravity#BOTTOM}. - */ - public Builder setGravity(int gravity) { - mGravity = gravity; - return this; - } - - /** - * Set the custom size preset for the display of this notification out of the available - * presets found in {@link WearableNotificationExtensions}, e.g. {@link #SIZE_LARGE}. - * <p>Some custom size presets are only applicable for custom display notifications created - * using {@link Builder#setDisplayIntent}. Check the documentation for the preset in - * question. See also {@link Builder#setCustomContentHeight} and - * {@link #getCustomSizePreset}. - */ - public Builder setCustomSizePreset(int sizePreset) { - mCustomSizePreset = sizePreset; - return this; - } - - /** - * Set the custom height in pixels for the display of this notification's content. - * <p>This option is only available for custom display notifications created - * using {@link Builder#setDisplayIntent}. See also {@link Builder#setCustomSizePreset} and - * {@link #getCustomContentHeight}. - */ - public Builder setCustomContentHeight(int height) { - mCustomContentHeight = height; - return this; - } - - /** - * Set whether the scrolling position for the contents of this notification should start - * at the bottom of the contents instead of the top when the contents are too long to - * display within the screen. Default is false (start scroll at the top). - */ - public Builder setStartScrollBottom(boolean startScrollBottom) { - setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); - return this; - } - - /** - * Set whether the content intent is available when the wearable device is not connected - * to a companion device. The user can still trigger this intent when the wearable device - * is offline, but a visual hint will indicate that the content intent may not be available. - * Defaults to true. - */ - public Builder setContentIntentAvailableOffline(boolean contentIntentAvailableOffline) { - setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); - return this; - } - - /** - * Set a hint that this notification's icon should not be displayed. - * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. - * @return this object for method chaining - */ - public Builder setHintHideIcon(boolean hintHideIcon) { - setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); - return this; - } - - /** - * Set a visual hint that only the background image of this notification should be - * displayed, and other semantic content should be hidden. This hint is only applicable - * to sub-pages added using {@link #addPage}. - */ - public Builder setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { - setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); - return this; - } - - /** - * Build a new {@link WearableNotificationExtensions} object with the extensions - * currently present on this builder. - * @return the extensions object. - */ - public WearableNotificationExtensions build() { - return new WearableNotificationExtensions( - mActions.toArray(new Notification.Action[mActions.size()]), mFlags, - mDisplayIntent, mPages.toArray(new Notification[mPages.size()]), - mBackground, mContentIcon, mContentIconGravity, mContentActionIndex, - mCustomSizePreset, mCustomContentHeight, mGravity); - } - - private void setFlag(int mask, boolean value) { - if (value) { - mFlags |= mask; - } else { - mFlags &= ~mask; - } - } - } - - public static final Creator<WearableNotificationExtensions> CREATOR = - new Creator<WearableNotificationExtensions>() { - @Override - public WearableNotificationExtensions createFromParcel(Parcel in) { - return new WearableNotificationExtensions(in); - } - - @Override - public WearableNotificationExtensions[] newArray(int size) { - return new WearableNotificationExtensions[size]; - } - }; -} diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index d3e9089..e5bf7d0 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -317,9 +317,9 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; /** - * Sent to providers after AppWidget state related to the provider has been restored from - * backup. The intent contains information about how to translate AppWidget ids from the - * restored data to their new equivalents. + * Sent to an {@link AppWidgetProvider} after AppWidget state related to that provider has + * been restored from backup. The intent contains information about how to translate AppWidget + * ids from the restored data to their new equivalents. * * <p>The intent will contain the following extras: * @@ -343,7 +343,7 @@ public class AppWidgetManager { * <p class="note">This is a protected intent that can only be sent * by the system. * - * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast + * @see #ACTION_APPWIDGET_HOST_RESTORED */ public static final String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED"; @@ -352,7 +352,7 @@ public class AppWidgetManager { * Sent to widget hosts after AppWidget state related to the host has been restored from * backup. The intent contains information about how to translate AppWidget ids from the * restored data to their new equivalents. If an application maintains multiple separate - * widget hosts instances, it will receive this broadcast separately for each one. + * widget host instances, it will receive this broadcast separately for each one. * * <p>The intent will contain the following extras: * @@ -380,7 +380,7 @@ public class AppWidgetManager { * <p class="note">This is a protected intent that can only be sent * by the system. * - * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast + * @see #ACTION_APPWIDGET_RESTORED */ public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6ae006c..b0673b5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2023,6 +2023,7 @@ public abstract class Context { PRINT_SERVICE, MEDIA_SESSION_SERVICE, BATTERY_SERVICE, + TASK_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -2079,6 +2080,8 @@ public abstract class Context { * <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads * <dt> {@link #BATTERY_SERVICE} ("batterymanager") * <dd> A {@link android.os.BatteryManager} for managing battery state + * <dt> {@link #TASK_SERVICE} ("taskmanager") + * <dd> A {@link android.app.task.TaskManager} for managing scheduled tasks * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -2134,6 +2137,8 @@ public abstract class Context { * @see android.app.DownloadManager * @see #BATTERY_SERVICE * @see android.os.BatteryManager + * @see #TASK_SERVICE + * @see android.app.task.TaskManager */ public abstract Object getSystemService(@ServiceName @NonNull String name); @@ -2728,6 +2733,15 @@ public abstract class Context { public static final String USAGE_STATS_SERVICE = "usagestats"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.task.TaskManager} instance for managing occasional + * background tasks. + * @see #getSystemService + * @see android.app.task.TaskManager + */ + public static final String TASK_SERVICE = "task"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index f1391aa..bd07470 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3739,32 +3739,27 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000; /** - * This flag is used to break out "documents" into separate tasks that can - * be reached via the Recents mechanism. Such a document is any kind of - * item for which an application may want to maintain multiple simultaneous - * instances. Examples might be text files, web pages, spreadsheets, or - * emails. Each such document will be in a separate task in the Recents list. - * - * <p>When set, the activity specified by this Intent will launch into a - * separate task rooted at that activity. The activity launched must be - * defined with {@link android.R.attr#launchMode} <code>standard</code> - * or <code>singleTop</code>. - * - * <p>If FLAG_ACTIVITY_NEW_DOCUMENT is used without - * {@link #FLAG_ACTIVITY_MULTIPLE_TASK} then the activity manager will - * search for an existing task with a matching target activity and Intent - * data URI and relaunch that task, first finishing all activities down to - * the root activity and then calling the root activity's - * {@link android.app.Activity#onNewIntent(Intent)} method. If no existing - * task's root activity matches the Intent's data URI then a new task will - * be launched with the target activity as root. - * - * <p>When paired with {@link #FLAG_ACTIVITY_MULTIPLE_TASK} this will - * always create a new task. Thus the same document may be made to appear - * more than one time in Recents. - * - * <p>This is equivalent to the attribute {@link android.R.attr#documentLaunchMode}. - * + * This flag is used to open a document into a new task rooted at the activity launched + * by this Intent. Through the use of this flag, or its equivalent attribute, + * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity + * containing different douments will appear in the recent tasks list. + * + * <p>The use of the activity attribute form of this, + * {@link android.R.attr#documentLaunchMode}, is + * preferred over the Intent flag described here. The attribute form allows the + * Activity to specify multiple document behavior for all launchers of the Activity + * whereas using this flag requires each Intent that launches the Activity to specify it. + * + * <p>FLAG_ACTIVITY_NEW_DOCUMENT may be used in conjunction with {@link + * #FLAG_ACTIVITY_MULTIPLE_TASK}. When used alone it is the + * equivalent of the Activity manifest specifying {@link + * android.R.attr#documentLaunchMode}="intoExisting". When used with + * FLAG_ACTIVITY_MULTIPLE_TASK it is the equivalent of the Activity manifest specifying + * {@link android.R.attr#documentLaunchMode}="always". + * + * Refer to {@link android.R.attr#documentLaunchMode} for more information. + * + * @see android.R.attr#documentLaunchMode * @see #FLAG_ACTIVITY_MULTIPLE_TASK */ public static final int FLAG_ACTIVITY_NEW_DOCUMENT = diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 8391209..7738d2d 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -508,14 +508,62 @@ public abstract class CameraCaptureSession implements AutoCloseable { } /** - * This method is called when an image capture has completed and the + * This method is called when an image capture makes partial forward progress; some + * (but not all) results from an image capture are available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple {@link #onCaptureProgressed} calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final {@link #onCaptureCompleted} call will always + * contain all the fields (in particular, the union of all the fields of all + * the partial results composing the total result).</p> + * + * <p>For each request, some result data might be available earlier than others. The typical + * delay between each partial result (per request) is a single frame interval. + * For performance-oriented use-cases, applications should query the metadata they need + * to make forward progress from the partial results and avoid waiting for the completed + * result.</p> + * + * <p>Each request will generate at least {@code 1} partial results, and at most + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p> + * + * <p>Depending on the request settings, the number of partial results per request + * will vary, although typically the partial count could be the same as long as the + * camera device subsystems enabled stay the same.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param partialResult The partial output metadata from the capture, which + * includes a subset of the {@link TotalCaptureResult} fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureProgressed(CameraDevice camera, + CaptureRequest request, CaptureResult partialResult) { + // default empty implementation + } + + /** + * This method is called when an image capture has fully completed and all the * result metadata is available. * + * <p>This callback will always fire after the last {@link #onCaptureProgressed}; + * in other words, no more partial results will be delivered once the completed result + * is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, consider + * using {@link #onCaptureProgressed} instead.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera The CameraDevice sending the callback. * @param request The request that was given to the CameraDevice - * @param result The output metadata from the capture, including the + * @param result The total output metadata from the capture, including the * final capture parameters and the state of the camera system during * capture. * @@ -525,7 +573,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingBurst */ public void onCaptureCompleted(CameraDevice camera, - CaptureRequest request, CaptureResult result) { + CaptureRequest request, TotalCaptureResult result) { // default empty implementation } @@ -563,24 +611,57 @@ public abstract class CameraCaptureSession implements AutoCloseable { * when a capture sequence finishes and all {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this listener. * + * <p>In total, there will be at least one result/failure returned by this listener + * before this callback is invoked. If the capture sequence is aborted before any + * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p> + * + * <p>The default implementation does nothing.</p> + * * @param camera * The CameraDevice sending the callback. * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. - * @param lastFrameNumber + * @param frameNumber * The last frame number (returned by {@link CaptureResult#getFrameNumber} * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. - * The last frame number may be equal to NO_FRAMES_CAPTURED if no images - * were captured for this sequence. This can happen, for example, when a - * repeating request or burst is cleared right after being set. * * @see CaptureResult#getFrameNumber() * @see CaptureFailure#getFrameNumber() * @see CaptureResult#getSequenceId() * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceAborted */ public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, int lastFrameNumber) { + int sequenceId, long frameNumber) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence aborts before any {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * <p>Due to the asynchronous nature of the camera device, not all submitted captures + * are immediately processed. It is possible to clear out the pending requests + * by a variety of operations such as {@link CameraDevice#stopRepeating} or + * {@link CameraDevice#flush}. When such an event happens, + * {@link #onCaptureSequenceCompleted} will not be called.</p> + * + * <p>The default implementation does nothing.</p> + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceCompleted + */ + public void onCaptureSequenceAborted(CameraDevice camera, + int sequenceId) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index c08424a..cea68d2 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -296,8 +296,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * valid anti-banding modes that the application may request * for this camera device; they must include AUTO.</p> */ - public static final Key<byte[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = - new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class); + public static final Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = + new Key<int[]>("android.control.aeAvailableAntibandingModes", int[].class); /** * <p>The set of auto-exposure modes that are supported by this @@ -315,15 +315,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#CONTROL_AE_MODE */ - public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES = - new Key<byte[]>("android.control.aeAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AE_AVAILABLE_MODES = + new Key<int[]>("android.control.aeAvailableModes", int[].class); /** * <p>List of frame rate ranges supported by the * AE algorithm/hardware</p> */ - public static final Key<int[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = - new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class); + public static final Key<android.util.Range<Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = + new Key<android.util.Range<Integer>[]>("android.control.aeAvailableTargetFpsRanges", new TypeReference<android.util.Range<Integer>[]>() {{ }}); /** * <p>Maximum and minimum exposure compensation @@ -332,8 +332,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP */ - public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE = - new Key<int[]>("android.control.aeCompensationRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_COMPENSATION_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeCompensationRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Smallest step by which exposure compensation @@ -355,8 +355,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see CaptureRequest#CONTROL_AF_MODE * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE */ - public static final Key<byte[]> CONTROL_AF_AVAILABLE_MODES = - new Key<byte[]>("android.control.afAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AF_AVAILABLE_MODES = + new Key<int[]>("android.control.afAvailableModes", int[].class); /** * <p>List containing the subset of color effects @@ -374,8 +374,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see CaptureRequest#CONTROL_EFFECT_MODE * @see CaptureRequest#CONTROL_MODE */ - public static final Key<byte[]> CONTROL_AVAILABLE_EFFECTS = - new Key<byte[]>("android.control.availableEffects", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_EFFECTS = + new Key<int[]>("android.control.availableEffects", int[].class); /** * <p>List containing a subset of scene modes @@ -388,15 +388,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#CONTROL_SCENE_MODE */ - public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES = - new Key<byte[]>("android.control.availableSceneModes", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_SCENE_MODES = + new Key<int[]>("android.control.availableSceneModes", int[].class); /** * <p>List of video stabilization modes that can * be supported</p> */ - public static final Key<byte[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = - new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = + new Key<int[]>("android.control.availableVideoStabilizationModes", int[].class); /** * <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) @@ -414,8 +414,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM * @see CaptureRequest#CONTROL_AWB_MODE */ - public static final Key<byte[]> CONTROL_AWB_AVAILABLE_MODES = - new Key<byte[]>("android.control.awbAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AWB_AVAILABLE_MODES = + new Key<int[]>("android.control.awbAvailableModes", int[].class); /** * <p>List of the maximum number of regions that can be used for metering in @@ -427,19 +427,53 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see CaptureRequest#CONTROL_AE_REGIONS * @see CaptureRequest#CONTROL_AF_REGIONS * @see CaptureRequest#CONTROL_AWB_REGIONS + * @hide */ public static final Key<int[]> CONTROL_MAX_REGIONS = new Key<int[]>("android.control.maxRegions", int[].class); /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-exposure (AE); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p> + * + * @see CaptureRequest#CONTROL_AE_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AE = + new Key<Integer>("android.control.maxRegionsAe", int.class); + + /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-white balance (AWB); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p> + * + * @see CaptureRequest#CONTROL_AWB_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AWB = + new Key<Integer>("android.control.maxRegionsAwb", int.class); + + /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-focus (AF); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p> + * + * @see CaptureRequest#CONTROL_AF_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AF = + new Key<Integer>("android.control.maxRegionsAf", int.class); + + /** * <p>The set of edge enhancement modes supported by this camera device.</p> * <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p> * <p>Full-capability camera devices must always support OFF and FAST.</p> * * @see CaptureRequest#EDGE_MODE */ - public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES = - new Key<byte[]>("android.edge.availableEdgeModes", byte[].class); + public static final Key<int[]> EDGE_AVAILABLE_EDGE_MODES = + new Key<int[]>("android.edge.availableEdgeModes", int[].class); /** * <p>Whether this camera device has a @@ -458,8 +492,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#HOT_PIXEL_MODE */ - public static final Key<byte[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES = - new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].class); + public static final Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES = + new Key<int[]>("android.hotPixel.availableHotPixelModes", int[].class); /** * <p>Supported resolutions for the JPEG thumbnail</p> @@ -526,8 +560,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE */ - public static final Key<byte[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = - new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class); + public static final Key<int[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = + new Key<int[]>("android.lens.info.availableOpticalStabilization", int[].class); /** * <p>Optional. Hyperfocal distance for this lens.</p> @@ -553,6 +587,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Dimensions of lens shading map.</p> * <p>The map should be on the order of 30-40 rows and columns, and * must be smaller than 64x64.</p> + * @hide */ public static final Key<android.util.Size> LENS_INFO_SHADING_MAP_SIZE = new Key<android.util.Size>("android.lens.info.shadingMapSize", android.util.Size.class); @@ -591,8 +626,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#NOISE_REDUCTION_MODE */ - public static final Key<byte[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES = - new Key<byte[]>("android.noiseReduction.availableNoiseReductionModes", byte[].class); + public static final Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES = + new Key<int[]>("android.noiseReduction.availableNoiseReductionModes", int[].class); /** * <p>If set to 1, the HAL will always split result @@ -621,7 +656,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p> * <p>This lists the upper bound of the number of output streams supported by * the camera device. Using more streams simultaneously may require more hardware and - * CPU resources that will consume more power. The image format for a output stream can + * CPU resources that will consume more power. The image format for an output stream can * be any supported format provided by android.scaler.availableStreamConfigurations. * The formats defined in android.scaler.availableStreamConfigurations can be catergorized * into the 3 stream types as below:</p> @@ -632,11 +667,79 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>Processed (but not-stalling): any non-RAW format without a stall duration. * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> * </ul> + * @hide */ public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS = new Key<int[]>("android.request.maxNumOutputStreams", int[].class); /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any <code>RAW</code> formats.</p> + * <p>This value contains the max number of output simultaneous + * streams from the raw sensor.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any <code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>In particular, a <code>RAW</code> format is typically one of:</p> + * <ul> + * <li>ImageFormat#RAW_SENSOR</li> + * <li>Opaque <code>RAW</code></li> + * </ul> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_RAW = + new Key<Integer>("android.request.maxNumOutputRaw", int.class); + + /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any processed (but not-stalling) formats.</p> + * <p>This value contains the max number of output simultaneous + * streams for any processed (but not-stalling) formats.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>Processed (but not-stalling) is defined as any non-RAW format without a stall duration. + * Typically:</p> + * <ul> + * <li>ImageFormat#YUV_420_888</li> + * <li>ImageFormat#NV21</li> + * <li>ImageFormat#YV12</li> + * <li>Implementation-defined formats, i.e. StreamConfiguration#isOutputSupportedFor(Class)</li> + * </ul> + * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with + * a processed format -- it will return 0 for a non-stalling stream.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC = + new Key<Integer>("android.request.maxNumOutputProc", int.class); + + /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any processed (and stalling) formats.</p> + * <p>This value contains the max number of output simultaneous + * streams for any processed (but not-stalling) formats.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations > 0. + * Typically only the <code>JPEG</code> format (ImageFormat#JPEG)</p> + * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with + * a processed format -- it will return a non-0 value for a stalling stream.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING = + new Key<Integer>("android.request.maxNumOutputProcStalling", int.class); + + /** * <p>The maximum numbers of any type of input streams * that can be configured and used simultaneously by a camera device.</p> * <p>When set to 0, it means no input stream is supported.</p> @@ -707,7 +810,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL devices:</p> * <ul> * <li>MANUAL_SENSOR</li> - * <li>ZSL</li> * </ul> * <p>Other capabilities may be available on either FULL or LIMITED * devices, but the app. should query this field to be sure.</p> @@ -716,7 +818,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE * @see #REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR - * @see #REQUEST_AVAILABLE_CAPABILITIES_GCAM + * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG */ @@ -750,7 +852,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * value.</p> * <p>The following keys may return <code>null</code> unless they are enabled:</p> * <ul> - * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li> + * <li>android.statistics.lensShadingMap (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li> * </ul> * <p>(Those sometimes-null keys should nevertheless be listed here * if they are available.)</p> @@ -761,7 +863,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>TODO: This should be used by #getAvailableCaptureResultKeys.</p> * * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @hide */ @@ -1229,8 +1330,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Range of valid sensitivities</p> */ - public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE = - new Key<int[]>("android.sensor.info.sensitivityRange", int[].class); + public static final Key<android.util.Range<Integer>> SENSOR_INFO_SENSITIVITY_RANGE = + new Key<android.util.Range<Integer>>("android.sensor.info.sensitivityRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Arrangement of color filters on sensor; @@ -1251,8 +1352,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE = - new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class); + public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE = + new Key<android.util.Range<Long>>("android.sensor.info.exposureTimeRange", new TypeReference<android.util.Range<Long>>() {{ }}); /** * <p>Maximum possible frame duration (minimum frame @@ -1276,8 +1377,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * array</p> * <p>Needed for FOV calculation for old API</p> */ - public static final Key<float[]> SENSOR_INFO_PHYSICAL_SIZE = - new Key<float[]>("android.sensor.info.physicalSize", float[].class); + public static final Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE = + new Key<android.util.SizeF>("android.sensor.info.physicalSize", android.util.SizeF.class); /** * <p>Dimensions of full pixel array, possibly @@ -1383,8 +1484,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 = - new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A per-device calibration transform matrix that maps from the @@ -1404,8 +1505,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 = - new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms color values from CIE XYZ color space to @@ -1426,8 +1527,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 = - new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms color values from CIE XYZ color space to @@ -1450,8 +1551,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 = - new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms white balanced camera colors from the reference @@ -1470,8 +1571,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 = - new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms white balanced camera colors from the reference @@ -1492,8 +1593,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_FORWARD_MATRIX2 = - new Key<Rational[]>("android.sensor.forwardMatrix2", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A fixed black level offset for each of the color filter arrangement @@ -1560,8 +1661,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * android.statistics.faceIds and * android.statistics.faceLandmarks outputs.</p> */ - public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = - new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class); + public static final Key<int[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = + new Key<int[]>("android.statistics.info.availableFaceDetectModes", int[].class); /** * <p>Maximum number of simultaneously detectable @@ -1584,19 +1685,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Maximum number of supported points in the - * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, or - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, or {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.</p> + * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p> * <p>If the actual number of points provided by the application (in - * android.tonemap.curve*) is less than max, the camera device will + * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is less than max, the camera device will * resample the curve to its internal representation, using linear * interpolation.</p> * <p>The output curves in the result metadata may have a different number * of points than the input curves, and will represent the actual * hardware curves used as closely as possible when linearly interpolated.</p> * - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE */ public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS = new Key<Integer>("android.tonemap.maxCurvePoints", int.class); @@ -1609,8 +1707,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @see CaptureRequest#TONEMAP_MODE */ - public static final Key<byte[]> TONEMAP_AVAILABLE_TONE_MAP_MODES = - new Key<byte[]>("android.tonemap.availableToneMapModes", byte[].class); + public static final Key<int[]> TONEMAP_AVAILABLE_TONE_MAP_MODES = + new Key<int[]>("android.tonemap.availableToneMapModes", int[].class); /** * <p>A list of camera LEDs that are available on this system.</p> @@ -1626,15 +1724,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>A FULL device has the most support possible and will enable the * widest range of use cases such as:</p> * <ul> - * <li>30 FPS at maximum resolution (== sensor resolution)</li> - * <li>Per frame control</li> - * <li>Manual sensor control</li> - * <li>Zero Shutter Lag (ZSL)</li> + * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li> + * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li> + * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li> + * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li> * </ul> * <p>A LIMITED device may have some or none of the above characteristics. * To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p> * * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraCharacteristics#SYNC_MAX_LATENCY * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL */ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 77640d1..6f5099b 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -766,14 +766,62 @@ public interface CameraDevice extends AutoCloseable { } /** - * This method is called when an image capture has completed and the + * This method is called when an image capture makes partial forward progress; some + * (but not all) results from an image capture are available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple {@link #onCaptureProgressed} calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final {@link #onCaptureCompleted} call will always + * contain all the fields (in particular, the union of all the fields of all + * the partial results composing the total result).</p> + * + * <p>For each request, some result data might be available earlier than others. The typical + * delay between each partial result (per request) is a single frame interval. + * For performance-oriented use-cases, applications should query the metadata they need + * to make forward progress from the partial results and avoid waiting for the completed + * result.</p> + * + * <p>Each request will generate at least {@code 1} partial results, and at most + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p> + * + * <p>Depending on the request settings, the number of partial results per request + * will vary, although typically the partial count could be the same as long as the + * camera device subsystems enabled stay the same.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param partialResult The partial output metadata from the capture, which + * includes a subset of the {@link TotalCaptureResult} fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureProgressed(CameraDevice camera, + CaptureRequest request, CaptureResult partialResult) { + // default empty implementation + } + + /** + * This method is called when an image capture has fully completed and all the * result metadata is available. * + * <p>This callback will always fire after the last {@link #onCaptureProgressed}; + * in other words, no more partial results will be delivered once the completed result + * is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, consider + * using {@link #onCaptureProgressed} instead.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera The CameraDevice sending the callback. * @param request The request that was given to the CameraDevice - * @param result The output metadata from the capture, including the + * @param result The total output metadata from the capture, including the * final capture parameters and the state of the camera system during * capture. * @@ -783,7 +831,7 @@ public interface CameraDevice extends AutoCloseable { * @see #setRepeatingBurst */ public void onCaptureCompleted(CameraDevice camera, - CaptureRequest request, CaptureResult result) { + CaptureRequest request, TotalCaptureResult result) { // default empty implementation } @@ -796,6 +844,10 @@ public interface CameraDevice extends AutoCloseable { * the capture may have been pushed to their respective output * streams.</p> * + * <p>Some partial results may have been delivered before the capture fails; + * however after this callback fires, no more partial results will be delivered by + * {@link #onCaptureProgressed}.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera @@ -821,24 +873,57 @@ public interface CameraDevice extends AutoCloseable { * when a capture sequence finishes and all {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this listener. * + * <p>In total, there will be at least one result/failure returned by this listener + * before this callback is invoked. If the capture sequence is aborted before any + * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p> + * + * <p>The default implementation does nothing.</p> + * * @param camera * The CameraDevice sending the callback. * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. - * @param lastFrameNumber + * @param frameNumber * The last frame number (returned by {@link CaptureResult#getFrameNumber} * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. - * The last frame number may be equal to NO_FRAMES_CAPTURED if no images - * were captured for this sequence. This can happen, for example, when a - * repeating request or burst is cleared right after being set. * * @see CaptureResult#getFrameNumber() * @see CaptureFailure#getFrameNumber() * @see CaptureResult#getSequenceId() * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceAborted */ public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, int lastFrameNumber) { + int sequenceId, long frameNumber) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence aborts before any {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * <p>Due to the asynchronous nature of the camera device, not all submitted captures + * are immediately processed. It is possible to clear out the pending requests + * by a variety of operations such as {@link CameraDevice#stopRepeating} or + * {@link CameraDevice#flush}. When such an event happens, + * {@link #onCaptureSequenceCompleted} will not be called.</p> + * + * <p>The default implementation does nothing.</p> + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceCompleted + */ + public void onCaptureSequenceAborted(CameraDevice camera, + int sequenceId) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 03b342c..83db056 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -220,6 +220,7 @@ public final class CameraManager { private CameraDevice openCameraDeviceUserAsync(String cameraId, CameraDevice.StateListener listener, Handler handler) throws CameraAccessException { + CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; try { @@ -231,7 +232,8 @@ public final class CameraManager { new android.hardware.camera2.impl.CameraDevice( cameraId, listener, - handler); + handler, + characteristics); BinderHolder holder = new BinderHolder(); diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 4cde601..b3e165e 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -236,9 +236,17 @@ public abstract class CameraMetadata<TKey> { /** * <p>The camera device can be manually controlled (3A algorithms such - * as auto exposure, and auto focus can be - * bypassed), this includes but is not limited to:</p> + * as auto exposure, and auto focus can be bypassed). + * The camera device supports basic manual control of the sensor image + * acquisition related stages. This means the following controls are + * guaranteed to be supported:</p> * <ul> + * <li>Manual frame duration control<ul> + * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li> + * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li> + * </ul> + * </li> * <li>Manual exposure control<ul> * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li> * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li> @@ -265,10 +273,15 @@ public abstract class CameraMetadata<TKey> { * <p>If any of the above 3A algorithms are enabled, then the camera * device will accurately report the values applied by 3A in the * result.</p> + * <p>A given camera device may also support additional manual sensor controls, + * but this capability only covers the above list of controls.</p> * * @see CaptureRequest#BLACK_LEVEL_LOCK + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE + * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE * @see CaptureRequest#SENSOR_SENSITIVITY * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -276,12 +289,12 @@ public abstract class CameraMetadata<TKey> { public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; /** - * <p>TODO: This should be @hide</p> + * <p>The camera device post-processing stages can be manually controlled. + * The camera device supports basic manual control of the image post-processing + * stages. This means the following controls are guaranteed to be supported:</p> * <ul> * <li>Manual tonemap control<ul> - * <li>{@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}</li> - * <li>{@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}</li> - * <li>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}</li> + * <li>{@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}</li> * <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li> * <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li> * </ul> @@ -292,8 +305,8 @@ public abstract class CameraMetadata<TKey> { * </ul> * </li> * <li>Lens shading map information<ul> - * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}</li> - * <li>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}</li> + * <li>android.statistics.lensShadingMap</li> + * <li>android.lens.info.shadingMapSize</li> * </ul> * </li> * </ul> @@ -301,19 +314,17 @@ public abstract class CameraMetadata<TKey> { * will accurately report the values applied by AWB in the result.</p> * <p>The camera device will also support everything in MANUAL_SENSOR * except manual lens control and manual flash control.</p> + * <p>A given camera device may also support additional post-processing + * controls, but this capability only covers the above list of controls.</p> * * @see CaptureRequest#COLOR_CORRECTION_GAINS * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3; + public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; /** * <p>The camera device supports the Zero Shutter Lag use case.</p> @@ -1548,17 +1559,14 @@ public abstract class CameraMetadata<TKey> { /** * <p>Use the tone mapping curve specified in - * the android.tonemap.curve* entries.</p> + * the {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}* entries.</p> * <p>All color enhancement and tonemapping must be disabled, except * for applying the tonemapping curve specified by - * {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}, or - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}.</p> + * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p> * <p>Must not slow down frame rate relative to raw * sensor output.</p> * - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_CONTRAST_CURVE = 0; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index a4aa296..6aa24e6 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -537,30 +537,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = - new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>Gains applying to Bayer raw color channels for * white-balance.</p> - * <p>The 4-channel white-balance gains are defined in - * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain - * for green pixels on even rows of the output, and <code>G_odd</code> - * is the gain for green pixels on the odd rows. if a HAL - * does not support a separate gain for even/odd green channels, - * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to - * <code>G_even</code> in the output result metadata.</p> - * <p>This array is either set by the camera device when the request - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or - * directly by the application in the request when the - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> - * <p>The output should be the gains actually applied by the camera device to - * the current frame.</p> + * <p>These per-channel gains are either set by the camera device + * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not + * TRANSFORM_MATRIX, or directly by the application in the + * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is + * TRANSFORM_MATRIX.</p> + * <p>The gains in the result metadata are the gains actually + * applied by the camera device to the current frame.</p> * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<float[]> COLOR_CORRECTION_GAINS = - new Key<float[]>("android.colorCorrection.gains", float[].class); + public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS = + new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class); /** * <p>The desired setting for the camera device's auto-exposure @@ -693,9 +687,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>List of areas to use for * metering.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, @@ -711,8 +702,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AE_REGIONS = - new Key<int[]>("android.control.aeRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Range over which fps can be adjusted to @@ -722,8 +713,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = - new Key<int[]>("android.control.aeTargetFpsRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Whether the camera device will trigger a precapture @@ -768,26 +759,23 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>List of areas to use for focus * estimation.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific focus area - * needs to be used by the camera device. If the focusing region is - * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture - * result metadata, the camera device will ignore the sections outside - * the region and output the used sections in the result metadata.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AF_REGIONS = - new Key<int[]>("android.control.afRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Whether the camera device will trigger autofocus for this request.</p> @@ -854,27 +842,23 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>List of areas to use for illuminant * estimation.</p> - * <p>Only used in AUTO mode.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area - * needs to be used by the camera device. If the AWB region is - * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, * the camera device will ignore the sections outside the region and output the * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AWB_REGIONS = - new Key<int[]>("android.control.awbRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Information to the camera device 3A (auto-exposure, @@ -1068,8 +1052,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Integer>("android.hotPixel.mode", int.class); /** + * <p>A location object to use when generating image GPS metadata.</p> + */ + public static final Key<android.location.Location> JPEG_GPS_LOCATION = + new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class); + + /** * <p>GPS coordinates to include in output JPEG * EXIF</p> + * @hide */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); @@ -1077,6 +1068,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>32 characters describing GPS algorithm to * include in EXIF</p> + * @hide */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); @@ -1084,6 +1076,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>Time GPS fix was made to include in * EXIF</p> + * @hide */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); @@ -1116,6 +1109,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * but the captured JPEG will still be a valid image.</p> * <p>When a jpeg image capture is issued, the thumbnail size selected should have * the same aspect ratio as the jpeg image.</p> + * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect + * ratio, the camera device creates the thumbnail by cropping it from the primary image. + * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has + * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to + * generate the thumbnail image. The thumbnail image will always have a smaller Field + * Of View (FOV) than the primary image when aspect ratios differ.</p> */ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE = new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class); @@ -1432,8 +1431,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>When set to OFF mode, no lens shading correction will be applied by the * camera device, and an identity lens shading map data will be provided * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens - * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, - * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>, + * the output android.statistics.lensShadingMap for this case will be an identity map * shown below:</p> * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1445,8 +1444,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>When set to other modes, lens shading correction will be applied by the * camera device. Applications can request lens shading map data by setting * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide - * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified - * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}; the returned shading map data will be the one + * lens shading map data in android.statistics.lensShadingMap, with size specified + * by android.lens.info.shadingMapSize; the returned shading map data will be the one * applied by the camera device for this capture request.</p> * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in @@ -1456,8 +1455,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_AWB_MODE - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @see #SHADING_MODE_OFF * @see #SHADING_MODE_FAST @@ -1498,10 +1495,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Whether the camera device will output the lens * shading map in output result metadata.</p> * <p>When set to ON, - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * android.statistics.lensShadingMap must be provided in * the output result metadata.</p> - * - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON */ @@ -1512,10 +1507,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); @@ -1524,10 +1519,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Tonemapping / contrast / gamma curve for the green * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); @@ -1537,7 +1532,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> * <p>Each channel's curve is defined by an array of control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * <pre><code>android.tonemap.curveRed = * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> * <p>These are sorted in order of increasing <code>Pin</code>; it is always @@ -1553,15 +1548,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> * <p>Linear mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, @@ -1569,7 +1564,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * </code></pre> * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, @@ -1577,14 +1572,67 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * </code></pre> * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} + * is CONTRAST_CURVE.</p> + * <p>The tonemapCurve consist of three curves for each of red, green, and blue + * channels respectively. The following example uses the red channel as an + * example. The same logic applies to green and blue channel. + * Each channel's curve is defined by an array of control points:</p> + * <pre><code>curveRed = + * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), + * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072), + * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), + * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), + * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130), + * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), + * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE + */ + public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE = + new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class); + + /** * <p>High-level global contrast/gamma/tonemapping control.</p> * <p>When switching to an application-defined contrast curve by setting * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined @@ -1600,8 +1648,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>This must be set to a valid mode in * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p> * <p>When using either FAST or HIGH_QUALITY, the camera device will - * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}. * These values are always available, and as close as possible to the * actually used nonlinear/nonglobal transforms.</p> * <p>If a request is sent with CONTRAST_CURVE with the camera device's @@ -1609,9 +1656,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * roughly the same.</p> * * @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 61d491b..42020eb 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -24,9 +24,9 @@ import android.util.Rational; import java.util.List; /** - * <p>The results of a single image capture from the image sensor.</p> + * <p>The subset of the results of a single image capture from the image sensor.</p> * - * <p>Contains the final configuration for the capture hardware (sensor, lens, + * <p>Contains a subset of the final configuration for the capture hardware (sensor, lens, * flash), the processing pipeline, the control algorithms, and the output * buffers.</p> * @@ -36,10 +36,15 @@ import java.util.List; * capture. The result also includes additional metadata about the state of the * camera device during the capture.</p> * - * <p>{@link CameraCharacteristics} objects are immutable.</p> + * <p>Not all properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()} + * are necessarily available. Some results are {@link CaptureResult partial} and will + * not have every key set. Only {@link TotalCaptureResult total} results are guaranteed to have + * every key available that was enabled by the request.</p> + * + * <p>{@link CaptureResult} objects are immutable.</p> * */ -public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { +public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { private static final String TAG = "CaptureResult"; private static final boolean VERBOSE = false; @@ -249,18 +254,19 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * Get the request associated with this result. * - * <p>Whenever a request is successfully captured, with - * {@link CameraDevice.CaptureListener#onCaptureCompleted}, - * the {@code result}'s {@code getRequest()} will return that {@code request}. + * <p>Whenever a request has been fully or partially captured, with + * {@link CameraDevice.CaptureListener#onCaptureCompleted} or + * {@link CameraDevice.CaptureListener#onCaptureProgressed}, the {@code result}'s + * {@code getRequest()} will return that {@code request}. * </p> * - * <p>In particular, + * <p>For example, * <code><pre>cameraDevice.capture(someRequest, new CaptureListener() { * {@literal @}Override * void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) { * assert(myResult.getRequest.equals(myRequest) == true); * } - * }; + * }, null); * </code></pre> * </p> * @@ -297,6 +303,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @return int The ID for the sequence of requests that this capture result is a part of * * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + * @see CameraDevice.CaptureListener#onCaptureSequenceAborted */ public int getSequenceId() { return mSequenceId; @@ -376,30 +383,24 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = - new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>Gains applying to Bayer raw color channels for * white-balance.</p> - * <p>The 4-channel white-balance gains are defined in - * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain - * for green pixels on even rows of the output, and <code>G_odd</code> - * is the gain for green pixels on the odd rows. if a HAL - * does not support a separate gain for even/odd green channels, - * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to - * <code>G_even</code> in the output result metadata.</p> - * <p>This array is either set by the camera device when the request - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or - * directly by the application in the request when the - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> - * <p>The output should be the gains actually applied by the camera device to - * the current frame.</p> + * <p>These per-channel gains are either set by the camera device + * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not + * TRANSFORM_MATRIX, or directly by the application in the + * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is + * TRANSFORM_MATRIX.</p> + * <p>The gains in the result metadata are the gains actually + * applied by the camera device to the current frame.</p> * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<float[]> COLOR_CORRECTION_GAINS = - new Key<float[]>("android.colorCorrection.gains", float[].class); + public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS = + new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class); /** * <p>The desired setting for the camera device's auto-exposure @@ -532,9 +533,6 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>List of areas to use for * metering.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, @@ -550,8 +548,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AE_REGIONS = - new Key<int[]>("android.control.aeRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Range over which fps can be adjusted to @@ -561,8 +559,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = - new Key<int[]>("android.control.aeTargetFpsRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Whether the camera device will trigger a precapture @@ -805,26 +803,23 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>List of areas to use for focus * estimation.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific focus area - * needs to be used by the camera device. If the focusing region is - * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture - * result metadata, the camera device will ignore the sections outside - * the region and output the used sections in the result metadata.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AF_REGIONS = - new Key<int[]>("android.control.afRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Whether the camera device will trigger autofocus for this request.</p> @@ -1288,27 +1283,23 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>List of areas to use for illuminant * estimation.</p> - * <p>Only used in AUTO mode.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the * bottom-right pixel in the active pixel array. The weight * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area - * needs to be used by the camera device. If the AWB region is - * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, * the camera device will ignore the sections outside the region and output the * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AWB_REGIONS = - new Key<int[]>("android.control.awbRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Information to the camera device 3A (auto-exposure, @@ -1649,8 +1640,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.hotPixel.mode", int.class); /** + * <p>A location object to use when generating image GPS metadata.</p> + */ + public static final Key<android.location.Location> JPEG_GPS_LOCATION = + new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class); + + /** * <p>GPS coordinates to include in output JPEG * EXIF</p> + * @hide */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); @@ -1658,6 +1656,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>32 characters describing GPS algorithm to * include in EXIF</p> + * @hide */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); @@ -1665,6 +1664,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Time GPS fix was made to include in * EXIF</p> + * @hide */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); @@ -1697,6 +1697,12 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * but the captured JPEG will still be a valid image.</p> * <p>When a jpeg image capture is issued, the thumbnail size selected should have * the same aspect ratio as the jpeg image.</p> + * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect + * ratio, the camera device creates the thumbnail by cropping it from the primary image. + * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has + * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to + * generate the thumbnail image. The thumbnail image will always have a smaller Field + * Of View (FOV) than the primary image when aspect ratios differ.</p> */ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE = new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class); @@ -1786,8 +1792,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>If variable focus not supported, can still report * fixed depth of field range</p> */ - public static final Key<float[]> LENS_FOCUS_RANGE = - new Key<float[]>("android.lens.focusRange", float[].class); + public static final Key<android.util.Range<Float>> LENS_FOCUS_RANGE = + new Key<android.util.Range<Float>>("android.lens.focusRange", new TypeReference<android.util.Range<Float>>() {{ }}); /** * <p>Sets whether the camera device uses optical image stabilization (OIS) @@ -2154,8 +2160,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When set to OFF mode, no lens shading correction will be applied by the * camera device, and an identity lens shading map data will be provided * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens - * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, - * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>, + * the output android.statistics.lensShadingMap for this case will be an identity map * shown below:</p> * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -2167,8 +2173,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When set to other modes, lens shading correction will be applied by the * camera device. Applications can request lens shading map data by setting * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide - * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified - * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}; the returned shading map data will be the one + * lens shading map data in android.statistics.lensShadingMap, with size specified + * by android.lens.info.shadingMapSize; the returned shading map data will be the one * applied by the camera device for this capture request.</p> * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in @@ -2178,8 +2184,6 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_AWB_MODE - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @see #SHADING_MODE_OFF * @see #SHADING_MODE_FAST @@ -2269,13 +2273,59 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * The map is assumed to be bilinearly interpolated between the sample points.</p> * <p>The channel order is [R, Geven, Godd, B], where Geven is the green * channel for the even rows of a Bayer pattern, and Godd is the odd rows. + * The shading map is stored in a fully interleaved format.</p> + * <p>The shading map should have on the order of 30-40 rows and columns, + * and must be smaller than 64x64.</p> + * <p>As an example, given a very small map defined as:</p> + * <pre><code>width,height = [ 4, 3 ] + * values = + * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, + * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3, + * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.25, 1.2, + * 1.3, 1.2, 1.2, 1.3, 1.2, 1.15, 1.1, 1.2, + * 1.2, 1.1, 1.0, 1.2, 1.3, 1.15, 1.2, 1.3 ] + * </code></pre> + * <p>The low-resolution scaling map images for each channel are + * (displayed using nearest-neighbor interpolation):</p> + * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> + * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> + * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> + * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> + * <p>As a visualization only, inverting the full-color map to recover an + * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p> + * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE + */ + public static final Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP = + new Key<android.hardware.camera2.params.LensShadingMap>("android.statistics.lensShadingCorrectionMap", android.hardware.camera2.params.LensShadingMap.class); + + /** + * <p>The shading map is a low-resolution floating-point map + * that lists the coefficients used to correct for vignetting, for each + * Bayer color channel.</p> + * <p>The least shaded section of the image should have a gain factor + * of 1; all other sections should have gains above 1.</p> + * <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map + * must take into account the colorCorrection settings.</p> + * <p>The shading map is for the entire active pixel array, and is not + * affected by the crop region specified in the request. Each shading map + * entry is the value of the shading compensation map over a specific + * pixel on the sensor. Specifically, with a (N x M) resolution shading + * map, and an active pixel array size (W x H), shading map entry + * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at + * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels. + * The map is assumed to be bilinearly interpolated between the sample points.</p> + * <p>The channel order is [R, Geven, Godd, B], where Geven is the green + * channel for the even rows of a Bayer pattern, and Godd is the odd rows. * The shading map is stored in a fully interleaved format, and its size - * is provided in the camera static metadata by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * is provided in the camera static metadata by android.lens.info.shadingMapSize.</p> * <p>The shading map should have on the order of 30-40 rows and columns, * and must be smaller than 64x64.</p> * <p>As an example, given a very small map defined as:</p> - * <pre><code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ] - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} = + * <pre><code>android.lens.info.shadingMapSize = [ 4, 3 ] + * android.statistics.lensShadingMap = * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3, * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0, @@ -2294,8 +2344,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> * * @see CaptureRequest#COLOR_CORRECTION_MODE - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @hide */ public static final Key<float[]> STATISTICS_LENS_SHADING_MAP = new Key<float[]>("android.statistics.lensShadingMap", float[].class); @@ -2395,17 +2444,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE */ - public static final Key<int[]> STATISTICS_HOT_PIXEL_MAP = - new Key<int[]>("android.statistics.hotPixelMap", int[].class); + public static final Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP = + new Key<android.graphics.Point[]>("android.statistics.hotPixelMap", android.graphics.Point[].class); /** * <p>Whether the camera device will output the lens * shading map in output result metadata.</p> * <p>When set to ON, - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * android.statistics.lensShadingMap must be provided in * the output result metadata.</p> - * - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON */ @@ -2416,10 +2463,10 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); @@ -2428,10 +2475,10 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Tonemapping / contrast / gamma curve for the green * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); @@ -2441,7 +2488,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> * <p>Each channel's curve is defined by an array of control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * <pre><code>android.tonemap.curveRed = * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> * <p>These are sorted in order of increasing <code>Pin</code>; it is always @@ -2457,15 +2504,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> * <p>Linear mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, @@ -2473,7 +2520,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * </code></pre> * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, @@ -2481,14 +2528,67 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * </code></pre> * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} + * is CONTRAST_CURVE.</p> + * <p>The tonemapCurve consist of three curves for each of red, green, and blue + * channels respectively. The following example uses the red channel as an + * example. The same logic applies to green and blue channel. + * Each channel's curve is defined by an array of control points:</p> + * <pre><code>curveRed = + * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), + * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072), + * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), + * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), + * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130), + * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), + * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE + */ + public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE = + new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class); + + /** * <p>High-level global contrast/gamma/tonemapping control.</p> * <p>When switching to an application-defined contrast curve by setting * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined @@ -2504,8 +2604,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>This must be set to a valid mode in * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p> * <p>When using either FAST or HIGH_QUALITY, the camera device will - * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}. * These values are always available, and as close as possible to the * actually used nonlinear/nonglobal transforms.</p> * <p>If a request is sent with CONTRAST_CURVE with the camera device's @@ -2513,9 +2612,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * roughly the same.</p> * * @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java new file mode 100644 index 0000000..2647959 --- /dev/null +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import java.util.Collections; +import java.util.List; + +/** + * <p>The total assembled results of a single image capture from the image sensor.</p> + * + * <p>Contains the final configuration for the capture hardware (sensor, lens, + * flash), the processing pipeline, the control algorithms, and the output + * buffers.</p> + * + * <p>A {@code TotalCaptureResult} is produced by a {@link CameraDevice} after processing a + * {@link CaptureRequest}. All properties listed for capture requests can also + * be queried on the capture result, to determine the final values used for + * capture. The result also includes additional metadata about the state of the + * camera device during the capture.</p> + * + * <p>All properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()} + * are available (that is {@link CaptureResult#get} will return non-{@code null}, if and only if + * that key that was enabled by the request. A few keys such as + * {@link CaptureResult#STATISTICS_FACES} are disabled by default unless enabled with a switch (such + * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on + * a case-by-case basis.</p> + * + * <p>{@link TotalCaptureResult} objects are immutable.</p> + * + * @see CameraDevice.CaptureListener#onCaptureCompleted + */ +public final class TotalCaptureResult extends CaptureResult { + + /** + * Takes ownership of the passed-in properties object + * @hide + */ + public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) { + super(results, parent, sequenceId); + } + + /** + * Creates a request-less result. + * + * <p><strong>For testing only.</strong></p> + * @hide + */ + public TotalCaptureResult(CameraMetadataNative results, int sequenceId) { + super(results, sequenceId); + } + + /** + * Get the read-only list of partial results that compose this total result. + * + * <p>The list is returned is unmodifiable; attempting to modify it will result in a + * {@code UnsupportedOperationException} being thrown.</p> + * + * <p>The list size will be inclusive between {@code 1} and + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order + * of when {@link CameraDevice.CaptureListener#onCaptureProgressed} was invoked.</p> + * + * @return unmodifiable list of partial results + */ + public List<CaptureResult> getPartialResults() { + // TODO + return Collections.unmodifiableList(null); + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index b082a70..9a4c531 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -20,10 +20,13 @@ import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.LongParcelable; @@ -72,6 +75,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); private final String mCameraId; + private final CameraCharacteristics mCharacteristics; /** * A list tracking request and its expected last frame. @@ -150,13 +154,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } }; - public CameraDevice(String cameraId, StateListener listener, Handler handler) { + public CameraDevice(String cameraId, StateListener listener, Handler handler, + CameraCharacteristics characteristics) { if (cameraId == null || listener == null || handler == null) { throw new IllegalArgumentException("Null argument given"); } mCameraId = cameraId; mDeviceListener = listener; mDeviceHandler = handler; + mCharacteristics = characteristics; final int MAX_TAG_LEN = 23; String tag = String.format("CameraDevice-JV-%s", mCameraId); @@ -359,7 +365,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder.getListener().onCaptureSequenceCompleted( CameraDevice.this, requestId, - (int)lastFrameNumber); + lastFrameNumber); } } }; @@ -717,7 +723,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder.getListener().onCaptureSequenceCompleted( CameraDevice.this, requestId, - (int)lastFrameNumber); + lastFrameNumber); } } }; @@ -850,11 +856,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras) throws RemoteException { + int requestId = resultExtras.getRequestId(); if (DEBUG) { Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id " + requestId); } + + + // TODO: Handle CameraCharacteristics access from CaptureResult correctly. + result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, + getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); + final CaptureListenerHolder holder; synchronized (mLock) { holder = CameraDevice.this.mCaptureListenerMap.get(requestId); @@ -888,12 +901,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); - final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); + Runnable resultDispatch = null; // Either send a partial result or the final capture completed result if (quirkIsPartialResult) { + final CaptureResult resultAsCapture = + new CaptureResult(result, request, requestId); + // Partial result resultDispatch = new Runnable() { @Override @@ -907,6 +923,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } }; } else { + final TotalCaptureResult resultAsCapture = + new TotalCaptureResult(result, request, requestId); + // Final capture result resultDispatch = new Runnable() { @Override @@ -958,4 +977,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return (mRemoteDevice == null); } } + + private CameraCharacteristics getCharacteristics() { + return mCharacteristics; + } } diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index ab2c49a..dc0c652 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -43,13 +43,19 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration; import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Face; +import android.hardware.camera2.params.LensShadingMap; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.params.TonemapCurve; import android.hardware.camera2.utils.TypeReference; +import android.location.Location; +import android.location.LocationManager; import android.os.Parcelable; import android.os.Parcel; import android.util.Log; +import android.util.Pair; +import android.util.Size; import com.android.internal.util.Preconditions; @@ -57,6 +63,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; /** * Implementation of camera metadata marshal/unmarshal across Binder to @@ -209,6 +216,37 @@ public class CameraMetadataNative implements Parcelable { // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h public static final int NATIVE_JPEG_FORMAT = 0x21; + private static final String CELLID_PROCESS = "CELLID"; + private static final String GPS_PROCESS = "GPS"; + + private static String translateLocationProviderToProcess(final String provider) { + if (provider == null) { + return null; + } + switch(provider) { + case LocationManager.GPS_PROVIDER: + return GPS_PROCESS; + case LocationManager.NETWORK_PROVIDER: + return CELLID_PROCESS; + default: + return null; + } + } + + private static String translateProcessToLocationProvider(final String process) { + if (process == null) { + return null; + } + switch(process) { + case GPS_PROCESS: + return LocationManager.GPS_PROVIDER; + case CELLID_PROCESS: + return LocationManager.NETWORK_PROVIDER; + default: + return null; + } + } + public CameraMetadataNative() { super(); mMetadataPtr = nativeAllocate(); @@ -297,9 +335,9 @@ public class CameraMetadataNative implements Parcelable { public <T> T get(Key<T> key) { Preconditions.checkNotNull(key, "key must not be null"); - T value = getOverride(key); - if (value != null) { - return value; + Pair<T, Boolean> override = getOverride(key); + if (override.second) { + return override.first; } return getBase(key); @@ -409,23 +447,44 @@ public class CameraMetadataNative implements Parcelable { ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return marshaler.unmarshal(buffer); } - // Need overwrite some metadata that has different definitions between native // and managed sides. @SuppressWarnings("unchecked") - private <T> T getOverride(Key<T> key) { + private <T> Pair<T, Boolean> getOverride(Key<T> key) { + T value = null; + boolean override = true; + if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) { - return (T) getAvailableFormats(); + value = (T) getAvailableFormats(); } else if (key.equals(CaptureResult.STATISTICS_FACES)) { - return (T) getFaces(); + value = (T) getFaces(); } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { - return (T) getFaceRectangles(); + value = (T) getFaceRectangles(); } else if (key.equals(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)) { - return (T) getStreamConfigurationMap(); + value = (T) getStreamConfigurationMap(); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) { + value = (T) getTonemapCurve(); + } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) { + value = (T) getGpsLocation(); + } else if (key.equals(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)) { + value = (T) getLensShadingMap(); + } else { + override = false; } - // For other keys, get() falls back to getBase() - return null; + return Pair.create(value, override); } private int[] getAvailableFormats() { @@ -541,6 +600,62 @@ public class CameraMetadataNative implements Parcelable { return fixedFaceRectangles; } + private LensShadingMap getLensShadingMap() { + float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP); + if (lsmArray == null) { + Log.w(TAG, "getLensShadingMap - Lens shading map was null."); + return null; + } + Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE); + LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth()); + return map; + } + + private Location getGpsLocation() { + String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); + Location l = new Location(translateProcessToLocationProvider(processingMethod)); + + double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); + Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP); + + if (timeStamp != null) { + l.setTime(timeStamp); + } else { + Log.w(TAG, "getGpsLocation - No timestamp for GPS location."); + } + + if (coords != null) { + l.setLatitude(coords[0]); + l.setLongitude(coords[1]); + l.setAltitude(coords[2]); + } else { + Log.w(TAG, "getGpsLocation - No coordinates for GPS location"); + } + + return l; + } + + private boolean setGpsLocation(Location l) { + if (l == null) { + return false; + } + + double[] coords = { l.getLatitude(), l.getLongitude(), l.getAltitude() }; + String processMethod = translateLocationProviderToProcess(l.getProvider()); + long timestamp = l.getTime(); + + set(CaptureRequest.JPEG_GPS_TIMESTAMP, timestamp); + set(CaptureRequest.JPEG_GPS_COORDINATES, coords); + + if (processMethod == null) { + Log.w(TAG, "setGpsLocation - No process method, Location is not from a GPS or NETWORK" + + "provider"); + } else { + setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, processMethod); + } + return true; + } + private StreamConfigurationMap getStreamConfigurationMap() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); @@ -552,6 +667,63 @@ public class CameraMetadataNative implements Parcelable { return new StreamConfigurationMap(configurations, minFrameDurations, stallDurations); } + private <T> Integer getMaxRegions(Key<T> key) { + final int AE = 0; + final int AWB = 1; + final int AF = 2; + + // The order of the elements is: (AE, AWB, AF) + int[] maxRegions = getBase(CameraCharacteristics.CONTROL_MAX_REGIONS); + + if (maxRegions == null) { + return null; + } + + if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) { + return maxRegions[AE]; + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) { + return maxRegions[AWB]; + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) { + return maxRegions[AF]; + } else { + throw new AssertionError("Invalid key " + key); + } + } + + private <T> Integer getMaxNumOutputs(Key<T> key) { + final int RAW = 0; + final int PROC = 1; + final int PROC_STALLING = 2; + + // The order of the elements is: (raw, proc+nonstalling, proc+stalling) + int[] maxNumOutputs = getBase(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS); + + if (maxNumOutputs == null) { + return null; + } + + if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) { + return maxNumOutputs[RAW]; + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) { + return maxNumOutputs[PROC]; + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) { + return maxNumOutputs[PROC_STALLING]; + } else { + throw new AssertionError("Invalid key " + key); + } + } + + private <T> TonemapCurve getTonemapCurve() { + float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED); + float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN); + float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE); + if (red == null || green == null || blue == null) { + return null; + } + TonemapCurve tc = new TonemapCurve(red, green, blue); + return tc; + } + private <T> void setBase(CameraCharacteristics.Key<T> key, T value) { setBase(key.getNativeKey(), value); } @@ -591,8 +763,11 @@ public class CameraMetadataNative implements Parcelable { return setAvailableFormats((int[]) value); } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { return setFaceRectangles((Rect[]) value); + } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) { + return setTonemapCurve((TonemapCurve) value); + } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) { + return setGpsLocation((Location) value); } - // For other keys, set() falls back to setBase(). return false; } @@ -646,6 +821,24 @@ public class CameraMetadataNative implements Parcelable { return true; } + private <T> boolean setTonemapCurve(TonemapCurve tc) { + if (tc == null) { + return false; + } + + float[][] curve = new float[3][]; + for (int i = TonemapCurve.CHANNEL_RED; i <= TonemapCurve.CHANNEL_BLUE; i++) { + int pointCount = tc.getPointCount(i); + curve[i] = new float[pointCount * TonemapCurve.POINT_SIZE]; + tc.copyColorCurve(i, curve[i], 0); + } + setBase(CaptureRequest.TONEMAP_CURVE_RED, curve[0]); + setBase(CaptureRequest.TONEMAP_CURVE_GREEN, curve[1]); + setBase(CaptureRequest.TONEMAP_CURVE_BLUE, curve[2]); + + return true; + } + private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); diff --git a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java index fa8c8ea..b4289db 100644 --- a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java +++ b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java @@ -139,8 +139,8 @@ public final class ColorSpaceTransform { throw new IllegalArgumentException("row out of range"); } - int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR]; - int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR]; + int numerator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_NUMERATOR]; + int denominator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_DENOMINATOR]; return new Rational(numerator, denominator); } @@ -162,7 +162,7 @@ public final class ColorSpaceTransform { public void copyElements(Rational[] destination, int offset) { checkArgumentNonnegative(offset, "offset must not be negative"); checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT) { + if (destination.length - offset < COUNT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } @@ -197,7 +197,7 @@ public final class ColorSpaceTransform { public void copyElements(int[] destination, int offset) { checkArgumentNonnegative(offset, "offset must not be negative"); checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT_INT) { + if (destination.length - offset < COUNT_INT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java index b328f57..9bbc33a 100644 --- a/core/java/android/hardware/camera2/params/LensShadingMap.java +++ b/core/java/android/hardware/camera2/params/LensShadingMap.java @@ -28,7 +28,7 @@ import java.util.Arrays; /** * Immutable class for describing a {@code 4 x N x M} lens shading map of floats. * - * @see CameraCharacteristics#LENS_SHADING_MAP + * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP */ public final class LensShadingMap { @@ -62,12 +62,12 @@ public final class LensShadingMap { public LensShadingMap(final float[] elements, final int rows, final int columns) { mRows = checkArgumentPositive(rows, "rows must be positive"); - mColumns = checkArgumentPositive(rows, "columns must be positive"); + mColumns = checkArgumentPositive(columns, "columns must be positive"); mElements = checkNotNull(elements, "elements must not be null"); if (elements.length != getGainFactorCount()) { throw new IllegalArgumentException("elements must be " + getGainFactorCount() + - " length"); + " length, received " + elements.length); } // Every element must be finite and >= 1.0f @@ -242,4 +242,4 @@ public final class LensShadingMap { private final int mRows; private final int mColumns; private final float[] mElements; -}; +} diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java index a26c57d..a7a3b59 100644 --- a/core/java/android/hardware/camera2/params/MeteringRectangle.java +++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java @@ -57,7 +57,7 @@ public final class MeteringRectangle { * @param height height >= 0 * @param meteringWeight weight >= 0 * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws IllegalArgumentException if any of the parameters were negative */ public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) { mX = checkArgumentNonnegative(x, "x must be nonnegative"); @@ -74,7 +74,7 @@ public final class MeteringRectangle { * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0 * @param meteringWeight weight >= 0 * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws IllegalArgumentException if any of the parameters were negative * @throws NullPointerException if any of the arguments were null */ public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) { @@ -94,7 +94,7 @@ public final class MeteringRectangle { * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0 * @param meteringWeight weight >= 0 * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws IllegalArgumentException if any of the parameters were negative * @throws NullPointerException if any of the arguments were null */ public MeteringRectangle(Rect rect, int meteringWeight) { @@ -210,7 +210,7 @@ public final class MeteringRectangle { && mY == other.mY && mWidth == other.mWidth && mHeight == other.mHeight - && mWidth == other.mWidth); + && mWeight == other.mWeight); } /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4bccaf1..3417de1 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,6 +39,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.view.Gravity; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -679,7 +680,7 @@ public class InputMethodService extends AbstractInputMethodService { mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, - false); + WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); if (mHardwareAccelerated) { mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index a9bace1..38a65c5 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -37,6 +37,8 @@ public class SoftInputWindow extends Dialog { final Callback mCallback; final KeyEvent.Callback mKeyEventCallback; final KeyEvent.DispatcherState mDispatcherState; + final int mWindowType; + final int mGravity; final boolean mTakesFocus; private final Rect mBounds = new Rect(); @@ -64,12 +66,14 @@ public class SoftInputWindow extends Dialog { */ public SoftInputWindow(Context context, String name, int theme, Callback callback, KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState, - boolean takesFocus) { + int windowType, int gravity, boolean takesFocus) { super(context, theme); mName = name; mCallback = callback; mKeyEventCallback = keyEventCallback; mDispatcherState = dispatcherState; + mWindowType = windowType; + mGravity = gravity; mTakesFocus = takesFocus; initDockWindow(); } @@ -97,47 +101,6 @@ public class SoftInputWindow extends Dialog { } /** - * Get the size of the DockWindow. - * - * @return If the DockWindow sticks to the top or bottom of the screen, the - * return value is the height of the DockWindow, and its width is - * equal to the width of the screen; If the DockWindow sticks to the - * left or right of the screen, the return value is the width of the - * DockWindow, and its height is equal to the height of the screen. - */ - public int getSize() { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - - if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { - return lp.height; - } else { - return lp.width; - } - } - - /** - * Set the size of the DockWindow. - * - * @param size If the DockWindow sticks to the top or bottom of the screen, - * <var>size</var> is the height of the DockWindow, and its width is - * equal to the width of the screen; If the DockWindow sticks to the - * left or right of the screen, <var>size</var> is the width of the - * DockWindow, and its height is equal to the height of the screen. - */ - public void setSize(int size) { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - - if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { - lp.width = -1; - lp.height = size; - } else { - lp.width = size; - lp.height = -1; - } - getWindow().setAttributes(lp); - } - - /** * Set which boundary of the screen the DockWindow sticks to. * * @param gravity The boundary of the screen to stick. See {#link @@ -147,18 +110,18 @@ public class SoftInputWindow extends Dialog { */ public void setGravity(int gravity) { WindowManager.LayoutParams lp = getWindow().getAttributes(); - - boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM); - lp.gravity = gravity; + updateWidthHeight(lp); + getWindow().setAttributes(lp); + } - boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM); - - if (oldIsVertical != newIsVertical) { - int tmp = lp.width; - lp.width = lp.height; - lp.height = tmp; - getWindow().setAttributes(lp); + private void updateWidthHeight(WindowManager.LayoutParams lp) { + if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + } else { + lp.width = WindowManager.LayoutParams.WRAP_CONTENT; + lp.height = WindowManager.LayoutParams.MATCH_PARENT; } } @@ -201,14 +164,11 @@ public class SoftInputWindow extends Dialog { private void initDockWindow() { WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD; + lp.type = mWindowType; lp.setTitle(mName); - lp.gravity = Gravity.BOTTOM; - lp.width = -1; - // Let the input method window's orientation follow sensor based rotation - // Turn this off for now, it is very problematic. - //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + lp.gravity = mGravity; + updateWidthHeight(lp); getWindow().setAttributes(lp); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 80a9598..2f2aba3 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -35,15 +35,17 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.telephony.ITelephony; +import com.android.internal.util.Protocol; + import java.net.InetAddress; import java.util.concurrent.atomic.AtomicInteger; import java.util.HashMap; -import com.android.internal.util.Protocol; - /** * Class that answers queries about the state of network connectivity. It also * notifies applications when network connectivity changes. Get an instance @@ -940,34 +942,18 @@ public class ConnectivityManager { } /** - * Gets the value of the setting for enabling Mobile data. - * - * @return Whether mobile data is enabled. - * - * <p>This method requires the call to hold the permission - * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * @hide + * @deprecated Talk to TelephonyManager directly */ public boolean getMobileDataEnabled() { - try { - return mService.getMobileDataEnabled(); - } catch (RemoteException e) { - return true; - } - } - - /** - * Sets the persisted value for enabling/disabling Mobile data. - * - * @param enabled Whether the user wants the mobile data connection used - * or not. - * @hide - */ - public void setMobileDataEnabled(boolean enabled) { - try { - mService.setMobileDataEnabled(enabled); - } catch (RemoteException e) { + IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE); + if (b != null) { + try { + ITelephony it = ITelephony.Stub.asInterface(b); + return it.getDataEnabled(); + } catch (RemoteException e) { } } + return false; } /** diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index d97b1e9..baec36a 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -74,9 +74,6 @@ interface IConnectivityManager boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName); - boolean getMobileDataEnabled(); - void setMobileDataEnabled(boolean enabled); - /** Policy control over specific {@link NetworkStateTracker}. */ void setPolicyDataEnable(int networkType, boolean enabled); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index c2b06a2..1c18ba5 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -257,31 +257,43 @@ public abstract class NetworkAgent extends Handler { } /** - * called to go through our list of requests and see if we're - * good enough to try connecting. + * Called to go through our list of requests and see if we're + * good enough to try connecting, or if we have gotten worse and + * need to disconnect. * - * Only does connects - we disconnect when requested via + * Once we are registered, does nothing: we disconnect when requested via * CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection * between modules (bearer or ConnectivityService dies) or more commonly * when the NetworkInfo reports to ConnectivityService it is disconnected. */ private void evalScores() { - if (mConnectionRequested) { - if (VDBG) log("evalScores - already trying - size=" + mNetworkRequests.size()); - // already trying - return; - } - if (VDBG) log("evalScores!"); - for (int i=0; i < mNetworkRequests.size(); i++) { - int score = mNetworkRequests.valueAt(i).score; - if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore); - if (score < mNetworkScore) { - // have a request that has a lower scored network servicing it - // (or no network) than we could provide, so lets connect! - mConnectionRequested = true; - connect(); + synchronized(mLockObj) { + if (mRegistered) { + if (VDBG) log("evalScores - already connected - size=" + mNetworkRequests.size()); + // already trying return; } + if (VDBG) log("evalScores!"); + for (int i=0; i < mNetworkRequests.size(); i++) { + int score = mNetworkRequests.valueAt(i).score; + if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore); + if (score < mNetworkScore) { + // have a request that has a lower scored network servicing it + // (or no network) than we could provide, so let's connect! + mConnectionRequested = true; + connect(); + return; + } + } + // Our score is not high enough to satisfy any current request. + // This can happen if our score goes down after a connection is + // requested but before we actually connect. In this case, disconnect + // rather than continue trying - there's no point connecting if we know + // we'll just be torn down as soon as we do. + if (mConnectionRequested) { + mConnectionRequested = false; + disconnect(); + } } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d3c6fd5..bc57b33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3303,7 +3303,8 @@ public abstract class BatteryStats implements Parcelable { if (rec.time >= histStart) { if (histStart >= 0 && !printed) { if (rec.cmd == HistoryItem.CMD_CURRENT_TIME - || rec.cmd == HistoryItem.CMD_RESET) { + || rec.cmd == HistoryItem.CMD_RESET + || rec.cmd == HistoryItem.CMD_START) { printed = true; hprinter.printNextItem(pw, rec, baseTime, checkin, (flags&DUMP_VERBOSE) != 0); diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index c1d4d4c..cb0f142 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -74,6 +74,7 @@ public abstract class Vibrator { * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type. * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls. + * @hide */ public void vibrate(long milliseconds, int streamHint) { vibrate(Process.myUid(), mPackageName, milliseconds, streamHint); @@ -125,6 +126,7 @@ public abstract class Vibrator { * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type. * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls. + * @hide */ public void vibrate(long[] pattern, int repeat, int streamHint) { vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4963991..68b91cb 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -58,24 +58,6 @@ import java.util.concurrent.atomic.AtomicInteger; * argument of {@link android.content.Context#STORAGE_SERVICE}. */ public class StorageManager { - - /// Consts to match the password types in cryptfs.h - /** Master key is encrypted with a password. - */ - public static final int CRYPT_TYPE_PASSWORD = 0; - - /** Master key is encrypted with the default password. - */ - public static final int CRYPT_TYPE_DEFAULT = 1; - - /** Master key is encrypted with a pattern. - */ - public static final int CRYPT_TYPE_PATTERN = 2; - - /** Master key is encrypted with a pin. - */ - public static final int CRYPT_TYPE_PIN = 3; - private static final String TAG = "StorageManager"; private final ContentResolver mResolver; @@ -663,4 +645,14 @@ public class StorageManager { return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES, DEFAULT_FULL_THRESHOLD_BYTES); } + + /// Consts to match the password types in cryptfs.h + /** @hide */ + public static final int CRYPT_TYPE_PASSWORD = 0; + /** @hide */ + public static final int CRYPT_TYPE_DEFAULT = 1; + /** @hide */ + public static final int CRYPT_TYPE_PATTERN = 2; + /** @hide */ + public static final int CRYPT_TYPE_PIN = 3; } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index d2d6ade..5e005d0 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -37,20 +37,22 @@ import android.widget.SeekBar.OnSeekBarChangeListener; * @hide */ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback { + private static final String TAG = "SeekBarVolumizer"; public interface Callback { void onSampleStarting(SeekBarVolumizer sbv); } - private Context mContext; - private Handler mHandler; + private final Context mContext; + private final Handler mHandler; private final Callback mCallback; + private final Uri mDefaultUri; + private final AudioManager mAudioManager; + private final int mStreamType; + private final int mMaxStreamVolume; - private AudioManager mAudioManager; - private int mStreamType; private int mOriginalStreamVolume; private Ringtone mRingtone; - private int mLastProgress = -1; private SeekBar mSeekBar; private int mVolumeBeforeMute = -1; @@ -58,9 +60,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private static final int MSG_SET_STREAM_VOLUME = 0; private static final int MSG_START_SAMPLE = 1; private static final int MSG_STOP_SAMPLE = 2; + private static final int MSG_INIT_SAMPLE = 3; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; - private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { + private ContentObserver mVolumeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); @@ -71,27 +74,17 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } }; - public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri, + public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) { mContext = context; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mStreamType = streamType; - mSeekBar = seekBar; - - HandlerThread thread = new HandlerThread(VolumePreference.TAG + ".CallbackHandler"); + mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType); + HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); thread.start(); mHandler = new Handler(thread.getLooper(), this); mCallback = callback; - - initSeekBar(seekBar, defaultUri); - } - - private void initSeekBar(SeekBar seekBar, Uri defaultUri) { - seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType)); mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); - seekBar.setProgress(mOriginalStreamVolume); - seekBar.setOnSeekBarChangeListener(this); - mContext.getContentResolver().registerContentObserver( System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver); @@ -105,12 +98,16 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI; } } + mDefaultUri = defaultUri; + mHandler.sendEmptyMessage(MSG_INIT_SAMPLE); + } - mRingtone = RingtoneManager.getRingtone(mContext, defaultUri); - - if (mRingtone != null) { - mRingtone.setStreamType(mStreamType); - } + public void setSeekBar(SeekBar seekBar) { + mSeekBar = seekBar; + mSeekBar.setOnSeekBarChangeListener(null); + mSeekBar.setMax(mMaxStreamVolume); + mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume); + mSeekBar.setOnSeekBarChangeListener(this); } @Override @@ -125,12 +122,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba case MSG_STOP_SAMPLE: onStopSample(); break; + case MSG_INIT_SAMPLE: + onInitSample(); + break; default: - Log.e(VolumePreference.TAG, "invalid SeekBarVolumizer message: "+msg.what); + Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); } return true; } + private void onInitSample() { + mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri); + if (mRingtone != null) { + mRingtone.setStreamType(mStreamType); + } + } + private void postStartSample() { mHandler.removeMessages(MSG_START_SAMPLE); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE), diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index 171e5c3..df9e10e 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -66,7 +66,8 @@ public class VolumePreference extends SeekBarDialogPreference implements super.onBindDialogView(view); final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); - mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType, null, this); + mSeekBarVolumizer = new SeekBarVolumizer(getContext(), mStreamType, null, this); + mSeekBarVolumizer.setSeekBar(seekBar); getPreferenceManager().registerOnActivityStopListener(this); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index a83544d..cd357b7 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Rect; import android.graphics.Region; import android.inputmethodservice.SoftInputWindow; import android.os.Binder; @@ -32,6 +33,7 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -262,14 +264,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { */ public static final class Insets { /** - * This is the top part of the UI that is the main content. It is + * This is the part of the UI that is the main content. It is * used to determine the basic space needed, to resize/pan the * application behind. It is assumed that this inset does not * change very much, since any change will cause a full resize/pan * of the application behind. This value is relative to the top edge * of the input method window. */ - public int contentTopInsets; + public final Rect contentInsets = new Rect(); /** * This is the region of the UI that is touchable. It is used when @@ -311,7 +313,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { new ViewTreeObserver.OnComputeInternalInsetsListener() { public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { onComputeInsets(mTmpInsets); - info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets; + info.contentInsets.set(mTmpInsets.contentInsets); + info.visibleInsets.set(mTmpInsets.contentInsets); info.touchableRegion.set(mTmpInsets.touchableRegion); info.setTouchableInsets(mTmpInsets.touchableInsets); } @@ -428,6 +431,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { throw new IllegalStateException("Can't call before onCreate()"); } try { + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); int res = mSystemService.startVoiceActivity(mToken, intent, intent.resolveType(mContext.getContentResolver())); Instrumentation.checkStartActivityResult(res, intent); @@ -460,7 +465,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mInflater = (LayoutInflater)mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, - mCallbacks, this, mDispatcherState, true); + mCallbacks, this, mDispatcherState, + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true); mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); @@ -517,7 +523,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { int[] loc = mTmpLocation; View decor = getWindow().getWindow().getDecorView(); decor.getLocationInWindow(loc); - outInsets.contentTopInsets = loc[1]; + outInsets.contentInsets.top = 0; + outInsets.contentInsets.left = 0; + outInsets.contentInsets.right = 0; + outInsets.contentInsets.bottom = 0; outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; outInsets.touchableRegion.setEmpty(); } diff --git a/core/java/android/tv/ITvInputHardware.aidl b/core/java/android/tv/ITvInputHardware.aidl new file mode 100644 index 0000000..7250453 --- /dev/null +++ b/core/java/android/tv/ITvInputHardware.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +import android.tv.TvStreamConfig; +import android.view.KeyEvent; +import android.view.Surface; + +/** + * TvInputService representing a physical port should connect to HAL through this interface. + * Framework will take care of communication among system services including TvInputManagerService, + * HdmiControlService, AudioService, etc. + * + * @hide + */ +interface ITvInputHardware { + /** + * Make the input render on the surface according to the config. In case of HDMI, this will + * trigger CEC commands for adjusting active HDMI source. Returns true on success. + */ + boolean setSurface(in Surface surface, in TvStreamConfig config); + /** + * Set volume for this stream via AudioGain. (TBD) + */ + void setVolume(float volume); + + /** + * Dispatch key event to HDMI service. The events would be automatically converted to + * HDMI CEC commands. If the hardware is not representing an HDMI port, this method will fail. + */ + boolean dispatchKeyEventToHdmi(in KeyEvent event); +} diff --git a/core/java/android/tv/ITvInputHardwareCallback.aidl b/core/java/android/tv/ITvInputHardwareCallback.aidl new file mode 100644 index 0000000..83041be --- /dev/null +++ b/core/java/android/tv/ITvInputHardwareCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +import android.tv.TvStreamConfig; + +/** + * @hide + */ +oneway interface ITvInputHardwareCallback { + void onReleased(); + void onStreamConfigChanged(in TvStreamConfig[] configs); +} diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl index b756aba..c6f8d79 100644 --- a/core/java/android/tv/ITvInputManager.aidl +++ b/core/java/android/tv/ITvInputManager.aidl @@ -19,7 +19,10 @@ package android.tv; import android.content.ComponentName; import android.graphics.Rect; import android.net.Uri; +import android.tv.ITvInputHardware; +import android.tv.ITvInputHardwareCallback; import android.tv.ITvInputClient; +import android.tv.TvInputHardwareInfo; import android.tv.TvInputInfo; import android.view.Surface; @@ -46,4 +49,10 @@ interface ITvInputManager { int userId); void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId); void removeOverlayView(in IBinder sessionToken, int userId); + + // For TV input hardware binding + List<TvInputHardwareInfo> getHardwareList(); + ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback, + int userId); + void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId); } diff --git a/core/java/android/tv/TvInputHardwareInfo.aidl b/core/java/android/tv/TvInputHardwareInfo.aidl new file mode 100644 index 0000000..484ab60 --- /dev/null +++ b/core/java/android/tv/TvInputHardwareInfo.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +parcelable TvInputHardwareInfo; diff --git a/core/java/android/tv/TvInputHardwareInfo.java b/core/java/android/tv/TvInputHardwareInfo.java new file mode 100644 index 0000000..b0dc58e --- /dev/null +++ b/core/java/android/tv/TvInputHardwareInfo.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * Simple container for information about TV input hardware. + * Not for third-party developers. + * + * @hide + */ +public final class TvInputHardwareInfo implements Parcelable { + static final String TAG = "TvInputHardwareInfo"; + + // Match hardware/libhardware/include/hardware/tv_input.h + public static final int TV_INPUT_TYPE_HDMI = 1; + public static final int TV_INPUT_TYPE_BUILT_IN_TUNER = 2; + public static final int TV_INPUT_TYPE_PASSTHROUGH = 3; + + public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR = + new Parcelable.Creator<TvInputHardwareInfo>() { + @Override + public TvInputHardwareInfo createFromParcel(Parcel source) { + try { + TvInputHardwareInfo info = new TvInputHardwareInfo(); + info.readFromParcel(source); + return info; + } catch (Exception e) { + Log.e(TAG, "Exception creating TvInputHardwareInfo from parcel", e); + return null; + } + } + + @Override + public TvInputHardwareInfo[] newArray(int size) { + return new TvInputHardwareInfo[size]; + } + }; + + private int mDeviceId; + private int mType; + // TODO: Add audio port & audio address for audio service. + // TODO: Add HDMI handle for HDMI service. + + public TvInputHardwareInfo() { } + + public TvInputHardwareInfo(int deviceId, int type) { + mDeviceId = deviceId; + mType = type; + } + + public int getDeviceId() { + return mDeviceId; + } + + public int getType() { + return mType; + } + + // Parcelable + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDeviceId); + dest.writeInt(mType); + } + + public void readFromParcel(Parcel source) { + mDeviceId = source.readInt(); + mType = source.readInt(); + } +} diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java index 50462cc..217e4b7 100644 --- a/core/java/android/tv/TvInputInfo.java +++ b/core/java/android/tv/TvInputInfo.java @@ -39,7 +39,7 @@ public final class TvInputInfo implements Parcelable { public TvInputInfo(ResolveInfo service) { mService = service; ServiceInfo si = service.serviceInfo; - mId = generateInputIdForComponenetName(new ComponentName(si.packageName, si.name)); + mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name)); } /** @@ -134,7 +134,7 @@ public final class TvInputInfo implements Parcelable { * @return the generated input id for the given {@code name}. * @hide */ - public static final String generateInputIdForComponenetName(ComponentName name) { + public static final String generateInputIdForComponentName(ComponentName name) { return name.flattenToShortString(); } diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java index eeb738d..cb0142f 100644 --- a/core/java/android/tv/TvInputService.java +++ b/core/java/android/tv/TvInputService.java @@ -69,7 +69,7 @@ public abstract class TvInputService extends Service { @Override public void onCreate() { super.onCreate(); - mId = TvInputInfo.generateInputIdForComponenetName( + mId = TvInputInfo.generateInputIdForComponentName( new ComponentName(getPackageName(), getClass().getName())); } diff --git a/core/java/android/tv/TvStreamConfig.aidl b/core/java/android/tv/TvStreamConfig.aidl new file mode 100644 index 0000000..4d0add4 --- /dev/null +++ b/core/java/android/tv/TvStreamConfig.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +parcelable TvStreamConfig;
\ No newline at end of file diff --git a/core/java/android/tv/TvStreamConfig.java b/core/java/android/tv/TvStreamConfig.java new file mode 100644 index 0000000..03e63b1 --- /dev/null +++ b/core/java/android/tv/TvStreamConfig.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tv; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * @hide + */ +public class TvStreamConfig implements Parcelable { + static final String TAG = TvStreamConfig.class.getSimpleName(); + + public final static int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1; + public final static int STREAM_TYPE_BUFFER_PRODUCER = 2; + + private int mStreamId; + private int mType; + // TODO: Revisit if max widht/height really make sense. + private int mMaxWidth; + private int mMaxHeight; + /** + * Generations are incremented once framework receives STREAM_CONFIGURATION_CHANGED event from + * HAL module. Framework should throw away outdated configurations and get new configurations + * via tv_input_device::get_stream_configurations(). + */ + private int mGeneration; + + public static final Parcelable.Creator<TvStreamConfig> CREATOR = + new Parcelable.Creator<TvStreamConfig>() { + @Override + public TvStreamConfig createFromParcel(Parcel source) { + try { + return new Builder(). + streamId(source.readInt()). + type(source.readInt()). + maxWidth(source.readInt()). + maxHeight(source.readInt()). + generation(source.readInt()).build(); + } catch (Exception e) { + Log.e(TAG, "Exception creating TvStreamConfig from parcel", e); + return null; + } + } + + @Override + public TvStreamConfig[] newArray(int size) { + return new TvStreamConfig[size]; + } + }; + + private TvStreamConfig() {} + + public int getStreamId() { + return mStreamId; + } + + public int getType() { + return mType; + } + + public int getMaxWidth() { + return mMaxWidth; + } + + public int getMaxHeight() { + return mMaxHeight; + } + + public int getGeneration() { + return mGeneration; + } + + // Parcelable + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStreamId); + dest.writeInt(mType); + dest.writeInt(mMaxWidth); + dest.writeInt(mMaxHeight); + dest.writeInt(mGeneration); + } + + /** + * A helper class for creating a TvStreamConfig object. + */ + public static final class Builder { + private Integer mStreamId; + private Integer mType; + private Integer mMaxWidth; + private Integer mMaxHeight; + private Integer mGeneration; + + public Builder() { + } + + public Builder streamId(int streamId) { + mStreamId = streamId; + return this; + } + + public Builder type(int type) { + mType = type; + return this; + } + + public Builder maxWidth(int maxWidth) { + mMaxWidth = maxWidth; + return this; + } + + public Builder maxHeight(int maxHeight) { + mMaxHeight = maxHeight; + return this; + } + + public Builder generation(int generation) { + mGeneration = generation; + return this; + } + + public TvStreamConfig build() { + if (mStreamId == null || mType == null || mMaxWidth == null || mMaxHeight == null + || mGeneration == null) { + throw new UnsupportedOperationException(); + } + + TvStreamConfig config = new TvStreamConfig(); + config.mStreamId = mStreamId; + config.mType = mType; + config.mMaxWidth = mMaxWidth; + config.mMaxHeight = mMaxHeight; + config.mGeneration = mGeneration; + return config; + } + } +}
\ No newline at end of file diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 0d5df99..af16185 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -79,7 +79,7 @@ interface IWindowManager void removeWindowToken(IBinder token); void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, - int configChanges); + int configChanges, boolean voiceInteraction); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 8417887..9c9a939 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -33,8 +33,6 @@ import java.io.PrintWriter; /** * Hardware renderer that proxies the rendering to a render thread. Most calls * are currently synchronous. - * TODO: Make draw() async. - * TODO: Figure out how to share the DisplayList between two threads (global lock?) * * The UI thread can block on the RenderThread, but RenderThread must never * block on the UI thread. @@ -117,7 +115,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void destroyHardwareResources(View view) { destroyResources(view); - // TODO: GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); + nFlushCaches(mNativeProxy, GLES20Canvas.FLUSH_CACHES_LAYERS); } private static void destroyResources(View view) { @@ -368,6 +366,8 @@ public class ThreadedRenderer extends HardwareRenderer { private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); private static native void nDestroyLayer(long nativeProxy, long layer); + private static native void nFlushCaches(long nativeProxy, int flushMode); + private static native void nFence(long nativeProxy); private static native void nNotifyFramePending(long nativeProxy); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0f21c1d..025cf69 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10687,8 +10687,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link Drawable#getOutline(Outline)}. Manually setting the Outline with this method allows * this behavior to be overridden. * <p> - * If the outline is empty or is null, shadows will be cast from the - * bounds of the View. + * If the outline is {@link Outline#isEmpty()} or is <code>null</code>, + * shadows will not be cast. * <p> * Only outlines that return true from {@link Outline#canClip()} may be used for clipping. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b821a3e..0f40ee7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -456,6 +456,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // views during a transition when they otherwise would have become gone/invisible private ArrayList<View> mVisibilityChangingChildren; + // Temporary holder of presorted children, only used for + // input/software draw dispatch for correctly Z ordering. + private ArrayList<View> mPreSortedChildren; + // Indicates how many of this container's child subtrees contain transient state @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; @@ -1499,13 +1503,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float y = event.getY(); final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final boolean customChildOrder = isChildrenDrawingOrderEnabled(); + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; HoverTarget lastHoverTarget = null; for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customChildOrder - ? getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1572,6 +1578,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager break; } } + if (preorderedList != null) preorderedList.clear(); } } @@ -1778,23 +1785,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final View[] children = mChildren; final float x = event.getX(); final float y = event.getY(); - final boolean customOrder = isChildrenDrawingOrderEnabled(); + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (dispatchTransformedGenericPointerEvent(event, child)) { + if (preorderedList != null) preorderedList.clear(); return true; } } + if (preorderedList != null) preorderedList.clear(); } // No child handled the event. Send it to this view group. @@ -1910,13 +1922,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; - - final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customOrder ? - getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + final int childIndex = customOrder + ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1934,7 +1948,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); - mLastTouchDownIndex = childIndex; + if (preorderedList != null) { + // childIndex points into presorted list, find original index + for (int j = 0; j < childrenCount; j++) { + if (children[childIndex] == mChildren[j]) { + mLastTouchDownIndex = j; + break; + } + } + } else { + mLastTouchDownIndex = childIndex; + } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); @@ -1942,6 +1966,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager break; } } + if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { @@ -2928,7 +2953,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override protected void dispatchDraw(Canvas canvas) { - final int count = mChildrenCount; + final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; @@ -2936,15 +2961,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; final boolean buildCache = !isHardwareAccelerated(); - for (int i = 0; i < count; i++) { + for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); - attachLayoutAnimationParameters(child, params, i, count); + attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); - if (buildCache) { + if (buildCache) { child.buildDrawingCache(true); } } @@ -2997,21 +3022,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean more = false; final long drawingTime = getDrawingTime(); - if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { - for (int i = 0; i < count; i++) { - final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - more |= drawChild(canvas, child, drawingTime); - } - } - } else { - for (int i = 0; i < count; i++) { - final View child = children[getChildDrawingOrder(count, i)]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - more |= drawChild(canvas, child, drawingTime); - } + + // Only use the preordered list if not HW accelerated, since the HW pipeline will do the + // draw reordering internally + final ArrayList<View> preorderedList = canvas.isHardwareAccelerated() + ? null : buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + for (int i = 0; i < childrenCount; i++) { + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { + more |= drawChild(canvas, child, drawingTime); } } + if (preorderedList != null) preorderedList.clear(); // Draw any disappearing views that have animations if (mDisappearingChildren != null) { @@ -3096,6 +3122,47 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return i; } + private boolean hasChildWithZ() { + for (int i = 0; i < mChildrenCount; i++) { + if (mChildren[i].getZ() != 0) return true; + } + return false; + } + + /** + * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children, + * sorted first by Z, then by child drawing order (if applicable). + * + * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated + * children. + */ + private ArrayList<View> buildOrderedChildList() { + final int count = mChildrenCount; + if (count <= 1 || !hasChildWithZ()) return null; + + if (mPreSortedChildren == null) { + mPreSortedChildren = new ArrayList<View>(count); + } else { + mPreSortedChildren.ensureCapacity(count); + } + + final boolean useCustomOrder = isChildrenDrawingOrderEnabled(); + for (int i = 0; i < mChildrenCount; i++) { + // add next child (in child order) to end of list + int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i; + View nextChild = mChildren[childIndex]; + float currentZ = nextChild.getZ(); + + // insert ahead of any Views with greater Z + int insertIndex = i; + while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { + insertIndex--; + } + mPreSortedChildren.add(insertIndex, nextChild); + } + return mPreSortedChildren; + } + private void notifyAnimationListener() { mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER; mGroupFlags |= FLAG_ANIMATION_DONE; diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 3104862..af1de78 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -253,9 +253,10 @@ public class ViewPropertyAnimator { ViewPropertyAnimator(View view) { mView = view; view.ensureTransformationInfo(); - if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { - mRTBackend = new ViewPropertyAnimatorRT(view); - } + // TODO: Disabled because of b/15287046 + //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { + // mRTBackend = new ViewPropertyAnimatorRT(view); + //} } /** @@ -1142,7 +1143,8 @@ public class ViewPropertyAnimator { // Shouldn't happen, but just to play it safe return; } - boolean useRenderNodeProperties = mView.mRenderNode != null; + + boolean hardwareAccelerated = mView.isHardwareAccelerated(); // alpha requires slightly different treatment than the other (transform) properties. // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation @@ -1150,13 +1152,13 @@ public class ViewPropertyAnimator { // We track what kinds of properties are set, and how alpha is handled when it is // set, and perform the invalidation steps appropriately. boolean alphaHandled = false; - if (!useRenderNodeProperties) { + if (!hardwareAccelerated) { mView.invalidateParentCaches(); } float fraction = animation.getAnimatedFraction(); int propertyMask = propertyBundle.mPropertyMask; if ((propertyMask & TRANSFORM_MASK) != 0) { - mView.invalidateViewProperty(false, false); + mView.invalidateViewProperty(hardwareAccelerated, false); } ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; if (valueList != null) { @@ -1172,7 +1174,7 @@ public class ViewPropertyAnimator { } } if ((propertyMask & TRANSFORM_MASK) != 0) { - if (!useRenderNodeProperties) { + if (!hardwareAccelerated) { mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 031ad80..4eecc6a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -218,7 +218,8 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"), @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"), @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"), - @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION") + @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"), + @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"), }) public int type; @@ -541,6 +542,12 @@ public interface WindowManager extends ViewManager { public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30; /** + * Window type: Windows in the voice interaction layer. + * @hide + */ + public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index cfb40da..5d36ee4 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -274,6 +274,11 @@ public interface WindowManagerPolicy { public IApplicationToken getAppToken(); /** + * Return true if this window is participating in voice interaction. + */ + public boolean isVoiceInteraction(); + + /** * Return true if, at any point, the application token associated with * this window has actually displayed any windows. This is most useful * with the "starting up" window to determine if any windows were diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 51759c5..1fddf3e 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -544,6 +544,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter public void setMenuView(ActionMenuView menuView) { mMenuView = menuView; + menuView.initialize(mMenu); } private static class SavedState implements Parcelable { diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 3975edf..a9a5eae 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -69,6 +69,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo /** @hide */ public void setPresenter(ActionMenuPresenter presenter) { mPresenter = presenter; + mPresenter.setMenuView(this); } @Override @@ -488,7 +489,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mPresenter.dismissPopupMenus(); + dismissPopupMenus(); } /** @hide */ @@ -578,6 +579,56 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } /** + * Returns the current menu or null if one has not yet been configured. + * @hide Internal use only for action bar integration + */ + public MenuBuilder peekMenu() { + return mMenu; + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mPresenter != null && mPresenter.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mPresenter != null && mPresenter.hideOverflowMenu(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mPresenter != null && mPresenter.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mPresenter != null && mPresenter.isOverflowMenuShowPending(); + } + + /** + * Dismiss any popups associated with this menu view. + */ + public void dismissPopupMenus() { + if (mPresenter != null) { + mPresenter.dismissPopupMenus(); + } + } + + /** * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public. */ @Override diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index f903346..8c67bb7 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -18,13 +18,17 @@ package android.widget; import android.annotation.NonNull; +import android.app.ActionBar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; +import android.view.CollapsibleActionView; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; @@ -32,7 +36,15 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.Window; import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuPresenter; +import com.android.internal.view.menu.MenuView; +import com.android.internal.view.menu.SubMenuBuilder; +import com.android.internal.widget.DecorToolbar; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.util.ArrayList; import java.util.List; @@ -80,14 +92,25 @@ import java.util.List; * layout is discouraged on API 21 devices and newer.</p> */ public class Toolbar extends ViewGroup { + private static final String TAG = "Toolbar"; + private ActionMenuView mMenuView; private TextView mTitleTextView; private TextView mSubtitleTextView; private ImageButton mNavButtonView; private ImageView mLogoView; + private Drawable mCollapseIcon; + private ImageButton mCollapseButtonView; + View mExpandedActionView; + private int mTitleTextAppearance; private int mSubtitleTextAppearance; + private int mNavButtonStyle; + + private int mButtonGravity; + + private int mMaxButtonHeight; private int mTitleMarginStart; private int mTitleMarginEnd; @@ -117,6 +140,10 @@ public class Toolbar extends ViewGroup { } }; + private ToolbarWidgetWrapper mWrapper; + private ActionMenuPresenter mOuterActionMenuPresenter; + private ExpandedActionViewMenuPresenter mExpandedMenuPresenter; + public Toolbar(Context context) { this(context, null); } @@ -137,7 +164,9 @@ public class Toolbar extends ViewGroup { mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0); mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0); + mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0); mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity); + mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP); mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0); @@ -162,6 +191,8 @@ public class Toolbar extends ViewGroup { mTitleMarginBottom = marginBottom; } + mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1); + final int contentInsetStart = a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart, RtlSpacingHelper.UNDEFINED); @@ -180,6 +211,8 @@ public class Toolbar extends ViewGroup { mContentInsets.setRelative(contentInsetStart, contentInsetEnd); } + mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon); + final CharSequence title = a.getText(R.styleable.Toolbar_title); if (!TextUtils.isEmpty(title)) { setTitle(title); @@ -211,6 +244,110 @@ public class Toolbar extends ViewGroup { setLogo(getContext().getDrawable(resId)); } + /** @hide */ + public boolean canShowOverflowMenu() { + return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mMenuView != null && mMenuView.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mMenuView != null && mMenuView.isOverflowMenuShowPending(); + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mMenuView != null && mMenuView.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mMenuView != null && mMenuView.hideOverflowMenu(); + } + + /** @hide */ + public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) { + if (menu == null && mMenuView == null) { + return; + } + + ensureMenuView(); + final MenuBuilder oldMenu = mMenuView.peekMenu(); + if (oldMenu == menu) { + return; + } + + if (oldMenu != null) { + oldMenu.removeMenuPresenter(mOuterActionMenuPresenter); + oldMenu.removeMenuPresenter(mExpandedMenuPresenter); + } + + final Context context = getContext(); + + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + + outerPresenter.setExpandedActionViewsExclusive(true); + if (menu != null) { + menu.addMenuPresenter(outerPresenter); + menu.addMenuPresenter(mExpandedMenuPresenter); + } else { + outerPresenter.initForMenu(context, null); + mExpandedMenuPresenter.initForMenu(context, null); + outerPresenter.updateMenuView(true); + mExpandedMenuPresenter.updateMenuView(true); + } + mMenuView.setPresenter(outerPresenter); + mOuterActionMenuPresenter = outerPresenter; + } + + /** + * Dismiss all currently showing popup menus, including overflow or submenus. + */ + public void dismissPopupMenus() { + if (mMenuView != null) { + mMenuView.dismissPopupMenus(); + } + } + + /** @hide */ + public boolean isTitleTruncated() { + if (mTitleTextView == null) { + return false; + } + + final Layout titleLayout = mTitleTextView.getLayout(); + if (titleLayout == null) { + return false; + } + + final int lineCount = titleLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + if (titleLayout.getEllipsisCount(i) > 0) { + return true; + } + } + return false; + } + /** * Set a logo drawable. * @@ -222,9 +359,7 @@ public class Toolbar extends ViewGroup { */ public void setLogo(Drawable drawable) { if (drawable != null) { - if (mLogoView == null) { - mLogoView = new ImageView(getContext()); - } + ensureLogoView(); if (mLogoView.getParent() == null) { addSystemView(mLogoView); } @@ -268,8 +403,8 @@ public class Toolbar extends ViewGroup { * @param description Description to set */ public void setLogoDescription(CharSequence description) { - if (!TextUtils.isEmpty(description) && mLogoView == null) { - mLogoView = new ImageView(getContext()); + if (!TextUtils.isEmpty(description)) { + ensureLogoView(); } if (mLogoView != null) { mLogoView.setContentDescription(description); @@ -285,10 +420,48 @@ public class Toolbar extends ViewGroup { return mLogoView != null ? mLogoView.getContentDescription() : null; } + private void ensureLogoView() { + if (mLogoView == null) { + mLogoView = new ImageView(getContext()); + } + } + /** - * Return the current title displayed in the toolbar. + * Check whether this Toolbar is currently hosting an expanded action view. * - * @return The current title + * <p>An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar + * has an expanded action view it can be collapsed using the {@link #collapseActionView()} + * method.</p> + * + * @return true if the Toolbar has an expanded action view + */ + public boolean hasExpandedActionView() { + return mExpandedMenuPresenter != null && + mExpandedMenuPresenter.mCurrentExpandedItem != null; + } + + /** + * Collapse a currently expanded action view. If this Toolbar does not have an + * expanded action view this method has no effect. + * + * <p>An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p> + * + * @see #hasExpandedActionView() + */ + public void collapseActionView() { + final MenuItemImpl item = mExpandedMenuPresenter == null ? null : + mExpandedMenuPresenter.mCurrentExpandedItem; + if (item != null) { + item.collapseActionView(); + } + } + + /** + * Returns the title of this toolbar. + * + * @return The current title. */ public CharSequence getTitle() { return mTitleText; @@ -319,6 +492,8 @@ public class Toolbar extends ViewGroup { if (mTitleTextView == null) { final Context context = getContext(); mTitleTextView = new TextView(context); + mTitleTextView.setSingleLine(); + mTitleTextView.setEllipsize(TextUtils.TruncateAt.END); mTitleTextView.setTextAppearance(context, mTitleTextAppearance); } if (mTitleTextView.getParent() == null) { @@ -365,6 +540,8 @@ public class Toolbar extends ViewGroup { if (mSubtitleTextView == null) { final Context context = getContext(); mSubtitleTextView = new TextView(context); + mSubtitleTextView.setSingleLine(); + mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END); mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance); } if (mSubtitleTextView.getParent() == null) { @@ -395,6 +572,30 @@ public class Toolbar extends ViewGroup { } /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param description Content description to set + */ + public void setNavigationContentDescription(CharSequence description) { + ensureNavButtonView(); + mNavButtonView.setContentDescription(description); + } + + /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param resId Resource ID of a content description string to set + */ + public void setNavigationContentDescription(int resId) { + ensureNavButtonView(); + mNavButtonView.setContentDescription(getContext().getText(resId)); + } + + /** * Set the icon to use for the toolbar's navigation button. * * <p>The navigation button appears at the start of the toolbar if present. Setting an icon @@ -480,12 +681,19 @@ public class Toolbar extends ViewGroup { * @return The toolbar's Menu */ public Menu getMenu() { + ensureMenuView(); + return mMenuView.getMenu(); + } + + private void ensureMenuView() { if (mMenuView == null) { mMenuView = new ActionMenuView(getContext()); mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mMenuView.setLayoutParams(lp); addSystemView(mMenuView); } - return mMenuView.getMenu(); } private MenuInflater getMenuInflater() { @@ -634,7 +842,27 @@ public class Toolbar extends ViewGroup { private void ensureNavButtonView() { if (mNavButtonView == null) { - mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle); + mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mNavButtonView.setLayoutParams(lp); + } + } + + private void ensureCollapseButtonView() { + if (mCollapseButtonView == null) { + mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); + mCollapseButtonView.setImageDrawable(mCollapseIcon); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mCollapseButtonView.setLayoutParams(lp); + mCollapseButtonView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + collapseActionView(); + } + }); } } @@ -657,6 +885,27 @@ public class Toolbar extends ViewGroup { super.onRestoreInstanceState(ss.getSuperState()); } + private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed, + int parentHeightSpec, int heightUsed, int heightConstraint) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childWidthSpec = getChildMeasureSpec(parentWidthSpec, + mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + int childHeightSpec = getChildMeasureSpec(parentHeightSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + final int childHeightMode = MeasureSpec.getMode(childHeightSpec); + if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) { + final int size = childHeightMode != MeasureSpec.UNSPECIFIED ? + Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) : + heightConstraint; + childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } + child.measure(childWidthSpec, childHeightSpec); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; @@ -667,18 +916,30 @@ public class Toolbar extends ViewGroup { int navWidth = 0; if (shouldLayout(mNavButtonView)) { - measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0); + measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView); height = Math.max(height, mNavButtonView.getMeasuredHeight() + getVerticalMargins(mNavButtonView)); childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState()); } + if (shouldLayout(mCollapseButtonView)) { + measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width, + heightMeasureSpec, 0, mMaxButtonHeight); + navWidth = mCollapseButtonView.getMeasuredWidth() + + getHorizontalMargins(mCollapseButtonView); + height = Math.max(height, mCollapseButtonView.getMeasuredHeight() + + getVerticalMargins(mCollapseButtonView)); + childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState()); + } + width += Math.max(getContentInsetStart(), navWidth); int menuWidth = 0; if (shouldLayout(mMenuView)) { - measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0); + measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView); height = Math.max(height, mMenuView.getMeasuredHeight() + getVerticalMargins(mMenuView)); @@ -687,6 +948,16 @@ public class Toolbar extends ViewGroup { width += Math.max(getContentInsetEnd(), menuWidth); + if (shouldLayout(mExpandedActionView)) { + measureChildWithMargins(mExpandedActionView, widthMeasureSpec, width, + heightMeasureSpec, 0); + width += mExpandedActionView.getMeasuredWidth() + + getHorizontalMargins(mExpandedActionView); + height = Math.max(height, mExpandedActionView.getMeasuredHeight() + + getVerticalMargins(mExpandedActionView)); + childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState()); + } + if (shouldLayout(mLogoView)) { measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0); width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView); @@ -723,7 +994,7 @@ public class Toolbar extends ViewGroup { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType == LayoutParams.SYSTEM || !shouldLayout(child)) { + if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) { // We already got all system views above. Skip them and GONE views. continue; } @@ -768,6 +1039,14 @@ public class Toolbar extends ViewGroup { } } + if (shouldLayout(mCollapseButtonView)) { + if (isRtl) { + right = layoutChildRight(mCollapseButtonView, right); + } else { + left = layoutChildLeft(mCollapseButtonView, left); + } + } + if (shouldLayout(mMenuView)) { if (isRtl) { left = layoutChildLeft(mMenuView, left); @@ -779,6 +1058,14 @@ public class Toolbar extends ViewGroup { left = Math.max(left, getContentInsetLeft()); right = Math.min(right, width - paddingRight - getContentInsetRight()); + if (shouldLayout(mExpandedActionView)) { + if (isRtl) { + right = layoutChildRight(mExpandedActionView, right); + } else { + left = layoutChildLeft(mExpandedActionView, left); + } + } + if (shouldLayout(mLogoView)) { if (isRtl) { right = layoutChildRight(mLogoView, right); @@ -801,40 +1088,42 @@ public class Toolbar extends ViewGroup { if (layoutTitle || layoutSubtitle) { int titleTop; + final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView; + final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView; + final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams(); + final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams(); + switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: - titleTop = getPaddingTop(); + titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop; break; default: case Gravity.CENTER_VERTICAL: - final View child = layoutTitle ? mTitleTextView : mSubtitleTextView; - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int space = height - paddingTop - paddingBottom; int spaceAbove = (space - titleHeight) / 2; - if (spaceAbove < lp.topMargin + mTitleMarginTop) { - spaceAbove = lp.topMargin + mTitleMarginTop; + if (spaceAbove < toplp.topMargin + mTitleMarginTop) { + spaceAbove = toplp.topMargin + mTitleMarginTop; } else { final int spaceBelow = height - paddingBottom - titleHeight - spaceAbove - paddingTop; - if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) { + if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) { spaceAbove = Math.max(0, spaceAbove - - (lp.bottomMargin + mTitleMarginBottom - spaceBelow)); + (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow)); } } titleTop = paddingTop + spaceAbove; break; case Gravity.BOTTOM: - titleTop = height - paddingBottom - titleHeight; + titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom - + titleHeight; break; } if (isRtl) { int titleRight = right; int subtitleRight = right; - titleTop += mTitleMarginTop; if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); titleRight -= lp.rightMargin + mTitleMarginStart; - titleTop += lp.topMargin; final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); @@ -855,11 +1144,9 @@ public class Toolbar extends ViewGroup { } else { int titleLeft = left; int subtitleLeft = left; - titleTop += mTitleMarginTop; if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); titleLeft += lp.leftMargin + mTitleMarginStart; - titleTop += lp.topMargin; final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); @@ -897,7 +1184,7 @@ public class Toolbar extends ViewGroup { // Centered views try to center with respect to the whole bar, but views pinned // to the left or right can push the mass of centered views to one side or the other. - addCustomViewsWithGravity(mTempViews, Gravity.CENTER); + addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL); final int centerViewsWidth = getViewListMeasuredWidth(mTempViews); final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2; final int halfCenterViewsWidth = centerViewsWidth / 2; @@ -1007,17 +1294,16 @@ public class Toolbar extends ViewGroup { for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) && + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && getChildHorizontalGravity(lp.gravity) == absGrav) { views.add(child); } - } } else { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) && + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && getChildHorizontalGravity(lp.gravity) == absGrav) { views.add(child); } @@ -1054,14 +1340,16 @@ public class Toolbar extends ViewGroup { } @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return super.generateLayoutParams(attrs); + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); } @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { if (p instanceof LayoutParams) { return new LayoutParams((LayoutParams) p); + } else if (p instanceof ActionBar.LayoutParams) { + return new LayoutParams((ActionBar.LayoutParams) p); } else if (p instanceof MarginLayoutParams) { return new LayoutParams((MarginLayoutParams) p); } else { @@ -1070,7 +1358,7 @@ public class Toolbar extends ViewGroup { } @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @@ -1083,6 +1371,25 @@ public class Toolbar extends ViewGroup { return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM; } + /** @hide */ + public DecorToolbar getWrapper() { + if (mWrapper == null) { + mWrapper = new ToolbarWidgetWrapper(this); + } + return mWrapper; + } + + private void setChildVisibilityForExpandedActionView(boolean expand) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) { + child.setVisibility(expand ? GONE : VISIBLE); + } + } + } + /** * Interface responsible for receiving menu item click events if the items themselves * do not have individual item click listeners. @@ -1103,44 +1410,15 @@ public class Toolbar extends ViewGroup { * * @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity */ - public static class LayoutParams extends MarginLayoutParams { - /** - * Gravity for the view associated with these LayoutParams. - * - * @see android.view.Gravity - */ - @ViewDebug.ExportedProperty(category = "layout", mapping = { - @ViewDebug.IntToString(from = -1, to = "NONE"), - @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), - @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), - @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), - @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), - @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), - @ViewDebug.IntToString(from = Gravity.START, to = "START"), - @ViewDebug.IntToString(from = Gravity.END, to = "END"), - @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), - @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), - @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), - @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"), - @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), - @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") - }) - public int gravity = Gravity.NO_GRAVITY; - + public static class LayoutParams extends ActionBar.LayoutParams { static final int CUSTOM = 0; static final int SYSTEM = 1; + static final int EXPANDED = 2; int mViewType = CUSTOM; public LayoutParams(@NonNull Context c, AttributeSet attrs) { super(c, attrs); - - TypedArray a = c.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Toolbar_LayoutParams); - gravity = a.getInt( - com.android.internal.R.styleable.Toolbar_LayoutParams_layout_gravity, - Gravity.NO_GRAVITY); - a.recycle(); } public LayoutParams(int width, int height) { @@ -1160,7 +1438,11 @@ public class Toolbar extends ViewGroup { public LayoutParams(LayoutParams source) { super(source); - this.gravity = source.gravity; + mViewType = source.mViewType; + } + + public LayoutParams(ActionBar.LayoutParams source) { + super(source); } public LayoutParams(MarginLayoutParams source) { @@ -1199,4 +1481,126 @@ public class Toolbar extends ViewGroup { } }; } + + private class ExpandedActionViewMenuPresenter implements MenuPresenter { + MenuBuilder mMenu; + MenuItemImpl mCurrentExpandedItem; + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + // Clear the expanded action view when menus change. + if (mMenu != null && mCurrentExpandedItem != null) { + mMenu.collapseItemActionView(mCurrentExpandedItem); + } + mMenu = menu; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + return null; + } + + @Override + public void updateMenuView(boolean cleared) { + // Make sure the expanded item we have is still there. + if (mCurrentExpandedItem != null) { + boolean found = false; + + if (mMenu != null) { + final int count = mMenu.size(); + for (int i = 0; i < count; i++) { + final MenuItem item = mMenu.getItem(i); + if (item == mCurrentExpandedItem) { + found = true; + break; + } + } + } + + if (!found) { + // The item we had expanded disappeared. Collapse. + collapseItemActionView(mMenu, mCurrentExpandedItem); + } + } + } + + @Override + public void setCallback(Callback cb) { + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + ensureCollapseButtonView(); + if (mCollapseButtonView.getParent() != Toolbar.this) { + addView(mCollapseButtonView); + } + mExpandedActionView = item.getActionView(); + mCurrentExpandedItem = item; + if (mExpandedActionView.getParent() != Toolbar.this) { + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mExpandedActionView.setLayoutParams(lp); + addView(mExpandedActionView); + } + + setChildVisibilityForExpandedActionView(true); + requestLayout(); + item.setActionViewExpanded(true); + + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); + } + + return true; + } + + @Override + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + // Do this before detaching the actionview from the hierarchy, in case + // it needs to dismiss the soft keyboard, etc. + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed(); + } + + removeView(mExpandedActionView); + removeView(mCollapseButtonView); + mExpandedActionView = null; + + setChildVisibilityForExpandedActionView(false); + mCurrentExpandedItem = null; + requestLayout(); + item.setActionViewExpanded(false); + + return true; + } + + @Override + public int getId() { + return 0; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + } } diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 41f3337..7e11850 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -1100,7 +1100,7 @@ public final class ProcessStats implements Parcelable { public boolean evaluateSystemProperties(boolean update) { boolean changed = false; - String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.1", + String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary()); if (!Objects.equals(runtime, mRuntime)) { changed = true; diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index a238ae3..5c7a4e6 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -18,7 +18,10 @@ package com.android.internal.app; import android.animation.ValueAnimator; import android.content.res.TypedArray; +import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.AdapterView; +import android.widget.Toolbar; import com.android.internal.R; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.MenuBuilder; @@ -28,6 +31,7 @@ import com.android.internal.widget.ActionBarContainer; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.ActionBarOverlayLayout; import com.android.internal.widget.ActionBarView; +import com.android.internal.widget.DecorToolbar; import com.android.internal.widget.ScrollingTabContainerView; import android.animation.Animator; @@ -55,6 +59,7 @@ import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; import android.widget.SpinnerAdapter; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -77,7 +82,7 @@ public class WindowDecorActionBar extends ActionBar implements private ActionBarOverlayLayout mOverlayLayout; private ActionBarContainer mContainerView; - private ActionBarView mActionView; + private DecorToolbar mDecorToolbar; private ActionBarContextView mContextView; private ActionBarContainer mSplitView; private View mContentView; @@ -187,7 +192,7 @@ public class WindowDecorActionBar extends ActionBar implements if (mOverlayLayout != null) { mOverlayLayout.setActionBarVisibilityCallback(this); } - mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); + mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar)); mContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); mContainerView = (ActionBarContainer) decor.findViewById( @@ -195,18 +200,17 @@ public class WindowDecorActionBar extends ActionBar implements mSplitView = (ActionBarContainer) decor.findViewById( com.android.internal.R.id.split_action_bar); - if (mActionView == null || mContextView == null || mContainerView == null) { + if (mDecorToolbar == null || mContextView == null || mContainerView == null) { throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout"); } - mContext = mActionView.getContext(); - mActionView.setContextView(mContextView); - mContextDisplayMode = mActionView.isSplitActionBar() ? + mContext = mDecorToolbar.getContext(); + mContextDisplayMode = mDecorToolbar.isSplit() ? CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; // This was initially read from the action bar style - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; if (homeAsUp) { mDisplayHomeAsUpSet = true; @@ -225,6 +229,17 @@ public class WindowDecorActionBar extends ActionBar implements a.recycle(); } + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + public void onConfigurationChanged(Configuration newConfig) { setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); } @@ -233,11 +248,11 @@ public class WindowDecorActionBar extends ActionBar implements mHasEmbeddedTabs = hasEmbeddedTabs; // Switch tab layout configuration if needed if (!mHasEmbeddedTabs) { - mActionView.setEmbeddedTabView(null); + mDecorToolbar.setEmbeddedTabView(null); mContainerView.setTabContainer(mTabScrollView); } else { mContainerView.setTabContainer(null); - mActionView.setEmbeddedTabView(mTabScrollView); + mDecorToolbar.setEmbeddedTabView(mTabScrollView); } final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; if (mTabScrollView != null) { @@ -250,7 +265,7 @@ public class WindowDecorActionBar extends ActionBar implements mTabScrollView.setVisibility(View.GONE); } } - mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode); + mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); } @@ -263,7 +278,7 @@ public class WindowDecorActionBar extends ActionBar implements if (mHasEmbeddedTabs) { tabScroller.setVisibility(View.VISIBLE); - mActionView.setEmbeddedTabView(tabScroller); + mDecorToolbar.setEmbeddedTabView(tabScroller); } else { if (getNavigationMode() == NAVIGATION_MODE_TABS) { tabScroller.setVisibility(View.VISIBLE); @@ -326,7 +341,8 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setCustomView(int resId) { - setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false)); + setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, + mDecorToolbar.getViewGroup(), false)); } @Override @@ -356,7 +372,7 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setHomeButtonEnabled(boolean enable) { - mActionView.setHomeButtonEnabled(enable); + mDecorToolbar.setHomeButtonEnabled(enable); } @Override @@ -370,12 +386,12 @@ public class WindowDecorActionBar extends ActionBar implements } public void setSelectedNavigationItem(int position) { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: selectTab(mTabs.get(position)); break; case NAVIGATION_MODE_LIST: - mActionView.setDropdownSelectedPosition(position); + mDecorToolbar.setDropdownSelectedPosition(position); break; default: throw new IllegalStateException( @@ -399,26 +415,26 @@ public class WindowDecorActionBar extends ActionBar implements } public void setTitle(CharSequence title) { - mActionView.setTitle(title); + mDecorToolbar.setTitle(title); } public void setSubtitle(CharSequence subtitle) { - mActionView.setSubtitle(subtitle); + mDecorToolbar.setSubtitle(subtitle); } public void setDisplayOptions(int options) { if ((options & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions(options); + mDecorToolbar.setDisplayOptions(options); } public void setDisplayOptions(int options, int mask) { - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); if ((mask & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions((options & mask) | (current & ~mask)); + mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask)); } public void setBackgroundDrawable(Drawable d) { @@ -436,23 +452,23 @@ public class WindowDecorActionBar extends ActionBar implements } public View getCustomView() { - return mActionView.getCustomNavigationView(); + return mDecorToolbar.getCustomView(); } public CharSequence getTitle() { - return mActionView.getTitle(); + return mDecorToolbar.getTitle(); } public CharSequence getSubtitle() { - return mActionView.getSubtitle(); + return mDecorToolbar.getSubtitle(); } public int getNavigationMode() { - return mActionView.getNavigationMode(); + return mDecorToolbar.getNavigationMode(); } public int getDisplayOptions() { - return mActionView.getDisplayOptions(); + return mDecorToolbar.getDisplayOptions(); } public ActionMode startActionMode(ActionMode.Callback callback) { @@ -572,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements return; } - final FragmentTransaction trans = mActionView.isInEditMode() ? null : + final FragmentTransaction trans = ((View) mDecorToolbar).isInEditMode() ? null : mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); if (mSelectedTab == tab) { @@ -828,13 +844,18 @@ public class WindowDecorActionBar extends ActionBar implements hideForActionMode(); } - mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); - if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) { + if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() && + isCollapsed((View) mDecorToolbar)) { mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); } } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0; + } + public Context getThemedContext() { if (mThemedContext == null) { TypedValue outValue = new TypedValue(); @@ -854,27 +875,27 @@ public class WindowDecorActionBar extends ActionBar implements @Override public boolean isTitleTruncated() { - return mActionView != null && mActionView.isTitleTruncated(); + return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); } @Override public void setHomeAsUpIndicator(Drawable indicator) { - mActionView.setHomeAsUpIndicator(indicator); + mDecorToolbar.setNavigationIcon(indicator); } @Override public void setHomeAsUpIndicator(int resId) { - mActionView.setHomeAsUpIndicator(resId); + mDecorToolbar.setNavigationIcon(resId); } @Override public void setHomeActionContentDescription(CharSequence description) { - mActionView.setHomeActionContentDescription(description); + mDecorToolbar.setNavigationContentDescription(description); } @Override public void setHomeActionContentDescription(int resId) { - mActionView.setHomeActionContentDescription(resId); + mDecorToolbar.setNavigationContentDescription(resId); } @Override @@ -938,7 +959,8 @@ public class WindowDecorActionBar extends ActionBar implements // Clear out the context mode views after the animation finishes mContextView.closeMode(); - mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + ((View) mDecorToolbar).sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); mActionMode = null; @@ -1178,28 +1200,27 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setCustomView(View view) { - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setCustomView(View view, LayoutParams layoutParams) { view.setLayoutParams(layoutParams); - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { - mActionView.setDropdownAdapter(adapter); - mActionView.setCallback(callback); + mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); } @Override public int getSelectedNavigationIndex() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mSelectedTab != null ? mSelectedTab.getPosition() : -1; case NAVIGATION_MODE_LIST: - return mActionView.getDropdownSelectedPosition(); + return mDecorToolbar.getDropdownSelectedPosition(); default: return -1; } @@ -1207,12 +1228,11 @@ public class WindowDecorActionBar extends ActionBar implements @Override public int getNavigationItemCount() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mTabs.size(); case NAVIGATION_MODE_LIST: - SpinnerAdapter adapter = mActionView.getDropdownAdapter(); - return adapter != null ? adapter.getCount() : 0; + return mDecorToolbar.getDropdownItemCount(); default: return 0; } @@ -1225,7 +1245,7 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setNavigationMode(int mode) { - final int oldMode = mActionView.getNavigationMode(); + final int oldMode = mDecorToolbar.getNavigationMode(); switch (oldMode) { case NAVIGATION_MODE_TABS: mSavedTabPosition = getSelectedNavigationIndex(); @@ -1238,7 +1258,7 @@ public class WindowDecorActionBar extends ActionBar implements mOverlayLayout.requestFitSystemWindows(); } } - mActionView.setNavigationMode(mode); + mDecorToolbar.setNavigationMode(mode); switch (mode) { case NAVIGATION_MODE_TABS: ensureTabsExist(); @@ -1249,7 +1269,7 @@ public class WindowDecorActionBar extends ActionBar implements } break; } - mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); } @@ -1261,30 +1281,30 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setIcon(int resId) { - mActionView.setIcon(resId); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable icon) { - mActionView.setIcon(icon); + mDecorToolbar.setIcon(icon); } public boolean hasIcon() { - return mActionView.hasIcon(); + return mDecorToolbar.hasIcon(); } @Override public void setLogo(int resId) { - mActionView.setLogo(resId); + mDecorToolbar.setLogo(resId); } @Override public void setLogo(Drawable logo) { - mActionView.setLogo(logo); + mDecorToolbar.setLogo(logo); } public boolean hasLogo() { - return mActionView.hasLogo(); + return mDecorToolbar.hasLogo(); } public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { @@ -1292,4 +1312,24 @@ public class WindowDecorActionBar extends ActionBar implements setDisplayHomeAsUpEnabled(enable); } } + + static class NavItemSelectedListener implements AdapterView.OnItemSelectedListener { + private final OnNavigationListener mListener; + + public NavItemSelectedListener(OnNavigationListener listener) { + mListener = listener; + } + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (mListener != null) { + mListener.onNavigationItemSelected(position, id); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Do nothing + } + } } diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index 183478f..9e7ff93 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -34,7 +34,7 @@ import android.view.animation.DecelerateInterpolator; public abstract class AbsActionBarView extends ViewGroup { protected ActionMenuView mMenuView; protected ActionMenuPresenter mActionMenuPresenter; - protected ActionBarContainer mSplitView; + protected ViewGroup mSplitView; protected boolean mSplitActionBar; protected boolean mSplitWhenNarrow; protected int mContentHeight; @@ -74,7 +74,7 @@ public abstract class AbsActionBarView extends ViewGroup { setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); a.recycle(); if (mSplitWhenNarrow) { - setSplitActionBar(getContext().getResources().getBoolean( + setSplitToolbar(getContext().getResources().getBoolean( com.android.internal.R.bool.split_action_bar_is_narrow)); } if (mActionMenuPresenter != null) { @@ -86,7 +86,7 @@ public abstract class AbsActionBarView extends ViewGroup { * Sets whether the bar should be split right now, no questions asked. * @param split true if the bar should split */ - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { mSplitActionBar = split; } @@ -107,7 +107,7 @@ public abstract class AbsActionBarView extends ViewGroup { return mContentHeight; } - public void setSplitView(ActionBarContainer splitView) { + public void setSplitView(ViewGroup splitView) { mSplitView = splitView; } @@ -214,6 +214,10 @@ public abstract class AbsActionBarView extends ViewGroup { return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); } + public boolean canShowOverflowMenu() { + return isOverflowReserved() && getVisibility() == VISIBLE; + } + public void dismissPopupMenus() { if (mActionMenuPresenter != null) { mActionMenuPresenter.dismissPopupMenus(); diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index ed07514..790b611 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -36,7 +36,7 @@ import android.widget.FrameLayout; public class ActionBarContainer extends FrameLayout { private boolean mIsTransitioning; private View mTabContainer; - private ActionBarView mActionBarView; + private View mActionBarView; private Drawable mBackground; private Drawable mStackedBackground; @@ -76,7 +76,7 @@ public class ActionBarContainer extends FrameLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mActionBarView = findViewById(com.android.internal.R.id.action_bar); } public void setPrimaryBackground(Drawable bg) { @@ -251,6 +251,10 @@ public class ActionBarContainer extends FrameLayout { return null; } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; + } + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mActionBarView == null && @@ -263,7 +267,7 @@ public class ActionBarContainer extends FrameLayout { if (mActionBarView == null) return; final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams(); - final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 : + final int actionBarViewHeight = isCollapsed(mActionBarView) ? 0 : mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { @@ -298,9 +302,8 @@ public class ActionBarContainer extends FrameLayout { } } else { if (mBackground != null) { - final ActionBarView actionBarView = mActionBarView; - mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(), - actionBarView.getRight(), actionBarView.getBottom()); + mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), + mActionBarView.getRight(), mActionBarView.getBottom()); needsInvalidate = true; } mIsStacked = hasTabs; diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index e10070f..6ff77a0 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -83,7 +83,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes); - setBackgroundDrawable(a.getDrawable( + setBackground(a.getDrawable( com.android.internal.R.styleable.ActionMode_background)); mTitleStyleRes = a.getResourceId( com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); @@ -109,7 +109,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi } @Override - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { if (mSplitActionBar != split) { if (mActionMenuPresenter != null) { // Mode is already active; move everything over and adjust the menu itself. @@ -137,7 +137,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi mSplitView.addView(mMenuView, layoutParams); } } - super.setSplitActionBar(split); + super.setSplitToolbar(split); } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 7ab4bed..8a9cb22 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -39,6 +39,7 @@ import android.view.ViewPropertyAnimator; import android.view.Window; import android.view.WindowInsets; import android.widget.OverScroller; +import android.widget.Toolbar; import com.android.internal.view.menu.MenuPresenter; /** @@ -59,7 +60,7 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar private ActionBarContainer mActionBarTop; // Some interior UI elements. - private ActionBarView mActionBarView; + private DecorToolbar mDecorToolbar; // Content overlay drawable - generally the action bar's shadow private Drawable mWindowContentOverlay; @@ -401,7 +402,7 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar topInset = mActionBarTop.getMeasuredHeight(); } - if (mActionBarView.isSplitActionBar()) { + if (mDecorToolbar.isSplit()) { // If action bar is split, adjust bottom insets for it. if (mActionBarBottom != null) { if (stable) { @@ -563,12 +564,23 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar mContent = findViewById(com.android.internal.R.id.content); mActionBarTop = (ActionBarContainer) findViewById( com.android.internal.R.id.action_bar_container); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar)); mActionBarBottom = (ActionBarContainer) findViewById( com.android.internal.R.id.split_action_bar); } } + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { if (hideOnContentScroll != mHideOnContentScroll) { mHideOnContentScroll = hideOnContentScroll; @@ -648,9 +660,9 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar final int action = event.getAction(); // Collapse any expanded action views. - if (mActionBarView != null && mActionBarView.hasExpandedActionView()) { + if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { if (action == KeyEvent.ACTION_UP) { - mActionBarView.collapseActionView(); + mDecorToolbar.collapseActionView(); } return true; } @@ -662,19 +674,19 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar @Override public void setWindowCallback(Window.Callback cb) { pullChildren(); - mActionBarView.setWindowCallback(cb); + mDecorToolbar.setWindowCallback(cb); } @Override public void setWindowTitle(CharSequence title) { pullChildren(); - mActionBarView.setWindowTitle(title); + mDecorToolbar.setWindowTitle(title); } @Override public CharSequence getTitle() { pullChildren(); - return mActionBarView.getTitle(); + return mDecorToolbar.getTitle(); } @Override @@ -682,10 +694,10 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar pullChildren(); switch (windowFeature) { case Window.FEATURE_PROGRESS: - mActionBarView.initProgress(); + mDecorToolbar.initProgress(); break; case Window.FEATURE_INDETERMINATE_PROGRESS: - mActionBarView.initIndeterminateProgress(); + mDecorToolbar.initIndeterminateProgress(); break; case Window.FEATURE_ACTION_BAR_OVERLAY: setOverlayMode(true); @@ -704,15 +716,15 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } if (splitActionBar) { pullChildren(); - if (mActionBarBottom != null) { - mActionBarView.setSplitView(mActionBarBottom); - mActionBarView.setSplitActionBar(splitActionBar); - mActionBarView.setSplitWhenNarrow(splitWhenNarrow); + if (mActionBarBottom != null && mDecorToolbar.canSplit()) { + mDecorToolbar.setSplitView(mActionBarBottom); + mDecorToolbar.setSplitToolbar(splitActionBar); + mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow); final ActionBarContextView cab = (ActionBarContextView) findViewById( com.android.internal.R.id.action_context_bar); cab.setSplitView(mActionBarBottom); - cab.setSplitActionBar(splitActionBar); + cab.setSplitToolbar(splitActionBar); cab.setSplitWhenNarrow(splitWhenNarrow); } else if (splitActionBar) { Log.e(TAG, "Requested split action bar with " + @@ -724,91 +736,91 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar @Override public boolean hasIcon() { pullChildren(); - return mActionBarView.hasIcon(); + return mDecorToolbar.hasIcon(); } @Override public boolean hasLogo() { pullChildren(); - return mActionBarView.hasLogo(); + return mDecorToolbar.hasLogo(); } @Override public void setIcon(int resId) { pullChildren(); - mActionBarView.setIcon(resId); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable d) { pullChildren(); - mActionBarView.setIcon(d); + mDecorToolbar.setIcon(d); } @Override public void setLogo(int resId) { pullChildren(); - mActionBarView.setLogo(resId); + mDecorToolbar.setLogo(resId); } @Override public boolean canShowOverflowMenu() { pullChildren(); - return mActionBarView.isOverflowReserved() && mActionBarView.getVisibility() == VISIBLE; + return mDecorToolbar.canShowOverflowMenu(); } @Override public boolean isOverflowMenuShowing() { pullChildren(); - return mActionBarView.isOverflowMenuShowing(); + return mDecorToolbar.isOverflowMenuShowing(); } @Override public boolean isOverflowMenuShowPending() { pullChildren(); - return mActionBarView.isOverflowMenuShowPending(); + return mDecorToolbar.isOverflowMenuShowPending(); } @Override public boolean showOverflowMenu() { pullChildren(); - return mActionBarView.showOverflowMenu(); + return mDecorToolbar.showOverflowMenu(); } @Override public boolean hideOverflowMenu() { pullChildren(); - return mActionBarView.hideOverflowMenu(); + return mDecorToolbar.hideOverflowMenu(); } @Override public void setMenuPrepared() { pullChildren(); - mActionBarView.setMenuPrepared(); + mDecorToolbar.setMenuPrepared(); } @Override public void setMenu(Menu menu, MenuPresenter.Callback cb) { pullChildren(); - mActionBarView.setMenu(menu, cb); + mDecorToolbar.setMenu(menu, cb); } @Override public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { pullChildren(); - mActionBarView.saveHierarchyState(toolbarStates); + mDecorToolbar.saveHierarchyState(toolbarStates); } @Override public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { pullChildren(); - mActionBarView.restoreHierarchyState(toolbarStates); + mDecorToolbar.restoreHierarchyState(toolbarStates); } @Override public void dismissPopups() { pullChildren(); - mActionBarView.dismissPopupMenus(); + mDecorToolbar.dismissPopupMenus(); } public static class LayoutParams extends MarginLayoutParams { diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 60631b9..af82778 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -18,7 +18,6 @@ package com.android.internal.widget; import android.animation.LayoutTransition; import android.app.ActionBar; -import android.app.ActionBar.OnNavigationListener; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -63,7 +62,7 @@ import com.android.internal.view.menu.SubMenuBuilder; /** * @hide */ -public class ActionBarView extends AbsActionBarView { +public class ActionBarView extends AbsActionBarView implements DecorToolbar { private static final String TAG = "ActionBarView"; /** @@ -117,8 +116,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mUserTitle; private boolean mIncludeTabs; - private boolean mIsCollapsable; - private boolean mIsCollapsed; + private boolean mIsCollapsible; private boolean mWasHomeEnabled; // Was it enabled before action view expansion? private MenuBuilder mOptionsMenu; @@ -129,7 +127,7 @@ public class ActionBarView extends AbsActionBarView { private ActionMenuItem mLogoNavItem; private SpinnerAdapter mSpinnerAdapter; - private OnNavigationListener mCallback; + private AdapterView.OnItemSelectedListener mNavItemSelectedListener; private Runnable mTabSelector; @@ -138,18 +136,6 @@ public class ActionBarView extends AbsActionBarView { Window.Callback mWindowCallback; - private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = - new AdapterView.OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (mCallback != null) { - mCallback.onNavigationItemSelected(position, id); - } - } - public void onNothingSelected(AdapterView parent) { - // Do nothing - } - }; - private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() { @Override public void onClick(View v) { @@ -178,8 +164,6 @@ public class ActionBarView extends AbsActionBarView { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, com.android.internal.R.attr.actionBarStyle, 0); - ApplicationInfo appInfo = context.getApplicationInfo(); - PackageManager pm = context.getPackageManager(); mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD); mTitle = a.getText(R.styleable.ActionBar_title); @@ -260,7 +244,7 @@ public class ActionBarView extends AbsActionBarView { } if (mHomeDescriptionRes != 0) { - setHomeActionContentDescription(mHomeDescriptionRes); + setNavigationContentDescription(mHomeDescriptionRes); } if (mTabScrollView != null && mIncludeTabs) { @@ -313,7 +297,7 @@ public class ActionBarView extends AbsActionBarView { } @Override - public void setSplitActionBar(boolean splitActionBar) { + public void setSplitToolbar(boolean splitActionBar) { if (mSplitActionBar != splitActionBar) { if (mMenuView != null) { final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); @@ -349,18 +333,26 @@ public class ActionBarView extends AbsActionBarView { mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); } } - super.setSplitActionBar(splitActionBar); + super.setSplitToolbar(splitActionBar); } } - public boolean isSplitActionBar() { + public boolean isSplit() { return mSplitActionBar; } + public boolean canSplit() { + return true; + } + public boolean hasEmbeddedTabs() { return mIncludeTabs; } + public void setEmbeddedTabView(View view) { + setEmbeddedTabView((ScrollingTabContainerView) view); + } + public void setEmbeddedTabView(ScrollingTabContainerView tabs) { if (mTabScrollView != null) { removeView(mTabScrollView); @@ -376,10 +368,6 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCallback(OnNavigationListener callback) { - mCallback = callback; - } - public void setMenuPrepared() { mMenuPrepared = true; } @@ -473,7 +461,7 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCustomNavigationView(View view) { + public void setCustomView(View view) { final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0; if (showCustom) { ActionBarTransition.beginDelayedTransition(this); @@ -765,15 +753,16 @@ public class ActionBarView extends AbsActionBarView { } } - public void setDropdownAdapter(SpinnerAdapter adapter) { + public void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l) { mSpinnerAdapter = adapter; + mNavItemSelectedListener = l; if (mSpinner != null) { mSpinner.setAdapter(adapter); } } - public SpinnerAdapter getDropdownAdapter() { - return mSpinnerAdapter; + public int getDropdownItemCount() { + return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0; } public void setDropdownSelectedPosition(int position) { @@ -784,7 +773,7 @@ public class ActionBarView extends AbsActionBarView { return mSpinner.getSelectedItemPosition(); } - public View getCustomNavigationView() { + public View getCustomView() { return mCustomNavView; } @@ -797,6 +786,11 @@ public class ActionBarView extends AbsActionBarView { } @Override + public ViewGroup getViewGroup() { + return this; + } + + @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { // Used by custom nav views if they don't supply layout params. Everything else // added to an ActionBarView should have them already. @@ -860,12 +854,8 @@ public class ActionBarView extends AbsActionBarView { mContextView = view; } - public void setCollapsable(boolean collapsable) { - mIsCollapsable = collapsable; - } - - public boolean isCollapsed() { - return mIsCollapsed; + public void setCollapsible(boolean collapsible) { + mIsCollapsible = collapsible; } /** @@ -893,7 +883,7 @@ public class ActionBarView extends AbsActionBarView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int childCount = getChildCount(); - if (mIsCollapsable) { + if (mIsCollapsible) { int visibleChildren = 0; for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); @@ -915,11 +905,9 @@ public class ActionBarView extends AbsActionBarView { if (visibleChildren == 0) { // No size for an empty action bar when collapsable. setMeasuredDimension(0, 0); - mIsCollapsed = true; return; } } - mIsCollapsed = false; int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { @@ -1323,20 +1311,20 @@ public class ActionBarView extends AbsActionBarView { } } - public void setHomeAsUpIndicator(Drawable indicator) { + public void setNavigationIcon(Drawable indicator) { mHomeLayout.setUpIndicator(indicator); } - public void setHomeAsUpIndicator(int resId) { + public void setNavigationIcon(int resId) { mHomeLayout.setUpIndicator(resId); } - public void setHomeActionContentDescription(CharSequence description) { + public void setNavigationContentDescription(CharSequence description) { mHomeDescription = description; updateHomeAccessibility(mUpGoerFive.isEnabled()); } - public void setHomeActionContentDescription(int resId) { + public void setNavigationContentDescription(int resId) { mHomeDescriptionRes = resId; mHomeDescription = resId != 0 ? getResources().getText(resId) : null; updateHomeAccessibility(mUpGoerFive.isEnabled()); diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java new file mode 100644 index 0000000..ee6988e --- /dev/null +++ b/core/java/com/android/internal/widget/DecorToolbar.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.internal.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.SpinnerAdapter; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Common interface for a toolbar that sits as part of the window decor. + * Layouts that control window decor use this as a point of interaction with different + * bar implementations. + * + * @hide + */ +public interface DecorToolbar { + ViewGroup getViewGroup(); + Context getContext(); + boolean isSplit(); + boolean hasExpandedActionView(); + void collapseActionView(); + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void setTitle(CharSequence title); + CharSequence getSubtitle(); + void setSubtitle(CharSequence subtitle); + void initProgress(); + void initIndeterminateProgress(); + boolean canSplit(); + void setSplitView(ViewGroup splitView); + void setSplitToolbar(boolean split); + void setSplitWhenNarrow(boolean splitWhenNarrow); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + void setLogo(Drawable d); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void dismissPopupMenus(); + + int getDisplayOptions(); + void setDisplayOptions(int opts); + void setEmbeddedTabView(View tabView); + boolean hasEmbeddedTabs(); + boolean isTitleTruncated(); + void setCollapsible(boolean collapsible); + void setHomeButtonEnabled(boolean enable); + int getNavigationMode(); + void setNavigationMode(int mode); + void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener listener); + void setDropdownSelectedPosition(int position); + int getDropdownSelectedPosition(); + int getDropdownItemCount(); + void setCustomView(View view); + View getCustomView(); + void animateToVisibility(int visibility); + void setNavigationIcon(Drawable icon); + void setNavigationIcon(int resId); + void setNavigationContentDescription(CharSequence description); + void setNavigationContentDescription(int resId); + void saveHierarchyState(SparseArray<Parcelable> toolbarStates); + void restoreHierarchyState(SparseArray<Parcelable> toolbarStates); +} diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java new file mode 100644 index 0000000..f90aaea --- /dev/null +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.internal.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.ActionBar; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ActionMenuPresenter; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.Toolbar; +import com.android.internal.R; +import com.android.internal.view.menu.ActionMenuItem; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Internal class used to interact with the Toolbar widget without + * exposing interface methods to the public API. + * + * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView + * so that either variant acting as a + * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave + * in the same way.</p> + * + * @hide + */ +public class ToolbarWidgetWrapper implements DecorToolbar { + private static final String TAG = "ToolbarWidgetWrapper"; + + private static final int AFFECTS_LOGO_MASK = + ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; + + private Toolbar mToolbar; + + private int mDisplayOpts; + private View mTabView; + private Spinner mSpinner; + private View mCustomView; + + private Drawable mIcon; + private Drawable mLogo; + private Drawable mNavIcon; + + private boolean mTitleSet; + private CharSequence mTitle; + private CharSequence mSubtitle; + + private Window.Callback mWindowCallback; + private boolean mMenuPrepared; + private ActionMenuPresenter mActionMenuPresenter; + + public ToolbarWidgetWrapper(Toolbar toolbar) { + mToolbar = toolbar; + + final TypedArray a = toolbar.getContext().obtainStyledAttributes(null, + R.styleable.ActionBar, R.attr.actionBarStyle, 0); + + final CharSequence title = a.getText(R.styleable.ActionBar_title); + if (title != null) { + setTitle(title); + } + + final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); + if (subtitle != null) { + setSubtitle(subtitle); + } + + final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); + if (logo != null) { + setLogo(logo); + } + + final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); + if (icon != null) { + setIcon(icon); + } + + final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); + if (navIcon != null) { + setNavigationIcon(navIcon); + } + + setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); + + final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0); + if (customNavId != 0) { + setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, + mToolbar, false)); + setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); + } + + final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); + if (height > 0) { + final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); + lp.height = height; + mToolbar.setLayoutParams(lp); + } + + final int contentInsetStart = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetStart, 0); + final int contentInsetEnd = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetEnd, 0); + if (contentInsetStart > 0 || contentInsetEnd > 0) { + mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd); + } + + a.recycle(); + + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), + 0, android.R.id.home, 0, 0, mTitle); + @Override + public void onClick(View v) { + if (mWindowCallback != null && mMenuPrepared) { + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); + } + } + }); + } + + @Override + public ViewGroup getViewGroup() { + return mToolbar; + } + + @Override + public Context getContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isSplit() { + return false; + } + + @Override + public boolean hasExpandedActionView() { + return mToolbar.hasExpandedActionView(); + } + + @Override + public void collapseActionView() { + mToolbar.collapseActionView(); + } + + @Override + public void setWindowCallback(Window.Callback cb) { + mWindowCallback = cb; + } + + @Override + public void setWindowTitle(CharSequence title) { + // "Real" title always trumps window title. + if (!mTitleSet) { + setTitleInt(title); + } + } + + @Override + public CharSequence getTitle() { + return mToolbar.getTitle(); + } + + @Override + public void setTitle(CharSequence title) { + mTitleSet = true; + setTitleInt(title); + } + + private void setTitleInt(CharSequence title) { + mTitle = title; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(title); + } + } + + @Override + public CharSequence getSubtitle() { + return mToolbar.getSubtitle(); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setSubtitle(subtitle); + } + } + + @Override + public void initProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public void initIndeterminateProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public boolean canSplit() { + return false; + } + + @Override + public void setSplitView(ViewGroup splitView) { + } + + @Override + public void setSplitToolbar(boolean split) { + if (split) { + throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); + } + } + + @Override + public void setSplitWhenNarrow(boolean splitWhenNarrow) { + // Ignore. + } + + @Override + public boolean hasIcon() { + return mIcon != null; + } + + @Override + public boolean hasLogo() { + return mLogo != null; + } + + @Override + public void setIcon(int resId) { + setIcon(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setIcon(Drawable d) { + mIcon = d; + updateToolbarLogo(); + } + + @Override + public void setLogo(int resId) { + setLogo(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setLogo(Drawable d) { + mLogo = d; + updateToolbarLogo(); + } + + private void updateToolbarLogo() { + Drawable logo = null; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { + if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { + logo = mLogo != null ? mLogo : mIcon; + } else { + logo = mIcon; + } + } + mToolbar.setLogo(logo); + } + + @Override + public boolean canShowOverflowMenu() { + return mToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + return mToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + return mToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + return mToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + mMenuPrepared = true; + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + if (mActionMenuPresenter == null) { + mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); + mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); + } + mActionMenuPresenter.setCallback(cb); + mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); + } + + @Override + public void dismissPopupMenus() { + mToolbar.dismissPopupMenus(); + } + + @Override + public int getDisplayOptions() { + return mDisplayOpts; + } + + @Override + public void setDisplayOptions(int newOpts) { + final int oldOpts = mDisplayOpts; + final int changed = oldOpts ^ newOpts; + mDisplayOpts = newOpts; + if (changed != 0) { + if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(mNavIcon); + } else { + mToolbar.setNavigationIcon(null); + } + } + + if ((changed & AFFECTS_LOGO_MASK) != 0) { + updateToolbarLogo(); + } + + if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(mTitle); + mToolbar.setSubtitle(mSubtitle); + } else { + mToolbar.setTitle(null); + mToolbar.setSubtitle(null); + } + } + + if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { + if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } else { + mToolbar.removeView(mCustomView); + } + } + } + } + + @Override + public void setEmbeddedTabView(View tabView) { + mTabView = tabView; + } + + @Override + public boolean hasEmbeddedTabs() { + return mTabView != null; + } + + @Override + public boolean isTitleTruncated() { + return mToolbar.isTitleTruncated(); + } + + @Override + public void setCollapsible(boolean collapsible) { + // Ignore + } + + @Override + public void setHomeButtonEnabled(boolean enable) { + // Ignore + } + + @Override + public int getNavigationMode() { + return 0; + } + + @Override + public void setNavigationMode(int mode) { + if (mode != ActionBar.NAVIGATION_MODE_STANDARD) { + throw new IllegalArgumentException( + "Navigation modes not supported in this configuration"); + } + } + + @Override + public void setDropdownParams(SpinnerAdapter adapter, + AdapterView.OnItemSelectedListener listener) { + if (mSpinner == null) { + mSpinner = new Spinner(getContext()); + } + mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(listener); + } + + @Override + public void setDropdownSelectedPosition(int position) { + if (mSpinner == null) { + throw new IllegalStateException( + "Can't set dropdown selected position without an adapter"); + } + mSpinner.setSelection(position); + } + + @Override + public int getDropdownSelectedPosition() { + return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; + } + + @Override + public int getDropdownItemCount() { + return mSpinner != null ? mSpinner.getCount() : 0; + } + + @Override + public void setCustomView(View view) { + if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.removeView(mCustomView); + } + mCustomView = view; + if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public void animateToVisibility(int visibility) { + if (visibility == View.GONE) { + mToolbar.animate().translationY(mToolbar.getHeight()).alpha(0) + .setListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + mToolbar.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + }); + } else if (visibility == View.VISIBLE) { + mToolbar.animate().translationY(0).alpha(1) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mToolbar.setVisibility(View.VISIBLE); + } + }); + } + } + + @Override + public void setNavigationIcon(Drawable icon) { + mNavIcon = icon; + if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(icon); + } + } + + @Override + public void setNavigationIcon(int resId) { + setNavigationIcon(mToolbar.getContext().getDrawable(resId)); + } + + @Override + public void setNavigationContentDescription(CharSequence description) { + mToolbar.setNavigationContentDescription(description); + } + + @Override + public void setNavigationContentDescription(int resId) { + mToolbar.setNavigationContentDescription(resId); + } + + @Override + public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.restoreHierarchyState(toolbarStates); + } + +} diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 0c7eefa..e069876 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -789,7 +789,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) } // libart tolerates libdvm flags, but not vice versa, so only pass some options if libart. - property_get("persist.sys.dalvik.vm.lib.1", dalvikVmLibBuf, "libdvm.so"); + property_get("persist.sys.dalvik.vm.lib.2", dalvikVmLibBuf, "libart.so"); bool libart = (strncmp(dalvikVmLibBuf, "libart", 6) == 0); if (libart) { diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 5bc0f62..6f256f0 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -293,6 +293,12 @@ static void android_view_ThreadedRenderer_destroyLayer(JNIEnv* env, jobject claz proxy->destroyLayer(layer); } +static void android_view_ThreadedRenderer_flushCaches(JNIEnv* env, jobject clazz, + jlong proxyPtr, jint flushMode) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->flushCaches(static_cast<Caches::FlushMode>(flushMode)); +} + static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz, jlong proxyPtr) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); @@ -334,6 +340,7 @@ static JNINativeMethod gMethods[] = { { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, { "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto }, { "nDestroyLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_destroyLayer }, + { "nFlushCaches", "(JI)V", (void*) android_view_ThreadedRenderer_flushCaches }, { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence }, { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, #endif diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1874fd8..14141d7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1014,6 +1014,13 @@ android:description="@string/permdesc_sim_communication" android:protectionLevel="dangerous" /> + <!-- Allows TvInputService to access underlying TV input hardware such as + built-in tuners and HDMI-in's. + @hide This should only be used by OEM's TvInputService's. + --> + <permission android:name="android.permission.TV_INPUT_HARDWARE" + android:protectionLevel="signatureOrSystem" /> + <!-- =========================================== --> <!-- Permissions associated with audio capture --> <!-- =========================================== --> diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml index e87352f..4c4f6a4 100644 --- a/core/res/res/anim/input_method_exit.xml +++ b/core/res/res/anim/input_method_exit.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/res/anim/fade_out.xml -** -** Copyright 2007, The Android Open Source Project +/* Copyright 2007, 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. @@ -19,7 +17,7 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <translate android:fromYDelta="0" android:toYDelta="10%" + <translate android:fromYDelta="0" android:toYDelta="-20%" android:interpolator="@interpolator/accelerate_quint" android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" diff --git a/core/res/res/anim/voice_activity_close_enter.xml b/core/res/res/anim/voice_activity_close_enter.xml new file mode 100644 index 0000000..4f3d3d5 --- /dev/null +++ b/core/res/res/anim/voice_activity_close_enter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/voice_activity_close_exit.xml b/core/res/res/anim/voice_activity_close_exit.xml new file mode 100644 index 0000000..023b012 --- /dev/null +++ b/core/res/res/anim/voice_activity_close_exit.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <translate android:fromYDelta="0" android:toYDelta="-20%" + android:interpolator="@interpolator/accelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/voice_activity_open_enter.xml b/core/res/res/anim/voice_activity_open_enter.xml new file mode 100644 index 0000000..57fba2a --- /dev/null +++ b/core/res/res/anim/voice_activity_open_enter.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/fade_in.xml +** +** Copyright 2007, 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <translate android:fromYDelta="-20%" android:toYDelta="0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.5" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_cubic" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/voice_activity_open_exit.xml b/core/res/res/anim/voice_activity_open_exit.xml new file mode 100644 index 0000000..4f3d3d5 --- /dev/null +++ b/core/res/res/anim/voice_activity_open_exit.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/voice_layer_enter.xml b/core/res/res/anim/voice_layer_enter.xml new file mode 100644 index 0000000..57fba2a --- /dev/null +++ b/core/res/res/anim/voice_layer_enter.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/fade_in.xml +** +** Copyright 2007, 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <translate android:fromYDelta="-20%" android:toYDelta="0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.5" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_cubic" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/voice_layer_exit.xml b/core/res/res/anim/voice_layer_exit.xml new file mode 100644 index 0000000..023b012 --- /dev/null +++ b/core/res/res/anim/voice_layer_exit.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <translate android:fromYDelta="0" android:toYDelta="-20%" + android:interpolator="@interpolator/accelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/drawable/ic_audio_ring_notif.xml b/core/res/res/drawable/ic_audio_ring_notif.xml index 247d1b4..b52db5c 100644 --- a/core/res/res/drawable/ic_audio_ring_notif.xml +++ b/core/res/res/drawable/ic_audio_ring_notif.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> +</vector> diff --git a/core/res/res/drawable/ic_audio_ring_notif_mute.xml b/core/res/res/drawable/ic_audio_ring_notif_mute.xml index 72aaa9d..8d7d6cb 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_mute.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_mute.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_mute_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" /> +</vector> diff --git a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml index 9e31aba..2f1d940 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_vibrate_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/> +</vector> diff --git a/core/res/res/layout/global_actions_silent_mode.xml b/core/res/res/layout/global_actions_silent_mode.xml index 79401af..a358623 100644 --- a/core/res/res/layout/global_actions_silent_mode.xml +++ b/core/res/res/layout/global_actions_silent_mode.xml @@ -37,7 +37,7 @@ android:layout_marginEnd="8dp" android:layout_marginTop="6dp" android:layout_marginBottom="6dp" - android:src="@drawable/ic_audio_vol_mute" + android:src="@drawable/ic_audio_ring_notif_mute" android:scaleType="center" android:duplicateParentState="true" android:background="@drawable/silent_mode_indicator" @@ -94,7 +94,7 @@ android:layout_marginEnd="8dp" android:layout_marginTop="6dp" android:layout_marginBottom="6dp" - android:src="@drawable/ic_audio_vol" + android:src="@drawable/ic_audio_ring_notif" android:scaleType="center" android:duplicateParentState="true" android:background="@drawable/silent_mode_indicator" diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml new file mode 100644 index 0000000..290c7da --- /dev/null +++ b/core/res/res/layout/screen_toolbar.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- +This is an optimized layout for a screen with a toolbar enabled. +--> + +<com.android.internal.widget.ActionBarOverlayLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/decor_content_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:splitMotionEvents="false" + android:theme="?attr/actionBarTheme"> + <FrameLayout android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <com.android.internal.widget.ActionBarContainer + android:id="@+id/action_bar_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + style="?attr/actionBarStyle" + android:viewName="android:action_bar" + android:gravity="top"> + <Toolbar + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/toolbarStyle" /> + <com.android.internal.widget.ActionBarContextView + android:id="@+id/action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + style="?attr/actionModeStyle" /> + </com.android.internal.widget.ActionBarContainer> +</com.android.internal.widget.ActionBarOverlayLayout> diff --git a/core/res/res/layout/volume_adjust.xml b/core/res/res/layout/volume_adjust.xml deleted file mode 100644 index 3ad1f23..0000000 --- a/core/res/res/layout/volume_adjust.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2007 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/visible_panel" - android:orientation="horizontal" - android:layout_width="300dp" - android:layout_height="wrap_content"> - - <LinearLayout - android:id="@+id/slider_group" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> - <!-- Sliders go here --> - </LinearLayout> - - <ImageView - android:id="@+id/expand_button_divider" - android:src="?attr/dividerVertical" - android:layout_width="wrap_content" - android:layout_height="32dip" - android:scaleType="fitXY" - android:layout_gravity="top" - android:layout_marginTop="16dip" - android:layout_marginBottom="16dip" /> - - <ImageView - android:id="@+id/expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:padding="16dip" - android:background="?attr/selectableItemBackground" - android:src="@drawable/ic_sysbar_quicksettings" /> - -</LinearLayout> diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index 8f83ab2..3180e58 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -19,13 +19,9 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">200dp</dimen> + <dimen name="thumbnail_width">512dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">177dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">512dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">512dp</dimen> + <dimen name="thumbnail_height">512dp</dimen> <!-- The maximum number of action buttons that should be permitted within an action bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit. "always" items can override this. --> diff --git a/core/res/res/values-sw720dp/dimens.xml b/core/res/res/values-sw720dp/dimens.xml index 040bb5b..21235ec 100644 --- a/core/res/res/values-sw720dp/dimens.xml +++ b/core/res/res/values-sw720dp/dimens.xml @@ -35,13 +35,9 @@ <item type="dimen" name="dialog_fixed_height_minor">90%</item> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">230dp</dimen> + <dimen name="thumbnail_width">640dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">135dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">512dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">512dp</dimen> + <dimen name="thumbnail_height">640dp</dimen> <!-- Preference activity, vertical padding for the header list --> <dimen name="preference_screen_header_vertical_padding">32dp</dimen> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c05dfca..5fec907 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -493,6 +493,9 @@ {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. --> <attr name="windowAllowExitTransitionOverlap" format="boolean"/> + <!-- Internal layout used internally for window decor --> + <attr name="windowActionBarFullscreenDecorLayout" format="reference" /> + <!-- ============ --> <!-- Alert Dialog styles --> <!-- ============ --> @@ -685,6 +688,7 @@ <!-- Default ActivityChooserView style. --> <attr name="activityChooserViewStyle" format="reference" /> + <!-- Default Toolbar style. --> <attr name="toolbarStyle" format="reference" /> <!-- Fast scroller styles --> @@ -1713,6 +1717,7 @@ <attr name="windowSwipeToDismiss" /> <attr name="windowContentTransitions" /> <attr name="windowContentTransitionManager" /> + <attr name="windowActionBarFullscreenDecorLayout" /> <!-- The minimum width the window is allowed to be, along the major axis of the screen. That is, when in landscape. Can be either @@ -6398,11 +6403,17 @@ <attr name="indeterminateProgressStyle" format="reference" /> <!-- Specifies the horizontal padding on either end for an embedded progress bar. --> <attr name="progressBarPadding" format="dimension" /> + <!-- Up navigation glyph --> + <attr name="homeAsUpIndicator" /> <!-- Specifies padding that should be applied to the left and right sides of system-provided items in the bar. --> <attr name="itemPadding" format="dimension" /> <!-- Set true to hide the action bar on a vertical nested scroll of content. --> <attr name="hideOnContentScroll" format="boolean" /> + <attr name="contentInsetStart" format="dimension" /> + <attr name="contentInsetEnd" format="dimension" /> + <attr name="contentInsetLeft" format="dimension" /> + <attr name="contentInsetRight" format="dimension" /> </declare-styleable> <declare-styleable name="ActionMode"> @@ -6653,10 +6664,19 @@ <attr name="titleMarginEnd" format="dimension" /> <attr name="titleMarginTop" format="dimension" /> <attr name="titleMarginBottom" format="dimension" /> - <attr name="contentInsetStart" format="dimension" /> - <attr name="contentInsetEnd" format="dimension" /> - <attr name="contentInsetLeft" format="dimension" /> - <attr name="contentInsetRight" format="dimension" /> + <attr name="contentInsetStart" /> + <attr name="contentInsetEnd" /> + <attr name="contentInsetLeft" /> + <attr name="contentInsetRight" /> + <attr name="maxButtonHeight" format="dimension" /> + <attr name="navigationButtonStyle" format="reference" /> + <attr name="buttonGravity"> + <!-- Push object to the top of its container, not changing its size. --> + <flag name="top" value="0x30" /> + <!-- Push object to the bottom of its container, not changing its size. --> + <flag name="bottom" value="0x50" /> + </attr> + <attr name="collapseIcon" format="reference" /> </declare-styleable> <declare-styleable name="Toolbar_LayoutParams"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b1f256e..acfbe2d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -885,41 +885,57 @@ be passed a persistable Bundle in their Intent.extras. --> <attr name="persistable" format="boolean" /> - <!-- Specify whether this activity should always be launched in doc-centric mode. For - values other than <code>none</code> the activity must be defined with + <!-- This attribute specifies that an activity shall become the root activity of a + new task each time it is launched. Using this attribute permits the user to + have multiple documents from the same applications appear in the recent tasks list. + + <p>Such a document is any kind of item for which an application may want to + maintain multiple simultaneous instances. Examples might be text files, web + pages, spreadsheets, or emails. Each such document will be in a separate + task in the recent taskss list. + + <p>This attribute is equivalent to adding the flag {@link + android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} to every Intent used to launch + the activity. + + <p>The documentLaunchMode attribute may be assigned one of three values, "none", + "intoExisting" and "always", described in detail below. For values other than + <code>none</code> the activity must be defined with {@link android.R.attr#launchMode} <code>standard</code> or <code>singleTop</code>. - This attribute can be overridden by {@link - android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. - - <p>If this attribute is not specified, <code>none</code> will be used. - Note that this launch behavior can be changed in some ways at runtime - through the {@link android.content.Intent} flags - {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. --> + If this attribute is not specified, <code>none</code> will be used. + Note that <code>none</code> can be overridden at run time if the Intent used + to launch it contains the flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. + Similarly <code>intoExisting</code> will be overridden by the flag + {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} combined with + {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. --> <attr name="documentLaunchMode"> <!-- The default mode, which will create a new task only when {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} is set. --> <enum name="none" value="0" /> - <!-- All tasks will be searched for a matching Intent. If one is found - That task will cleared and restarted with the root activity receiving a call + <!-- All tasks will be searched for one whose base Intent's ComponentName and + data URI match those of the launching Intent. If such a task is found + that task will be cleared and restarted with the root activity receiving a call to {@link android.app.Activity#onNewIntent Activity.onNewIntent}. If no such task is found a new task will be created. - This is the equivalent of with {@link + <p>This is the equivalent of launching an activity with {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} - without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK - Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + set and without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK} set. --> <enum name="intoExisting" value="1" /> - <!-- A new task rooted at this activity will be created. - This is the equivalent of with {@link + <!-- A new task rooted at this activity will be created. This will happen whether or + not there is an existing task whose ComponentName and data URI match + that of the launcing intent This is the equivalent of launching an activity + with {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} - paired with {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK - Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + and {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK} both set. --> <enum name="always" value="2" /> </attr> - <!-- Tasks launched by activities with this attribute will remain in the recent task + <!-- Tasks launched by activities with this attribute will remain in the recent tasks list until the last activity in the task is completed. When that happens the task - will be automatically removed from the recent task list. + will be automatically removed from the recent tasks list. This attribute is the equivalent of {@link android.content.Intent#FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5375c14..e9d8ccc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1538,9 +1538,6 @@ --> <string-array translatable="false" name="config_globalActionsList"> <item>power</item> - <item>airplane</item> - <item>bugreport</item> - <item>silent</item> <item>users</item> </string-array> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 52b021f..657f614 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -19,13 +19,9 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">164dp</dimen> + <dimen name="thumbnail_width">256dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">145dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">256dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">256dp</dimen> + <dimen name="thumbnail_height">256dp</dimen> <!-- The standard size (both width and height) of an application icon that will be displayed in the app launcher and elsewhere. --> <dimen name="app_icon_size">48dip</dimen> @@ -206,9 +202,6 @@ <!-- Default width for a textview error popup --> <dimen name="textview_error_popup_default_width">240dip</dimen> - <!-- Volume panel y offset --> - <dimen name="volume_panel_top">16dp</dimen> - <!-- Default padding to apply to AppWidgetHostViews containing widgets targeting API level 14 and up. --> <dimen name="default_app_widget_padding_left">8dp</dimen> <dimen name="default_app_widget_padding_top">8dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ce0d2d5..7dc967c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2181,9 +2181,6 @@ <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> - <public type="dimen" name="recents_thumbnail_height" /> - <public type="dimen" name="recents_thumbnail_width" /> - <public-padding type="id" name="l_resource_pad" end="0x01020040" /> <public type="id" name="mask" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index f6cd9e8..933063f 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -171,8 +171,8 @@ please see styles_device_defaults.xml. <!-- Window animations that are applied to voice interaction overlay windows. --> <style name="Animation.VoiceInteractionSession"> - <item name="windowEnterAnimation">@anim/input_method_enter</item> - <item name="windowExitAnimation">@anim/input_method_exit</item> + <item name="windowEnterAnimation">@anim/voice_layer_enter</item> + <item name="windowExitAnimation">@anim/voice_layer_exit</item> </style> <!-- Special optional fancy IM animations. @hide --> @@ -1197,6 +1197,16 @@ please see styles_device_defaults.xml. <item name="android:subtitleTextAppearance">@android:style/TextAppearance.Widget.Toolbar.Subtitle</item> <item name="android:minHeight">?android:attr/actionBarSize</item> <item name="android:titleMargins">4dp</item> + <item name="android:maxButtonHeight">56dp</item> + <item name="android:buttonGravity">top</item> + <item name="android:navigationButtonStyle">@android:style/Widget.Toolbar.Button.Navigation</item> + <item name="android:collapseIcon">?android:attr/homeAsUpIndicator</item> + </style> + + <style name="Widget.Toolbar.Button.Navigation" parent="@android:style/Widget"> + <item name="android:background">?android:attr/selectableItemBackground</item> + <item name="android:minWidth">56dp</item> + <item name="android:scaleType">center</item> </style> <style name="TextAppearance.Widget.ActionBar.Title" @@ -2394,7 +2404,6 @@ please see styles_device_defaults.xml. <item name="android:background">@android:drawable/ab_transparent_light_holo</item> <item name="android:backgroundStacked">@android:drawable/ab_stacked_transparent_light_holo</item> <item name="android:backgroundSplit">@android:drawable/ab_bottom_transparent_light_holo</item> - <item name="android:homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item> <item name="android:progressBarStyle">@android:style/Widget.Holo.Light.ProgressBar.Horizontal</item> <item name="android:indeterminateProgressStyle">@android:style/Widget.Holo.Light.ProgressBar</item> </style> diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml index 2e7a5b1..ea32681 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -773,7 +773,7 @@ please see styles_device_defaults.xml. <item name="background">@null</item> <item name="backgroundStacked">@null</item> <item name="backgroundSplit">@null</item> - <item name="displayOptions">useLogo|showHome|showTitle</item> + <item name="displayOptions">showTitle</item> <item name="divider">?attr/dividerVertical</item> <item name="titleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Title</item> <item name="subtitleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Subtitle</item> @@ -783,6 +783,7 @@ please see styles_device_defaults.xml. <item name="itemPadding">8dip</item> <item name="homeLayout">@layout/action_bar_home_quantum</item> <item name="gravity">center_vertical</item> + <item name="contentInsetStart">56dp</item> </style> <style name="Widget.Quantum.ActionBar.Solid"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dcff978..6e1629b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -69,8 +69,6 @@ <java-symbol type="id" name="edittext_container" /> <java-symbol type="id" name="enter_pin_section" /> <java-symbol type="id" name="expand_activities_button" /> - <java-symbol type="id" name="expand_button" /> - <java-symbol type="id" name="expand_button_divider" /> <java-symbol type="id" name="expires_on" /> <java-symbol type="id" name="find_next" /> <java-symbol type="id" name="find_prev" /> @@ -161,9 +159,7 @@ <java-symbol type="id" name="share" /> <java-symbol type="id" name="shortcut" /> <java-symbol type="id" name="skip_button" /> - <java-symbol type="id" name="slider_group" /> <java-symbol type="id" name="split_action_bar" /> - <java-symbol type="id" name="stream_icon" /> <java-symbol type="id" name="submit_area" /> <java-symbol type="id" name="switch_new" /> <java-symbol type="id" name="switch_old" /> @@ -181,7 +177,6 @@ <java-symbol type="id" name="topPanel" /> <java-symbol type="id" name="up" /> <java-symbol type="id" name="value" /> - <java-symbol type="id" name="visible_panel" /> <java-symbol type="id" name="websearch" /> <java-symbol type="id" name="wifi_p2p_wps_pin" /> <java-symbol type="id" name="year" /> @@ -343,7 +338,6 @@ <java-symbol type="dimen" name="search_view_preferred_width" /> <java-symbol type="dimen" name="textview_error_popup_default_width" /> <java-symbol type="dimen" name="toast_y_offset" /> - <java-symbol type="dimen" name="volume_panel_top" /> <java-symbol type="dimen" name="action_bar_stacked_max_height" /> <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" /> <java-symbol type="dimen" name="notification_text_size" /> @@ -1199,8 +1193,6 @@ <java-symbol type="layout" name="time_picker_legacy" /> <java-symbol type="layout" name="time_picker_dialog" /> <java-symbol type="layout" name="transient_notification" /> - <java-symbol type="layout" name="volume_adjust" /> - <java-symbol type="layout" name="volume_adjust_item" /> <java-symbol type="layout" name="voice_interaction_session" /> <java-symbol type="layout" name="web_text_view_dropdown" /> <java-symbol type="layout" name="webview_find" /> @@ -1294,6 +1286,10 @@ <java-symbol type="anim" name="dock_left_exit" /> <java-symbol type="anim" name="dock_right_enter" /> <java-symbol type="anim" name="dock_right_exit" /> + <java-symbol type="anim" name="voice_activity_close_exit" /> + <java-symbol type="anim" name="voice_activity_close_enter" /> + <java-symbol type="anim" name="voice_activity_open_exit" /> + <java-symbol type="anim" name="voice_activity_open_enter" /> <java-symbol type="array" name="config_hdmiCecLogicalDeviceType" /> <java-symbol type="array" name="config_keyboardTapVibePattern" /> @@ -1870,6 +1866,7 @@ <java-symbol type="attr" name="toolbarStyle" /> <java-symbol type="attr" name="titleTextAppearance" /> <java-symbol type="attr" name="subtitleTextAppearance" /> + <java-symbol type="attr" name="windowActionBarFullscreenDecorLayout" /> <java-symbol type="drawable" name="ic_lock_bugreport" /> <java-symbol type="id" name="icon_frame" /> <java-symbol type="style" name="Animation.VolumePanel" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index aaab949..41f4ff8 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -192,6 +192,7 @@ please see themes_device_defaults.xml. <item name="windowDrawsSystemBarBackgrounds">false</item> <item name="statusBarColor">@android:color/black</item> <item name="navigationBarColor">@android:color/black</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item> <!-- Define these here; ContextThemeWrappers around themes that define them should always clear these values. --> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index bb787bb..484c694 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -162,6 +162,7 @@ please see themes_device_defaults.xml. <item name="windowActionBar">true</item> <item name="windowActionModeOverlay">false</item> <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> <item name="statusBarColor">?attr/colorPrimaryDark</item> <item name="navigationBarColor">?attr/colorPrimaryDark</item> @@ -505,6 +506,7 @@ please see themes_device_defaults.xml. <item name="windowActionBar">true</item> <item name="windowActionModeOverlay">false</item> <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> <item name="statusBarColor">?attr/colorPrimaryDark</item> <item name="navigationBarColor">?attr/colorPrimaryDark</item> diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index a759a79..210ea86b 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -31,7 +31,6 @@ public class FontFamily { public FontFamily() { mNativePtr = nCreateFamily(); - mNativePtr = nCreateFamily(); if (mNativePtr == 0) { throw new RuntimeException(); } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 64451c4..b7613fb 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -66,6 +66,9 @@ public class Typeface { static Map<String, Typeface> sSystemFontMap; static FontFamily[] sFallbackFonts; + static final String SYSTEM_FONTS_CONFIG = "system_fonts.xml"; + static final String FALLBACK_FONTS_CONFIG = "fallback_fonts.xml"; + /** * @hide */ @@ -249,10 +252,16 @@ public class Typeface { return fontFamily; } - static { + /* + * (non-Javadoc) + * + * This should only be called once, from the static class initializer block. + */ + private static void init() { // Load font config and initialize Minikin state - String systemConfigFilename = "/system/etc/system_fonts.xml"; - String configFilename = "/system/etc/fallback_fonts.xml"; + File systemFontConfigLocation = getSystemFontConfigLocation(); + File systemConfigFilename = new File(systemFontConfigLocation, SYSTEM_FONTS_CONFIG); + File configFilename = new File(systemFontConfigLocation, FALLBACK_FONTS_CONFIG); try { // TODO: throws an exception non-Minikin builds, to fail early; // remove when Minikin-only @@ -301,7 +310,10 @@ public class Typeface { } catch (XmlPullParserException e) { Log.e(TAG, "XML parse exception for " + configFilename); } + } + static { + init(); // Set up defaults and typefaces exposed in public API DEFAULT = create((String) null, 0); DEFAULT_BOLD = create((String) null, Typeface.BOLD); @@ -315,6 +327,11 @@ public class Typeface { create((String) null, Typeface.ITALIC), create((String) null, Typeface.BOLD_ITALIC), }; + + } + + private static File getSystemFontConfigLocation() { + return new File("/system/etc/"); } @Override diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index e2bd50d..9a63fa3 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -101,6 +101,7 @@ import java.util.HashMap; * <dd>Sets the Miter limit for a stroked path</dd></dt> * </dl> * </dd> + * @hide */ public class VectorDrawable extends Drawable { private static final String LOGTAG = VectorDrawable.class.getSimpleName(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b6b3428..160fbea 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -556,6 +556,13 @@ bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) return LayerRenderer::copyLayer(layer->backingLayer(), bitmap); } +void CanvasContext::flushCaches(Caches::FlushMode flushMode) { + if (mGlobalContext->hasContext()) { + requireGlContext(); + Caches::getInstance().flush(flushMode); + } +} + void CanvasContext::runWithGlContext(RenderTask* task) { requireGlContext(); task->run(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a54b33e..da85d44 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -63,6 +63,8 @@ public: bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); + void flushCaches(Caches::FlushMode flushMode); + void invokeFunctor(Functor* functor); void runWithGlContext(RenderTask* task); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 2e103d8..8e772f2 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -282,6 +282,18 @@ void RenderProxy::destroyLayer(DeferredLayerUpdater* layer) { post(task); } +CREATE_BRIDGE2(flushCaches, CanvasContext* context, Caches::FlushMode flushMode) { + args->context->flushCaches(args->flushMode); + return NULL; +} + +void RenderProxy::flushCaches(Caches::FlushMode flushMode) { + SETUP_TASK(flushCaches); + args->context = mContext; + args->flushMode = flushMode; + post(task); +} + CREATE_BRIDGE0(fence) { // Intentionally empty return NULL; diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 8aeb264..22d4e22 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -29,6 +29,7 @@ #include <utils/StrongPointer.h> #include <utils/Vector.h> +#include "../Caches.h" #include "DrawFrameTask.h" namespace android { @@ -81,6 +82,8 @@ public: ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); ANDROID_API void destroyLayer(DeferredLayerUpdater* layer); + ANDROID_API void flushCaches(Caches::FlushMode flushMode); + ANDROID_API void fence(); ANDROID_API void notifyFramePending(); diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index bb23a36..5a3aaab 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.Set; /** + * @hide * A class to encapsulate a collection of attributes describing information about an audio * player or recorder. */ diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java new file mode 100644 index 0000000..c088906 --- /dev/null +++ b/media/java/android/media/AudioDevicePort.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The AudioDevicePort is a specialized type of AudioPort + * describing an input (e.g microphone) or output device (e.g speaker) + * of the system. + * An AudioDevicePort is an AudioPort controlled by the audio HAL, almost always a physical + * device at the boundary of the audio system. + * In addition to base audio port attributes, the device descriptor contains: + * - the device type (e.g AudioManager.DEVICE_OUT_SPEAKER) + * - the device address (e.g MAC adddress for AD2P sink). + * @see AudioPort + * @hide + */ + +public class AudioDevicePort extends AudioPort { + + private final int mType; + private final String mAddress; + + AudioDevicePort(AudioHandle handle, int[] samplingRates, int[] channelMasks, + int[] formats, AudioGain[] gains, int type, String address) { + super(handle, + (AudioManager.isInputDevice(type) == true) ? + AudioPort.ROLE_SOURCE : AudioPort.ROLE_SINK, + samplingRates, channelMasks, formats, gains); + mType = type; + mAddress = address; + } + + /** + * Get the device type (e.g AudioManager.DEVICE_OUT_SPEAKER) + */ + public int type() { + return mType; + } + + /** + * Get the device address. Address format varies with the device type. + * - USB devices ({@link AudioManager#DEVICE_OUT_USB_DEVICE}, + * {@link AudioManager#DEVICE_IN_USB_DEVICE}) use an address composed of the ALSA card number + * and device number: "card=2;device=1" + * - Bluetooth devices ({@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO}, + * {@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO}, {@link AudioManager#DEVICE_OUT_BLUETOOTH_A2DP}) + * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by + * {@link BluetoothDevice#getAddress()}. + * - Deivces that do not have an address will indicate an empty string "". + */ + public String address() { + return mAddress; + } + + /** + * Build a specific configuration of this audio device port for use by methods + * like AudioManager.connectAudioPatch(). + */ + public AudioDevicePortConfig buildConfig(int samplingRate, int channelMask, int format, + AudioGainConfig gain) { + return new AudioDevicePortConfig(this, samplingRate, channelMask, format, gain); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof AudioDevicePort)) { + return false; + } + return super.equals(o); + } +} diff --git a/media/java/android/media/AudioDevicePortConfig.java b/media/java/android/media/AudioDevicePortConfig.java new file mode 100644 index 0000000..a381e10 --- /dev/null +++ b/media/java/android/media/AudioDevicePortConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * An AudioDevicePortConfig describes a possible configuration of an output or input device + * (speaker, headphone, microphone ...). + * It is used to specify a sink or source when creating a connection with + * AudioManager.connectAudioPatch(). + * An AudioDevicePortConfig is obtained from AudioDevicePort.buildConfig(). + * @hide + */ + +public class AudioDevicePortConfig extends AudioPortConfig { + AudioDevicePortConfig(AudioDevicePort devicePort, int samplingRate, int channelMask, + int format, AudioGainConfig gain) { + super((AudioPort)devicePort, samplingRate, channelMask, format, gain); + } + + /** + * Returns the audio device port this AudioDevicePortConfig is issued from. + */ + public AudioDevicePort port() { + return (AudioDevicePort)mPort; + } +} + diff --git a/media/java/android/media/AudioGain.java b/media/java/android/media/AudioGain.java new file mode 100644 index 0000000..57709d5 --- /dev/null +++ b/media/java/android/media/AudioGain.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The AudioGain describes a gain controller. Gain controllers are exposed by + * audio ports when the gain is configurable at this port's input or output. + * Gain values are expressed in millibels. + * A gain controller has the following attributes: + * - mode: defines modes of operation or features + * MODE_JOINT: all channel gains are controlled simultaneously + * MODE_CHANNELS: each channel gain is controlled individually + * MODE_RAMP: ramps can be applied when gain changes + * - channel mask: indicates for which channels the gain can be controlled + * - min value: minimum gain value in millibel + * - max value: maximum gain value in millibel + * - default value: gain value after reset in millibel + * - step value: granularity of gain control in millibel + * - min ramp duration: minimum ramp duration in milliseconds + * - max ramp duration: maximum ramp duration in milliseconds + * + * This object is always created by the framework and read only by applications. + * Applications get a list of AudioGainDescriptors from AudioPortDescriptor.gains() and can build a + * valid gain configuration from AudioGain.buildConfig() + * @hide + */ +public class AudioGain { + + /** + * Bit of AudioGain.mode() field indicating that + * all channel gains are controlled simultaneously + */ + public static final int MODE_JOINT = 1; + /** + * Bit of AudioGain.mode() field indicating that + * each channel gain is controlled individually + */ + public static final int MODE_CHANNELS = 2; + /** + * Bit of AudioGain.mode() field indicating that + * ramps can be applied when gain changes. The type of ramp (linear, log etc...) is + * implementation specific. + */ + public static final int MODE_RAMP = 4; + + private final int mIndex; + private final int mMode; + private final int mChannelMask; + private final int mMinValue; + private final int mMaxValue; + private final int mDefaultValue; + private final int mStepValue; + private final int mRampDurationMinMs; + private final int mRampDurationMaxMs; + + // The channel mask passed to the constructor is as specified in AudioFormat + // (e.g. AudioFormat.CHANNEL_OUT_STEREO) + AudioGain(int index, int mode, int channelMask, + int minValue, int maxValue, int defaultValue, int stepValue, + int rampDurationMinMs, int rampDurationMaxMs) { + mIndex = index; + mMode = mode; + mChannelMask = channelMask; + mMinValue = minValue; + mMaxValue = maxValue; + mDefaultValue = defaultValue; + mStepValue = stepValue; + mRampDurationMinMs = rampDurationMinMs; + mRampDurationMaxMs = rampDurationMaxMs; + } + + /** + * Bit field indicating supported modes of operation + */ + public int mode() { + return mMode; + } + + /** + * Indicates for which channels the gain can be controlled + * (e.g. AudioFormat.CHANNEL_OUT_STEREO) + */ + public int channelMask() { + return mChannelMask; + } + + /** + * Minimum gain value in millibel + */ + public int minValue() { + return mMinValue; + } + + /** + * Maximum gain value in millibel + */ + public int maxValue() { + return mMaxValue; + } + + /** + * Default gain value in millibel + */ + public int defaultValue() { + return mDefaultValue; + } + + /** + * Granularity of gain control in millibel + */ + public int stepValue() { + return mStepValue; + } + + /** + * Minimum ramp duration in milliseconds + * 0 if MODE_RAMP not set + */ + public int rampDurationMinMs() { + return mRampDurationMinMs; + } + + /** + * Maximum ramp duration in milliseconds + * 0 if MODE_RAMP not set + */ + public int rampDurationMaxMs() { + return mRampDurationMaxMs; + } + + /** + * Build a valid gain configuration for this gain controller for use by + * AudioPortDescriptor.setGain() + * @param mode: desired mode of operation + * @param channelMask: channels of which the gain should be modified. + * @param values: gain values for each channels. + * @param rampDurationMs: ramp duration if mode MODE_RAMP is set. + * ignored if MODE_JOINT. + */ + public AudioGainConfig buildConfig(int mode, int channelMask, + int[] values, int rampDurationMs) { + //TODO: check params here + return new AudioGainConfig(mIndex, this, mode, channelMask, values, rampDurationMs); + } +} diff --git a/media/java/android/media/AudioGainConfig.java b/media/java/android/media/AudioGainConfig.java new file mode 100644 index 0000000..ea61679 --- /dev/null +++ b/media/java/android/media/AudioGainConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The AudioGainConfig is used by APIs setting or getting values on a given gain + * controller. It contains a valid configuration (value, channels...) for a gain controller + * exposed by an audio port. + * @see AudioGain + * @see AudioPort + * @hide + */ +public class AudioGainConfig { + AudioGain mGain; + private final int mIndex; + private final int mMode; + private final int mChannelMask; + private final int mValues[]; + private final int mRampDurationMs; + + AudioGainConfig(int index, AudioGain gain, int mode, int channelMask, + int[] values, int rampDurationMs) { + mIndex = index; + mGain = gain; + mMode = mode; + mChannelMask = channelMask; + mValues = values; + mRampDurationMs = rampDurationMs; + } + + /** + * get the index of the parent gain. + * frameworks use only. + */ + int index() { + return mIndex; + } + + /** + * Bit field indicating requested modes of operation. See {@link AudioGain#MODE_JOINT}, + * {@link AudioGain#MODE_CHANNELS}, {@link AudioGain#MODE_RAMP} + */ + public int mode() { + return mMode; + } + + /** + * Indicates for which channels the gain is set. + * See {@link AudioFormat#CHANNEL_OUT_STEREO}, {@link AudioFormat#CHANNEL_OUT_MONO} ... + */ + public int channelMask() { + return mChannelMask; + } + + /** + * Gain values for each channel in the order of bits set in + * channelMask() from LSB to MSB + */ + public int[] values() { + return mValues; + } + + /** + * Ramp duration in milliseconds. N/A if mode() does not + * specify MODE_RAMP. + */ + public int rampDurationMs() { + return mRampDurationMs; + } +} diff --git a/media/java/android/media/AudioHandle.java b/media/java/android/media/AudioHandle.java new file mode 100644 index 0000000..b58e7a3 --- /dev/null +++ b/media/java/android/media/AudioHandle.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The AudioHandle is used by the audio framework implementation to + * uniquely identify a particular component of the routing topology + * (AudioPort or AudioPatch) + * It is not visible or used at the API. + */ +class AudioHandle { + private final int mId; + + AudioHandle(int id) { + mId = id; + } + + int id() { + return mId; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof AudioHandle)) { + return false; + } + AudioHandle ah = (AudioHandle)o; + return mId == ah.id(); + } + + @Override + public int hashCode() { + return mId; + } +} diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 9803161..f4affa0 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -39,6 +39,7 @@ import android.util.Log; import android.view.KeyEvent; import java.util.HashMap; +import java.util.ArrayList; /** * AudioManager provides access to volume and ringer mode control. @@ -2966,4 +2967,151 @@ public class AudioManager { Log.w(TAG, "Error disabling safe media volume", e); } } + + /** + * Return codes for listAudioPorts(), createAudioPatch() ... + */ + + /** @hide + */ + public static final int SUCCESS = AudioSystem.SUCCESS; + /** @hide + */ + public static final int ERROR = AudioSystem.ERROR; + /** @hide + */ + public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE; + /** @hide + */ + public static final int ERROR_INVALID_OPERATION = AudioSystem.INVALID_OPERATION; + /** @hide + */ + public static final int ERROR_PERMISSION_DENIED = AudioSystem.PERMISSION_DENIED; + /** @hide + */ + public static final int ERROR_NO_INIT = AudioSystem.NO_INIT; + /** @hide + */ + public static final int ERROR_DEAD_OBJECT = AudioSystem.DEAD_OBJECT; + + /** + * Returns a list of descriptors for all audio ports managed by the audio framework. + * Audio ports are nodes in the audio framework or audio hardware that can be configured + * or connected and disconnected with createAudioPatch() or releaseAudioPatch(). + * See AudioPort for a list of attributes of each audio port. + * @param ports An AudioPort ArrayList where the list will be returned. + * @hide + */ + public int listAudioPorts(ArrayList<AudioPort> ports) { + return ERROR_INVALID_OPERATION; + } + + /** + * Specialized version of listAudioPorts() listing only audio devices (AudioDevicePort) + * @see listAudioPorts(ArrayList<AudioPort>) + * @hide + */ + public int listAudioDevicePorts(ArrayList<AudioPort> devices) { + return ERROR_INVALID_OPERATION; + } + + /** + * Create a connection between two or more devices. The framework will reject the request if + * device types are not compatible or the implementation does not support the requested + * configuration. + * NOTE: current implementation is limited to one source and one sink per patch. + * @param patch AudioPatch array where the newly created patch will be returned. + * As input, if patch[0] is not null, the specified patch will be replaced by the + * new patch created. This avoids calling releaseAudioPatch() when modifying a + * patch and allows the implementation to optimize transitions. + * @param sources List of source audio ports. All must be AudioPort.ROLE_SOURCE. + * @param sinks List of sink audio ports. All must be AudioPort.ROLE_SINK. + * + * @return - {@link #SUCCESS} if connection is successful. + * - {@link #ERROR_BAD_VALUE} if incompatible device types are passed. + * - {@link #ERROR_INVALID_OPERATION} if the requested connection is not supported. + * - {@link #ERROR_PERMISSION_DENIED} if the client does not have permission to create + * a patch. + * - {@link #ERROR_DEAD_OBJECT} if the server process is dead + * - {@link #ERROR} if patch cannot be connected for any other reason. + * + * patch[0] contains the newly created patch + * @hide + */ + public int createAudioPatch(AudioPatch[] patch, + AudioPortConfig[] sources, + AudioPortConfig[] sinks) { + return ERROR_INVALID_OPERATION; + } + + /** + * Releases an existing audio patch connection. + * @param patch The audio patch to disconnect. + * @return - {@link #SUCCESS} if disconnection is successful. + * - {@link #ERROR_BAD_VALUE} if the specified patch does not exist. + * - {@link #ERROR_PERMISSION_DENIED} if the client does not have permission to release + * a patch. + * - {@link #ERROR_DEAD_OBJECT} if the server process is dead + * - {@link #ERROR} if patch cannot be released for any other reason. + * @hide + */ + public int releaseAudioPatch(AudioPatch patch) { + return ERROR_INVALID_OPERATION; + } + + /** + * List all existing connections between audio ports. + * @param patches An AudioPatch array where the list will be returned. + * @hide + */ + public int listAudioPatches(ArrayList<AudioPatch> patches) { + return ERROR_INVALID_OPERATION; + } + + /** + * Set the gain on the specified AudioPort. The AudioGainConfig config is build by + * AudioGain.buildConfig() + * @hide + */ + public int setAudioPortGain(AudioPort port, AudioGainConfig gain) { + return ERROR_INVALID_OPERATION; + } + + /** + * Listener registered by client to be notified upon new audio port connections, + * disconnections or attributes update. + * @hide + */ + public interface OnAudioPortUpdateListener { + /** + * Callback method called upon audio port list update. + * @param portList the updated list of audio ports + */ + public void OnAudioPortListUpdate(AudioPort[] portList); + + /** + * Callback method called upon audio patch list update. + * @param patchList the updated list of audio patches + */ + public void OnAudioPatchListUpdate(AudioPatch[] patchList); + + /** + * Callback method called when the mediaserver dies + */ + public void OnServiceDied(); + } + + /** + * Register an audio port update listener. + * @hide + */ + public void registerAudioPortUpdateListener(OnAudioPortUpdateListener l) { + } + + /** + * Unregister an audio port update listener. + * @hide + */ + public void unregisterAudioPortUpdateListener(OnAudioPortUpdateListener l) { + } } diff --git a/media/java/android/media/AudioMixPort.java b/media/java/android/media/AudioMixPort.java new file mode 100644 index 0000000..1500a43 --- /dev/null +++ b/media/java/android/media/AudioMixPort.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The AudioMixPort is a specialized type of AudioPort + * describing an audio mix or stream at an input or output stream of the audio + * framework. + * @see AudioPort + * @hide + */ + +public class AudioMixPort extends AudioPort { + + AudioMixPort(AudioHandle handle, int role, int[] samplingRates, int[] channelMasks, + int[] formats, AudioGain[] gains) { + super(handle, role, samplingRates, channelMasks, formats, gains); + } + + /** + * Build a specific configuration of this audio mix port for use by methods + * like AudioManager.connectAudioPatch(). + */ + public AudioMixPortConfig buildConfig(int samplingRate, int channelMask, int format, + AudioGainConfig gain) { + return new AudioMixPortConfig(this, samplingRate, channelMask, format, gain); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof AudioMixPort)) { + return false; + } + return super.equals(o); + } + +} diff --git a/media/java/android/media/AudioMixPortConfig.java b/media/java/android/media/AudioMixPortConfig.java new file mode 100644 index 0000000..8eb9ef4 --- /dev/null +++ b/media/java/android/media/AudioMixPortConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * An AudioMixPortConfig describes a possible configuration of an output or input mixer. + * It is used to specify a sink or source when creating a connection with + * AudioManager.connectAudioPatch(). + * An AudioMixPortConfig is obtained from AudioMixPort.buildConfig(). + * @hide + */ + +public class AudioMixPortConfig extends AudioPortConfig { + + AudioMixPortConfig(AudioMixPort mixPort, int samplingRate, int channelMask, int format, + AudioGainConfig gain) { + super((AudioPort)mixPort, samplingRate, channelMask, format, gain); + } + + /** + * Returns the audio mix port this AudioMixPortConfig is issued from. + */ + public AudioMixPort port() { + return (AudioMixPort)mPort; + } +} + diff --git a/media/java/android/media/AudioPatch.java b/media/java/android/media/AudioPatch.java new file mode 100644 index 0000000..72291f6 --- /dev/null +++ b/media/java/android/media/AudioPatch.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + + +/** + * An AudioPatch describes a connection between audio sources and audio sinks. + * An audio source can be an output mix (playback AudioBus) or an input device (microphone). + * An audio sink can be an output device (speaker) or an input mix (capture AudioBus). + * An AudioPatch is created by AudioManager.connectAudioPatch() and released by + * AudioManager.disconnectAudioPatch() + * It contains the list of source and sink AudioPortConfig showing audio port configurations + * being connected. + * @hide + */ +public class AudioPatch { + + private final AudioHandle mHandle; + private final AudioPortConfig[] mSources; + private final AudioPortConfig[] mSinks; + + AudioPatch(AudioHandle patchHandle, AudioPortConfig[] sources, AudioPortConfig[] sinks) { + mHandle = patchHandle; + mSources = sources; + mSinks = sinks; + } + + /** + * Retrieve the list of sources of this audio patch. + */ + public AudioPortConfig[] sources() { + return mSources; + } + + /** + * Retreive the list of sinks of this audio patch. + */ + public AudioPortConfig[] sinks() { + return mSinks; + } +} diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java new file mode 100644 index 0000000..9aeddef --- /dev/null +++ b/media/java/android/media/AudioPort.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + + +/** + * An audio port is a node of the audio framework or hardware that can be connected to or + * disconnect from another audio node to create a specific audio routing configuration. + * Examples of audio ports are an output device (speaker) or an output mix (see AudioMixPort). + * All attributes that are relevant for applications to make routing selection are decribed + * in an AudioPort, in particular: + * - possible channel mask configurations. + * - audio format (PCM 16bit, PCM 24bit...) + * - gain: a port can be associated with one or more gain controllers (see AudioGain). + * + * This object is always created by the framework and read only by applications. + * A list of all audio port descriptors currently available for applications to control + * is obtained by AudioManager.listAudioPorts(). + * An application can obtain an AudioPortConfig for a valid configuration of this port + * by calling AudioPort.buildConfig() and use this configuration + * to create a connection between audio sinks and sources with AudioManager.connectAudioPatch() + * + * @hide + */ +public class AudioPort { + + /** + * For use by the audio framework. + */ + public static final int ROLE_NONE = 0; + /** + * The audio port is a source (produces audio) + */ + public static final int ROLE_SOURCE = 1; + /** + * The audio port is a sink (consumes audio) + */ + public static final int ROLE_SINK = 2; + + /** + * audio port type for use by audio framework implementation + */ + public static final int TYPE_NONE = 0; + /** + */ + public static final int TYPE_DEVICE = 1; + /** + */ + public static final int TYPE_SUBMIX = 2; + /** + */ + public static final int TYPE_SESSION = 3; + + + AudioHandle mHandle; + private final int mRole; + private final int[] mSamplingRates; + private final int[] mChannelMasks; + private final int[] mFormats; + private final AudioGain[] mGains; + private AudioPortConfig mActiveConfig; + + AudioPort(AudioHandle handle, int role, int[] samplingRates, int[] channelMasks, + int[] formats, AudioGain[] gains) { + mHandle = handle; + mRole = role; + mSamplingRates = samplingRates; + mChannelMasks = channelMasks; + mFormats = formats; + mGains = gains; + } + + AudioHandle handle() { + return mHandle; + } + + /** + * Get the audio port role + */ + public int role() { + return mRole; + } + + /** + * Get the list of supported sampling rates + * Empty array if sampling rate is not relevant for this audio port + */ + public int[] samplingRates() { + return mSamplingRates; + } + + /** + * Get the list of supported channel mask configurations + * (e.g AudioFormat.CHANNEL_OUT_STEREO) + * Empty array if channel mask is not relevant for this audio port + */ + public int[] channelMasks() { + return mChannelMasks; + } + + /** + * Get the list of supported audio format configurations + * (e.g AudioFormat.ENCODING_PCM_16BIT) + * Empty array if format is not relevant for this audio port + */ + public int[] formats() { + return mFormats; + } + + /** + * Get the list of gain descriptors + * Empty array if this port does not have gain control + */ + public AudioGain[] gains() { + return mGains; + } + + /** + * Get the gain descriptor at a given index + */ + AudioGain gain(int index) { + return mGains[index]; + } + + /** + * Build a specific configuration of this audio port for use by methods + * like AudioManager.connectAudioPatch(). + * @param channelMask The desired channel mask. AudioFormat.CHANNEL_OUT_DEFAULT if no change + * from active configuration requested. + * @param format The desired audio format. AudioFormat.ENCODING_DEFAULT if no change + * from active configuration requested. + * @param gain The desired gain. null if no gain changed requested. + */ + public AudioPortConfig buildConfig(int samplingRate, int channelMask, int format, + AudioGainConfig gain) { + return new AudioPortConfig(this, samplingRate, channelMask, format, gain); + } + + /** + * Get currently active configuration of this audio port. + */ + public AudioPortConfig activeConfig() { + return mActiveConfig; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof AudioPort)) { + return false; + } + AudioPort ap = (AudioPort)o; + return mHandle.equals(ap.handle()); + } + + @Override + public int hashCode() { + return mHandle.hashCode(); + } +} + diff --git a/media/java/android/media/AudioPortConfig.java b/media/java/android/media/AudioPortConfig.java new file mode 100644 index 0000000..5dc768d --- /dev/null +++ b/media/java/android/media/AudioPortConfig.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * An AudioPortConfig contains a possible configuration of an audio port chosen + * among all possible attributes described by an AudioPort. + * An AudioPortConfig is created by AudioPort.buildConfiguration(). + * AudioPorts are used to specify the sources and sinks of a patch created + * with AudioManager.connectAudioPatch(). + * Several specialized versions of AudioPortConfig exist to handle different categories of + * audio ports and their specific attributes: + * - AudioDevicePortConfig for input (e.g micropohone) and output devices (e.g speaker) + * - AudioMixPortConfig for input or output streams of the audio framework. + * @hide + */ + +public class AudioPortConfig { + final AudioPort mPort; + private final int mSamplingRate; + private final int mChannelMask; + private final int mFormat; + private final AudioGainConfig mGain; + + // mConfigMask indicates which fields in this configuration should be + // taken into account. Used with AudioSystem.setAudioPortConfig() + // framework use only. + static final int SAMPLE_RATE = 0x1; + static final int CHANNEL_MASK = 0x2; + static final int FORMAT = 0x4; + static final int GAIN = 0x8; + int mConfigMask; + + AudioPortConfig(AudioPort port, int samplingRate, int channelMask, int format, + AudioGainConfig gain) { + mPort = port; + mSamplingRate = samplingRate; + mChannelMask = channelMask; + mFormat = format; + mGain = gain; + mConfigMask = 0; + } + + /** + * Returns the audio port this AudioPortConfig is issued from. + */ + public AudioPort port() { + return mPort; + } + + /** + * Sampling rate configured for this AudioPortConfig. + */ + public int samplingRate() { + return mSamplingRate; + } + + /** + * Channel mask configuration (e.g AudioFormat.CHANNEL_CONFIGURATION_STEREO). + */ + public int channelMask() { + return mChannelMask; + } + + /** + * Audio format configuration (e.g AudioFormat.ENCODING_PCM_16BIT). + */ + public int format() { + return mFormat; + } + + /** + * The gain configuration if this port supports gain control, null otherwise + * @see AudioGainConfig. + */ + public AudioGainConfig gain() { + return mGain; + } +} diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index c736fc7..5b620fd 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -112,6 +112,9 @@ public class AudioService extends IAudioService.Stub { private static final boolean USE_SESSIONS = true; private static final boolean DEBUG_SESSIONS = true; + /** Allow volume changes to set ringer mode to silent? */ + private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -1019,7 +1022,8 @@ public class AudioService extends IAudioService.Stub { int newRingerMode; if (index == 0) { newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE - : AudioManager.RINGER_MODE_SILENT; + : VOLUME_SETS_RINGER_MODE_SILENT ? AudioManager.RINGER_MODE_SILENT + : AudioManager.RINGER_MODE_NORMAL; } else { newRingerMode = AudioManager.RINGER_MODE_NORMAL; } @@ -2552,7 +2556,9 @@ public class AudioService extends IAudioService.Stub { } } else { // (oldIndex < step) is equivalent to (old UI index == 0) - if ((oldIndex < step) && mPrevVolDirection != AudioManager.ADJUST_LOWER) { + if ((oldIndex < step) + && VOLUME_SETS_RINGER_MODE_SILENT + && mPrevVolDirection != AudioManager.ADJUST_LOWER) { ringerMode = RINGER_MODE_SILENT; } } @@ -2565,7 +2571,8 @@ public class AudioService extends IAudioService.Stub { break; } if ((direction == AudioManager.ADJUST_LOWER)) { - if (mPrevVolDirection != AudioManager.ADJUST_LOWER) { + if (VOLUME_SETS_RINGER_MODE_SILENT + && mPrevVolDirection != AudioManager.ADJUST_LOWER) { ringerMode = RINGER_MODE_SILENT; } } else if (direction == AudioManager.ADJUST_RAISE) { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index a3caba2..fe51215 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -41,6 +41,7 @@ import android.hardware.camera2.params.RggbChannelVector; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.params.TonemapCurve; import android.hardware.camera2.utils.TypeReference; import static android.hardware.camera2.impl.CameraMetadataNative.*; @@ -49,6 +50,7 @@ import static com.android.mediaframeworktest.unit.ByteArrayHelpers.*; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; import java.util.List; /** @@ -1015,6 +1017,26 @@ public class CameraMetadataTest extends junit.framework.TestCase { assertNull(resultSimpleFaces[i].getMouthPosition()); } + /** + * Read/Write TonemapCurve + */ + float[] red = new float[] {0.0f, 0.0f, 1.0f, 1.0f}; + float[] green = new float[] {0.0f, 1.0f, 1.0f, 0.0f}; + float[] blue = new float[] { + 0.0000f, 0.0000f, 0.0667f, 0.2920f, 0.1333f, 0.4002f, 0.2000f, 0.4812f, + 0.2667f, 0.5484f, 0.3333f, 0.6069f, 0.4000f, 0.6594f, 0.4667f, 0.7072f, + 0.5333f, 0.7515f, 0.6000f, 0.7928f, 0.6667f, 0.8317f, 0.7333f, 0.8685f, + 0.8000f, 0.9035f, 0.8667f, 0.9370f, 0.9333f, 0.9691f, 1.0000f, 1.0000f}; + TonemapCurve tcIn = new TonemapCurve(red, green, blue); + mMetadata.set(CaptureResult.TONEMAP_CURVE, tcIn); + float[] redOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_RED); + float[] greenOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_GREEN); + float[] blueOut = mMetadata.get(CaptureResult.TONEMAP_CURVE_BLUE); + assertTrue("Input and output tonemap curve should match", Arrays.equals(red, redOut)); + assertTrue("Input and output tonemap curve should match", Arrays.equals(green, greenOut)); + assertTrue("Input and output tonemap curve should match", Arrays.equals(blue, blueOut)); + TonemapCurve tcOut = mMetadata.get(CaptureResult.TONEMAP_CURVE); + assertTrue("Input and output tonemap curve should match", tcIn.equals(tcOut)); } /** @@ -1166,6 +1188,48 @@ public class CameraMetadataTest extends junit.framework.TestCase { } } + private <T> void assertKeyValueEquals(T expected, CameraCharacteristics.Key<T> key) { + assertKeyValueEquals(expected, key.getNativeKey()); + } + + private <T> void assertKeyValueEquals(T expected, Key<T> key) { + T actual = mMetadata.get(key); + + assertEquals("Expected value for key " + key + " to match", expected, actual); + } + + @SmallTest + public void testOverrideMaxRegions() { + // All keys are null before doing any writes. + assertKeyValueEquals(null, CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + assertKeyValueEquals(null, CameraCharacteristics.CONTROL_MAX_REGIONS_AWB); + assertKeyValueEquals(null, CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + + mMetadata.set(CameraCharacteristics.CONTROL_MAX_REGIONS, + new int[] { /*AE*/1, /*AWB*/2, /*AF*/3 }); + + // All keys are the expected value after doing a write + assertKeyValueEquals(1, CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + assertKeyValueEquals(2, CameraCharacteristics.CONTROL_MAX_REGIONS_AWB); + assertKeyValueEquals(3, CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + } + + @SmallTest + public void testOverrideMaxNumOutputStreams() { + // All keys are null before doing any writes. + assertKeyValueEquals(null, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW); + assertKeyValueEquals(null, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC); + assertKeyValueEquals(null, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING); + + mMetadata.set(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS, + new int[] { /*AE*/1, /*AWB*/2, /*AF*/3 }); + + // All keys are the expected value after doing a write + assertKeyValueEquals(1, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW); + assertKeyValueEquals(2, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC); + assertKeyValueEquals(3, CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING); + } + @SmallTest public void testCaptureResult() { mMetadata.set(CaptureRequest.CONTROL_AE_MODE, diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml index b2d0219..78b5746 100644 --- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml +++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml @@ -37,6 +37,7 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/kg_status_line_font_size" android:textColor="?android:attr/textColorSecondary" + android:visibility="gone" androidprv:allCaps="@bool/kg_use_all_caps" /> <LinearLayout diff --git a/packages/Keyguard/res/layout/keyguard_pin_view.xml b/packages/Keyguard/res/layout/keyguard_pin_view.xml index a804c8c..a8e330b 100644 --- a/packages/Keyguard/res/layout/keyguard_pin_view.xml +++ b/packages/Keyguard/res/layout/keyguard_pin_view.xml @@ -41,28 +41,16 @@ android:layout_weight="1" android:layoutDirection="ltr" > - <LinearLayout + <RelativeLayout + android:id="@+id/row0" android:layout_width="match_parent" android:layout_height="0dp" - android:orientation="horizontal" android:layout_weight="1" > - <TextView android:id="@+id/pinEntry" - android:editable="true" - android:layout_width="0dip" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center" - android:layout_marginStart="@dimen/keyguard_lockscreen_pin_margin_left" - android:singleLine="true" - android:cursorVisible="false" - android:background="@null" - android:textAppearance="@style/TextAppearance.NumPadKey" - android:imeOptions="flagForceAscii|actionDone" - /> - <ImageButton android:id="@+id/delete_button" + <ImageButton android:id="@+id/delete_button" android:layout_width="wrap_content" android:layout_height="match_parent" + android:layout_alignParentEnd="true" android:gravity="center_vertical" android:src="@drawable/ic_input_delete" android:clickable="true" @@ -73,13 +61,30 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/keyboardview_keycode_delete" /> - </LinearLayout> - <View - android:layout_width="wrap_content" - android:layout_height="1dp" - android:background="#55FFFFFF" - /> + <TextView android:id="@+id/pinEntry" + android:editable="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_toStartOf="@+id/delete_button" + android:layout_alignParentStart="true" + android:gravity="center" + android:layout_marginStart="@dimen/keyguard_lockscreen_pin_margin_left" + android:singleLine="true" + android:cursorVisible="false" + android:background="@null" + android:textAppearance="@style/TextAppearance.NumPadKey" + android:imeOptions="flagForceAscii|actionDone" + /> + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_alignParentBottom="true" + android:background="#55FFFFFF" + /> + </RelativeLayout> <LinearLayout + android:id="@+id/row1" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -114,6 +119,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -148,6 +154,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row3" android:layout_width="match_parent" android:layout_height="0dp" android:orientation="horizontal" @@ -182,6 +189,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row4" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml index 3830df7..01d9ab3 100644 --- a/packages/Keyguard/res/values/dimens.xml +++ b/packages/Keyguard/res/values/dimens.xml @@ -161,4 +161,6 @@ <dimen name="widget_big_font_size">68dp</dimen> <dimen name="big_font_size">120dp</dimen> + <!-- The y translation to apply at the start in appear animations. --> + <dimen name="appear_y_translation_start">24dp</dimen> </resources> diff --git a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java new file mode 100644 index 0000000..ea896d5 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.keyguard; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +/** + * A class to make nice appear transitions for views in a tabular layout. + */ +public class AppearAnimationUtils { + + public static final long APPEAR_DURATION = 220; + + private final Interpolator mLinearOutSlowIn; + private final float mStartTranslation; + + public AppearAnimationUtils(Context ctx) { + mLinearOutSlowIn = AnimationUtils.loadInterpolator( + ctx, android.R.interpolator.linear_out_slow_in); + mStartTranslation = + ctx.getResources().getDimensionPixelOffset(R.dimen.appear_y_translation_start); + } + + public void startAppearAnimation(View[][] views, final Runnable finishListener) { + long maxDelay = 0; + ViewPropertyAnimator maxDelayAnimator = null; + for (int row = 0; row < views.length; row++) { + View[] columns = views[row]; + for (int col = 0; col < columns.length; col++) { + long delay = calculateDelay(row, col); + ViewPropertyAnimator animator = startAppearAnimation(columns[col], delay); + if (animator != null && delay > maxDelay) { + maxDelay = delay; + maxDelayAnimator = animator; + } + } + } + if (maxDelayAnimator != null) { + maxDelayAnimator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishListener.run(); + } + }); + } else { + finishListener.run(); + } + } + + private ViewPropertyAnimator startAppearAnimation(View view, long delay) { + if (view == null) return null; + view.setAlpha(0f); + view.setTranslationY(mStartTranslation); + view.animate() + .alpha(1f) + .translationY(0) + .setInterpolator(mLinearOutSlowIn) + .setDuration(APPEAR_DURATION) + .setStartDelay(delay) + .setListener(null); + if (view.hasOverlappingRendering()) { + view.animate().withLayer(); + } + return view.animate(); + } + + private long calculateDelay(int row, int col) { + return (long) (row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20); + } + + public TimeInterpolator getInterpolator() { + return mLinearOutSlowIn; + } + + public float getStartTranslation() { + return mStartTranslation; + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java b/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java index 6d392fc..a592db9 100644 --- a/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java +++ b/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java @@ -17,13 +17,12 @@ package com.android.keyguard; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; -import com.android.keyguard.R; - public class EmergencyCarrierArea extends LinearLayout { private CarrierText mCarrierText; @@ -48,6 +47,7 @@ public class EmergencyCarrierArea extends LinearLayout { mEmergencyButton.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { + if (mCarrierText.getVisibility() != View.VISIBLE) return false; switch(event.getAction()) { case MotionEvent.ACTION_DOWN: mCarrierText.animate().alpha(0); @@ -59,4 +59,8 @@ public class EmergencyCarrierArea extends LinearLayout { return false; }}); } + + public void setCarrierTextVisible(boolean visible) { + mCarrierText.setVisibility(visible ? View.VISIBLE : View.GONE); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java index f69fa5f..69abc7a 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java @@ -328,5 +328,10 @@ public class KeyguardAccountView extends LinearLayout implements KeyguardSecurit @Override public void hideBouncer(int duration) { } + + @Override + public void startAppearAnimation() { + // TODO. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java index 701d15f..c9fe93c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java @@ -349,4 +349,8 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); } + @Override + public void startAppearAnimation() { + // TODO. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java index ede23ef..d589283 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java @@ -48,35 +48,19 @@ class KeyguardMessageArea extends TextView { */ private static final long ANNOUNCEMENT_DELAY = 250; - static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; - static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; - static final int SECURITY_MESSAGE_DURATION = 5000; protected static final int FADE_DURATION = 750; private static final String TAG = "KeyguardMessageArea"; - // are we showing battery information? - boolean mShowingBatteryInfo = false; - // is the bouncer up? boolean mShowingBouncer = false; - // last known plugged in state - boolean mCharging = false; - - // last known battery level - int mBatteryLevel = 100; - KeyguardUpdateMonitor mUpdateMonitor; // Timeout before we reset the message to show charging/owner info long mTimeout = SECURITY_MESSAGE_DURATION; - // Shadowed text values - protected boolean mBatteryCharged; - protected boolean mBatteryIsLow; - private Handler mHandler; CharSequence mMessage; @@ -146,16 +130,6 @@ class KeyguardMessageArea extends TextView { } private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { - mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); - mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING - || status.status == BatteryManager.BATTERY_STATUS_FULL; - mBatteryLevel = status.level; - mBatteryCharged = status.isCharged(); - mBatteryIsLow = status.isBatteryLow(); - update(); - } public void onScreenTurnedOff(int why) { setSelected(false); }; @@ -212,7 +186,7 @@ class KeyguardMessageArea extends TextView { */ void update() { MutableInt icon = new MutableInt(0); - CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage()); + CharSequence status = concat(getOwnerInfo(), getCurrentMessage()); setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); setText(status); } @@ -248,25 +222,6 @@ class KeyguardMessageArea extends TextView { return info; } - private CharSequence getChargeInfo(MutableInt icon) { - CharSequence string = null; - if (mShowingBatteryInfo && !mShowingMessage) { - // Battery status - if (mCharging) { - // Charging, charged or waiting to charge. - string = getContext().getString(mBatteryCharged - ? R.string.keyguard_charged - : R.string.keyguard_plugged_in, mBatteryLevel); - icon.value = CHARGING_ICON; - } else if (mBatteryIsLow) { - // Battery is low - string = getContext().getString(R.string.keyguard_low_battery); - icon.value = BATTERY_LOW_ICON; - } - } - return string; - } - private void hideMessage(int duration, boolean thenUpdate) { if (duration > 0) { Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java index ca2d615..1f3c176 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java @@ -22,6 +22,7 @@ import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView.OnEditorActionListener; /** @@ -30,12 +31,21 @@ import android.widget.TextView.OnEditorActionListener; public class KeyguardPINView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + private final AppearAnimationUtils mAppearAnimationUtils; + private ViewGroup mKeyguardBouncerFrame; + private ViewGroup mRow0; + private ViewGroup mRow1; + private ViewGroup mRow2; + private ViewGroup mRow3; + private View mDivider; + public KeyguardPINView(Context context) { this(context, null); } public KeyguardPINView(Context context, AttributeSet attrs) { super(context, attrs); + mAppearAnimationUtils = new AppearAnimationUtils(context); } protected void resetState() { @@ -56,6 +66,12 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView protected void onFinishInflate() { super.onFinishInflate(); + mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame); + mRow0 = (ViewGroup) findViewById(R.id.row0); + mRow1 = (ViewGroup) findViewById(R.id.row1); + mRow2 = (ViewGroup) findViewById(R.id.row2); + mRow3 = (ViewGroup) findViewById(R.id.row3); + mDivider = findViewById(R.id.divider); final View ok = findViewById(R.id.key_enter); if (ok != null) { ok.setOnClickListener(new View.OnClickListener() { @@ -114,4 +130,48 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView public int getWrongPasswordStringId() { return R.string.kg_wrong_pin; } + + @Override + public void startAppearAnimation() { + enableClipping(false); + setTranslationY(mAppearAnimationUtils.getStartTranslation()); + animate() + .setDuration(500) + .setInterpolator(mAppearAnimationUtils.getInterpolator()) + .translationY(0); + mAppearAnimationUtils.startAppearAnimation(new View[][] { + new View[] { + mRow0, null, null + }, + new View[] { + findViewById(R.id.key1), findViewById(R.id.key2), findViewById(R.id.key3) + }, + new View[] { + findViewById(R.id.key4), findViewById(R.id.key5), findViewById(R.id.key6) + }, + new View[] { + findViewById(R.id.key7), findViewById(R.id.key8), findViewById(R.id.key9) + }, + new View[] { + null, findViewById(R.id.key0), findViewById(R.id.key_enter) + }, + new View[] { + null, mEcaView, null + }}, + new Runnable() { + @Override + public void run() { + enableClipping(true); + } + }); + } + + private void enableClipping(boolean enable) { + mKeyguardBouncerFrame.setClipToPadding(enable); + mKeyguardBouncerFrame.setClipChildren(enable); + mRow1.setClipToPadding(enable); + mRow2.setClipToPadding(enable); + mRow3.setClipToPadding(enable); + setClipChildren(enable); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java index e733afc..0c385da 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java @@ -198,4 +198,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView public int getWrongPasswordStringId() { return R.string.kg_wrong_password; } + + @Override + public void startAppearAnimation() { + // TODO: Fancy animation. + setAlpha(0); + animate().alpha(1).withLayer().setDuration(200); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index 98122fc..5853ff9 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -400,4 +400,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit KeyguardSecurityViewHelper. hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); } + + @Override + public void startAppearAnimation() { + // TODO: Fancy animation. + setAlpha(0); + animate().alpha(1).withLayer().setDuration(200); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java index 94edc07..382cbec 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -81,6 +81,10 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe getSecurityView(mCurrentSecuritySelection).onPause(); } + public void startAppearAnimation() { + getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); + } + void updateSecurityViews(boolean isBouncing) { int children = mSecurityViewFlipper.getChildCount(); for (int i = 0; i < children; i++) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java index dfeacf3..86bd877 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java @@ -84,4 +84,9 @@ public interface KeyguardSecurityView { * @param duration millisends for the transisiton animation. */ void hideBouncer(int duration); + + /** + * Starts the animation which should run when the security view appears. + */ + void startAppearAnimation(); } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index 07239d1..178ca5e 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -159,6 +159,14 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard } @Override + public void startAppearAnimation() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.startAppearAnimation(); + } + } + + @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java index 03e7b07..98baa04 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java @@ -244,4 +244,9 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri KeyguardSecurityViewHelper. hideBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration); } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java index 4791956..09c4e7c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java @@ -135,6 +135,9 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPasswordEntry.requestFocus(); mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default + if (mEcaView instanceof EmergencyCarrierArea) { + ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); + } } @Override @@ -270,5 +273,10 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mCheckSimPinThread.start(); } } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java index b9c7f51..6215d34 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java @@ -186,6 +186,9 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView mPasswordEntry.requestFocus(); mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default + if (mEcaView instanceof EmergencyCarrierArea) { + ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); + } } @Override @@ -339,6 +342,11 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView protected void verifyPasswordAndUnlock() { mStateMachine.next(); } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java index 48b7be9..3e444fa 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java @@ -241,6 +241,13 @@ public abstract class KeyguardViewBase extends FrameLayout implements SecurityCa } /** + * Starts the animation when the Keyguard gets shown. + */ + public void startAppearAnimation() { + mSecurityContainer.startAppearAnimation(); + } + + /** * Verify that the user can get past the keyguard securely. This is called, * for example, when the phone disables the keyguard but then wants to launch * something else that requires secure access. diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png Binary files differdeleted file mode 100644 index 3c0dc4e..0000000 --- a/packages/SystemUI/res/drawable-hdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png Binary files differdeleted file mode 100644 index 731f19b..0000000 --- a/packages/SystemUI/res/drawable-hdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png Binary files differdeleted file mode 100644 index 8010ce7..0000000 --- a/packages/SystemUI/res/drawable-mdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png Binary files differdeleted file mode 100644 index a4d82f0..0000000 --- a/packages/SystemUI/res/drawable-mdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png Binary files differdeleted file mode 100644 index 6d46fdd..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png Binary files differdeleted file mode 100644 index b62c74e..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png Binary files differdeleted file mode 100644 index 7742207..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png Binary files differdeleted file mode 100644 index f364577..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml index 3a20c58..787eec5 100644 --- a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml +++ b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M6.6,3.6L5.2,2.2C2.8,4.0 1.2,6.8 1.0,10.0l2.0,0.0C3.2,7.3 4.5,5.0 6.6,3.6zM20.0,10.0l2.0,0.0c-0.2,-3.2 -1.7,-6.0 -4.1,-7.8l-1.4,1.4C18.5,5.0 19.8,7.3 20.0,10.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0l-2.0,-2.0L18.0,10.5zM11.5,22.0c0.1,0.0 0.3,0.0 0.4,0.0c0.7,-0.1 1.2,-0.6 1.4,-1.2c0.1,-0.2 0.2,-0.5 0.2,-0.8l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0z" /> + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_ringer_audible.xml new file mode 100644 index 0000000..2969948 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_audible.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_silent.xml b/packages/SystemUI/res/drawable/ic_ringer_silent.xml new file mode 100644 index 0000000..b5837f6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_silent.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml new file mode 100644 index 0000000..d8ded58 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml new file mode 100644 index 0000000..ea5ab70 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml @@ -0,0 +1,30 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#00000000" + android:stroke="#CCCCCC" + android:strokeWidth="1.0" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml new file mode 100644 index 0000000..44024f3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> +</vector> diff --git a/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml b/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml index 84f0950..a291495 100644 --- a/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml +++ b/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml @@ -158,17 +158,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="80dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -316,17 +305,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/search_light" - android:layout_height="80dp" - android:layout_width="match_parent" - android:layout_gravity="center_vertical" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml index 34b674b..f8b7bae 100644 --- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml +++ b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml @@ -156,17 +156,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="128dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -313,17 +302,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="162dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 936f73b..9bf42b2 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -22,7 +22,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" > - <com.android.systemui.statusbar.phone.SwipeAffordanceView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/camera_button" android:layout_height="64dp" android:layout_width="64dp" @@ -30,10 +30,9 @@ android:tint="#ffffffff" android:src="@drawable/ic_camera_alt_24dp" android:scaleType="center" - android:contentDescription="@string/accessibility_camera_button" - systemui:swipeDirection="start"/> + android:contentDescription="@string/accessibility_camera_button" /> - <com.android.systemui.statusbar.phone.SwipeAffordanceView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/phone_button" android:layout_height="64dp" android:layout_width="64dp" @@ -41,8 +40,7 @@ android:tint="#ffffffff" android:src="@drawable/ic_phone_24dp" android:scaleType="center" - android:contentDescription="@string/accessibility_phone_button" - systemui:swipeDirection="end"/> + android:contentDescription="@string/accessibility_phone_button" /> <com.android.systemui.statusbar.phone.KeyguardIndicationTextView android:id="@+id/keyguard_indication_text" @@ -54,15 +52,13 @@ android:textColor="#ffffff" android:textAppearance="?android:attr/textAppearanceSmall"/> - <ImageView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/lock_icon" android:layout_width="64dp" android:layout_height="64dp" android:layout_gravity="bottom|center_horizontal" android:src="@drawable/ic_lock_24dp" android:scaleType="center" - android:alpha="0.7" - android:layerType="hardware" - android:tint="#ffffffff"/> + android:tint="#ffffffff" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index fa6f7f5..7616cb1 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -160,22 +160,6 @@ /> </LinearLayout> - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="80dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - </FrameLayout> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -325,19 +309,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/search_light" - android:layout_height="80dp" - android:layout_width="match_parent" - android:layout_gravity="center_vertical" - android:src="@drawable/search_light_land" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - - <!-- No camera button in landscape mode --> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index 85b294d..e73b431 100644 --- a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -14,64 +14,44 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.systemui.qs.tiles.ZenModeDetail xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/system_secondary_color" > + android:background="@color/system_primary_color" > <ImageView android:id="@android:id/button1" - android:src="@drawable/ic_qs_close" android:layout_width="64dp" android:layout_height="64dp" android:layout_alignParentStart="true" - android:padding="@dimen/qs_panel_padding" /> - - <Switch - android:id="@android:id/checkbox" - android:layout_width="wrap_content" - android:layout_height="64dp" - android:layout_alignParentEnd="true" - android:gravity="center" - android:padding="@dimen/qs_panel_padding" /> + android:contentDescription="@string/accessibility_quick_settings_close" + android:padding="@dimen/qs_panel_padding" + android:src="@drawable/ic_qs_close" /> <TextView android:id="@android:id/title" android:layout_width="match_parent" android:layout_height="64dp" + android:layout_alignParentTop="true" android:layout_toEndOf="@android:id/button1" android:layout_toStartOf="@android:id/checkbox" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" android:gravity="center_vertical" - android:paddingStart="@dimen/qs_panel_padding" - android:text="@string/zen_mode_title" /> + android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> - <View + <ImageView android:id="@android:id/custom" android:layout_width="match_parent" - android:layout_height="2dp" + android:layout_height="wrap_content" android:layout_below="@android:id/title" - android:background="#888" /> + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:scaleType="fitXY" + android:src="?android:attr/dividerHorizontal" /> - <ListView + <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_above="@android:id/button2" - android:layout_below="@android:id/custom" - android:divider="#00000000" - android:dividerHeight="0px" /> - - <TextView - android:id="@android:id/button2" - style="@style/QSBorderless" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentEnd="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:padding="@dimen/qs_panel_padding" - android:text="@string/quick_settings_more_settings" - android:textAllCaps="true" /> + android:layout_below="@android:id/custom" /> -</com.android.systemui.qs.tiles.ZenModeDetail>
\ No newline at end of file +</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index dfc3b22..7f34041 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -98,7 +98,8 @@ android:gravity="center_vertical" android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="#ffffff" /> + android:textColor="#ffffff" + android:singleLine="true" /> <include layout="@layout/quick_settings_brightness_dialog" diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml index 70c5042..816af57 100644 --- a/packages/SystemUI/res/layout/user_switcher_host.xml +++ b/packages/SystemUI/res/layout/user_switcher_host.xml @@ -27,7 +27,7 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@*android:dimen/volume_panel_top" + android:layout_marginTop="@dimen/volume_panel_top" android:background="@*android:drawable/dialog_full_holo_dark"> <ListView android:id="@android:id/list" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/volume_panel.xml b/packages/SystemUI/res/layout/volume_panel.xml new file mode 100644 index 0000000..046862f --- /dev/null +++ b/packages/SystemUI/res/layout/volume_panel.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2007 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/visible_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" > + + <FrameLayout + android:id="@+id/slider_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="64dip" + android:layout_toLeftOf="@+id/expand_button_divider" /> + + <ImageView + android:id="@+id/expand_button_divider" + android:layout_width="wrap_content" + android:layout_height="32dip" + android:layout_gravity="top" + android:layout_marginBottom="16dip" + android:layout_marginTop="16dip" + android:layout_toLeftOf="@+id/expand_button" + android:scaleType="fitXY" + android:src="?android:attr/dividerVertical" /> + + <ImageView + android:id="@+id/expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_gravity="top" + style="@style/BorderlessButton.Tiny" + android:padding="16dip" /> + + <ImageView + android:id="@+id/zen_panel_divider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/slider_panel" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:scaleType="fitXY" + android:src="?android:attr/dividerHorizontal" /> + + <ViewStub + android:id="@+id/zen_panel_stub" + android:layout_below="@+id/zen_panel_divider" + android:inflatedId="@+id/zen_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout="@layout/zen_mode_panel" /> + +</RelativeLayout> diff --git a/core/res/res/layout/volume_adjust_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml index 57cecf4..98cb8f4 100644 --- a/core/res/res/layout/volume_adjust_item.xml +++ b/packages/SystemUI/res/layout/volume_panel_item.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="16dip" - android:background="?attr/selectableItemBackground" + android:background="?android:attr/selectableItemBackground" android:contentDescription="@null" /> <SeekBar diff --git a/packages/SystemUI/res/layout/qs_zen_mode_detail_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml index fd27aaf..8b34400 100644 --- a/packages/SystemUI/res/layout/qs_zen_mode_detail_condition.xml +++ b/packages/SystemUI/res/layout/zen_mode_condition.xml @@ -16,44 +16,47 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="wrap_content" > <RadioButton android:id="@android:id/checkbox" android:layout_width="32dp" - android:layout_height="64dp" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentStart="true" - android:layout_marginStart="@dimen/qs_panel_padding" android:gravity="center" /> <TextView android:id="@android:id/title" android:layout_width="match_parent" - android:layout_height="64dp" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_toEndOf="@android:id/checkbox" android:layout_toStartOf="@android:id/button1" android:ellipsize="end" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" android:gravity="center_vertical" android:maxLines="1" - android:text="@string/accessibility_back" /> + android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" /> <ImageView android:id="@android:id/button1" - android:src="@drawable/ic_qs_minus" - android:layout_width="64dp" - android:layout_height="64dp" + style="@style/BorderlessButton" + android:layout_width="@dimen/zen_mode_condition_height" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentEnd="true" - android:layout_marginEnd="48dp" - android:padding="@dimen/qs_panel_padding" - android:paddingRight="0px" /> + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/zen_mode_condition_height" + android:contentDescription="@string/accessibility_quick_settings_less_time" + android:padding="@dimen/zen_mode_condition_detail_button_padding" + android:src="@drawable/ic_qs_minus" /> <ImageView android:id="@android:id/button2" - android:src="@drawable/ic_qs_plus" - android:layout_width="64dp" - android:layout_height="64dp" + style="@style/BorderlessButton" + android:layout_width="@dimen/zen_mode_condition_height" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentEnd="true" - android:padding="@dimen/qs_panel_padding" /> + android:layout_centerVertical="true" + android:contentDescription="@string/accessibility_quick_settings_more_time" + android:padding="@dimen/zen_mode_condition_detail_button_padding" + android:src="@drawable/ic_qs_plus" /> -</RelativeLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml new file mode 100644 index 0000000..0936cc2 --- /dev/null +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- extends LinearLayout --> +<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/zen_mode_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/system_primary_color" + android:orientation="vertical" + android:paddingTop="@dimen/qs_panel_padding" + android:paddingLeft="@dimen/qs_panel_padding" + android:paddingRight="@dimen/qs_panel_padding" > + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_marginBottom="8dp" + android:text="@string/zen_mode_title" + android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> + + <LinearLayout + android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + + <TextView + android:id="@android:id/button2" + style="@style/BorderlessButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_gravity="end" + android:text="@string/quick_settings_more_settings" + android:textAppearance="@style/TextAppearance.QS.DetailButton" /> + +</com.android.systemui.volume.ZenModePanel>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3549689..c453618 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -43,12 +43,6 @@ <declare-styleable name="BatteryMeterView"> <attr name="frameColor" format="color" /> </declare-styleable> - <declare-styleable name="SwipeAffordanceView"> - <attr name="swipeDirection" format="enum"> - <enum name="start" value="0" /> - <enum name="end" value="1" /> - </attr> - </declare-styleable> <declare-styleable name="Clock"> <attr name="amPmStyle" format="enum"> <enum name="normal" value="0" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 0bd4f18..6405ae6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -135,5 +135,8 @@ <!-- Defines the implementation of the velocity tracker to be used for the panel expansion. Can be 'platform' or 'noisy' (i.e. for noisy touch screens). --> <string name="velocity_tracker_impl" translatable="false">platform</string> + + <!-- Wait on the touch feedback this long before performing an action. --> + <integer name="feedback_start_delay">300</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bf0cb68..610b376 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -199,6 +199,9 @@ <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">8dp</dimen> + <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> + <dimen name="zen_mode_condition_height">48dp</dimen> + <!-- used by DessertCase --> <dimen name="dessert_case_cell_size">192dp</dimen> @@ -291,4 +294,12 @@ <dimen name="keyguard_clock_notifications_margin_min">22dp</dimen> <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> + <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> + <dimen name="keyguard_min_swipe_amount">75dp</dimen> + + <!-- Volume panel dialog y offset --> + <dimen name="volume_panel_top">16dp</dimen> + + <!-- Volume panel dialog width --> + <dimen name="volume_panel_width">300dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f5bc353..ef3956e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -388,6 +388,12 @@ <string name="accessibility_quick_settings_location">Location <xliff:g id="state" example="Off">%s</xliff:g>.</string> <!-- Content description of the alarm tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_alarm">Alarm set for <xliff:g id="time" example="Wed 3:30 PM">%s</xliff:g>.</string> + <!-- Content description of quick settings detail panel close button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_close">Close panel</string> + <!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_more_time">More time</string> + <!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_less_time">Less time</string> <!-- Title of dialog shown when 2G-3G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] --> <string name="data_usage_disabled_dialog_3g_title">2G-3G data disabled</string> @@ -512,6 +518,8 @@ <string name="quick_settings_tethering_label">Tethering</string> <!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] --> <string name="quick_settings_hotspot_label">Hotspot</string> + <!-- QuickSettings: Notifications [CHAR LIMIT=NONE] --> + <string name="quick_settings_notifications_label">Notifications</string> <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] --> <string name="recents_empty_message">RECENTS</string> @@ -563,4 +571,19 @@ <string name="keyguard_unlock">Swipe up to unlock</string> <string name="bugreport_tile_extended" translatable="false">%s\n%s (%s)</string> + + <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] --> + <string name="zen_mode_forever">Until you turn this off</string> + + <!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] --> + <plurals name="zen_mode_duration_minutes"> + <item quantity="one">For one minute</item> + <item quantity="other">For %d minutes</item> + </plurals> + + <!-- Zen mode condition: time duration in hours. [CHAR LIMIT=NONE] --> + <plurals name="zen_mode_duration_hours"> + <item quantity="one">For one hour</item> + <item quantity="other">For %d hours</item> + </plurals> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 19888a8..6a12232 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -133,6 +133,32 @@ <item name="android:fadingEdge">horizontal</item> </style> + <style name="TextAppearance.QS"> + <item name="android:textStyle">normal</item> + <item name="android:textColor">#ffffff</item> + <item name="android:fontFamily">sans-serif</item> + </style> + + <style name="TextAppearance.QS.DetailHeader"> + <item name="android:textSize">20sp</item> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + + <style name="TextAppearance.QS.DetailItemPrimary"> + <item name="android:textSize">16sp</item> + </style> + + <style name="TextAppearance.QS.DetailItemSecondary"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">#7fcac3</item> + </style> + + <style name="TextAppearance.QS.DetailButton"> + <item name="android:textSize">14sp</item> + <item name="android:textAllCaps">true</item> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + <style name="BaseBrightnessDialogContainer"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> @@ -192,9 +218,9 @@ <item name="android:colorControlActivated">@color/system_accent_color</item> </style> - <style name="QSBorderless" parent="@android:style/Widget.Quantum.Button.Borderless" /> + <style name="BorderlessButton" parent="@android:style/Widget.Quantum.Button.Borderless" /> - <style name="QSBorderless.Tiny"> + <style name="BorderlessButton.Tiny"> <item name="android:minHeight">12dip</item> <item name="android:minWidth">12dip</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index d7ce255..630ba13 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -42,12 +42,12 @@ public class SystemUIApplication extends Application { private final Class<?>[] SERVICES = new Class[] { com.android.systemui.keyguard.KeyguardViewMediator.class, com.android.systemui.recent.Recents.class, + com.android.systemui.volume.VolumeUI.class, com.android.systemui.statusbar.SystemBars.class, com.android.systemui.usb.StorageNotification.class, com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, com.android.systemui.settings.SettingsUI.class, - com.android.systemui.volume.VolumeUI.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index bdac7a0..626fc0d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -191,15 +191,23 @@ public class QSPanel extends ViewGroup { final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight; record.tileView.measure(exactly(cw), exactly(ch)); } - final int actualHeight = rows == 0 ? 0 : getRowTop(rows); - mDetail.measure(exactly(width), exactly(actualHeight)); - setMeasuredDimension(width, actualHeight); + int h = rows == 0 ? 0 : getRowTop(rows); + mDetail.measure(exactly(width), unspecified()); + if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) { + final int dmh = mDetail.getMeasuredHeight(); + if (dmh > 0) h = dmh; + } + setMeasuredDimension(width, h); } private static int exactly(int size) { return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); } + private static int unspecified() { + return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int w = getWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 835a5c4..c76ee8c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -26,6 +26,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.R; import com.android.systemui.qs.QSTile.State; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.TetheringController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; import java.util.List; import java.util.Objects; @@ -49,12 +51,12 @@ import java.util.Objects; public abstract class QSTile<TState extends State> implements Listenable { protected final String TAG = "QSTile." + getClass().getSimpleName(); protected static final boolean DEBUG = false; - public static final int FEEDBACK_START_DELAY = 400; protected final Host mHost; protected final Context mContext; protected final H mHandler; protected final Handler mUiHandler = new Handler(Looper.getMainLooper()); + private final int mFeedbackStartDelay; private Callback mCallback; protected final TState mState = newTileState(); @@ -68,6 +70,7 @@ public abstract class QSTile<TState extends State> implements Listenable { mHost = host; mContext = host.getContext(); mHandler = new H(host.getLooper()); + mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay); } public boolean supportsDualTargets() { @@ -116,6 +119,10 @@ public abstract class QSTile<TState extends State> implements Listenable { mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget(); } + protected void postAfterFeedback(Runnable runnable) { + mHandler.postDelayed(runnable, mFeedbackStartDelay); + } + // call only on tile worker looper private void handleSetCallback(Callback callback) { @@ -213,6 +220,7 @@ public abstract class QSTile<TState extends State> implements Listenable { ZenModeController getZenModeController(); TetheringController getTetheringController(); CastController getCastController(); + VolumeComponent getVolumeComponent(); } public static class State { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 5eecc20..2edd8d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -84,7 +84,8 @@ public class QSTileView extends ViewGroup { removeView(mLabel); } final Resources res = mContext.getResources(); - mLabel = new TextView(mDual ? new ContextThemeWrapper(mContext, R.style.QSBorderless_Tiny) + mLabel = new TextView(mDual + ? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny) : mContext); mLabel.setId(android.R.id.title); mLabel.setTextColor(res.getColor(R.color.qs_tile_text)); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java index fa41837..07ea825 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java @@ -58,13 +58,13 @@ public class BugreportTile extends QSTile<QSTile.State> { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { @Override public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 907c77e..6793051 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -65,12 +65,12 @@ public class CastTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java new file mode 100644 index 0000000..20bbf8b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; +import com.android.systemui.volume.VolumePanel; +import com.android.systemui.volume.ZenModePanel; + +/** Quick settings tile: Notifications **/ +public class NotificationsTile extends QSTile<NotificationsTile.NotificationsState> { + private final ZenModeController mZenController; + private final AudioManager mAudioManager; + + public NotificationsTile(Host host) { + super(host); + mZenController = host.getZenModeController(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public View createDetailView(Context context, ViewGroup root) { + final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme); + final View v = LayoutInflater.from(themedContext).inflate(R.layout.qs_detail, root, false); + final TextView title = (TextView) v.findViewById(android.R.id.title); + title.setText(R.string.quick_settings_notifications_label); + final View close = v.findViewById(android.R.id.button1); + close.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDetail(false); + } + }); + final ViewGroup content = (ViewGroup) v.findViewById(android.R.id.content); + final VolumeComponent volumeComponent = mHost.getVolumeComponent(); + final VolumePanel vp = new VolumePanel(mContext, content, mZenController); + v.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewDetachedFromWindow(View v) { + volumeComponent.setVolumePanel(null); + } + + @Override + public void onViewAttachedToWindow(View v) { + volumeComponent.setVolumePanel(vp); + } + }); + vp.setZenModePanelCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + mHost.startSettingsActivity(ZenModePanel.ZEN_SETTINGS); + } + + @Override + public void onInteraction() { + // noop + } + }); + vp.postVolumeChanged(AudioManager.STREAM_RING, AudioManager.FLAG_SHOW_UI); + return v; + } + + @Override + protected NotificationsState newTileState() { + return new NotificationsState(); + } + + @Override + public void setListening(boolean listening) { + if (listening) { + mZenController.addCallback(mCallback); + final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + mContext.registerReceiver(mReceiver, filter); + } else { + mZenController.removeCallback(mCallback); + mContext.unregisterReceiver(mReceiver); + } + } + + @Override + protected void handleClick() { + showDetail(true); + } + + @Override + protected void handleUpdateState(NotificationsState state, Object arg) { + state.visible = true; + state.zen = arg instanceof Boolean ? (Boolean) arg : mZenController.isZen(); + state.ringerMode = mAudioManager.getRingerMode(); + if (state.zen) { + state.iconId = R.drawable.ic_qs_zen_on; + } else if (state.ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + state.iconId = R.drawable.ic_qs_ringer_vibrate; + } else if (state.ringerMode == AudioManager.RINGER_MODE_SILENT) { + state.iconId = R.drawable.ic_qs_ringer_silent; + } else { + state.iconId = R.drawable.ic_qs_ringer_audible; + } + state.label = mContext.getString(R.string.quick_settings_notifications_label); + } + + private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { + @Override + public void onZenChanged(boolean zen) { + if (DEBUG) Log.d(TAG, "onZenChanged " + zen); + refreshState(zen); + } + }; + + public static final class NotificationsState extends QSTile.State { + public boolean zen; + public int ringerMode; + + @Override + public boolean copyTo(State other) { + final NotificationsState o = (NotificationsState) other; + final boolean changed = o.zen != zen || o.ringerMode != ringerMode; + o.zen = zen; + o.ringerMode = ringerMode; + return super.copyTo(other) || changed; + } + + @Override + protected StringBuilder toStringBuilder() { + final StringBuilder rt = super.toStringBuilder(); + rt.insert(rt.length() - 1, ",zen=" + zen + ",ringerMode=" + ringerMode); + return rt; + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { + refreshState(); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java deleted file mode 100644 index c5e9b52..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; - -/** Quick settings tile: Ringer mode **/ -public class RingerModeTile extends QSTile<RingerModeTile.IntState> { - - private final AudioManager mAudioManager; - - public RingerModeTile(Host host) { - super(host); - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - } - - @Override - protected IntState newTileState() { - return new IntState(); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(mReceiver, filter); - } else { - mContext.unregisterReceiver(mReceiver); - } - } - - @Override - protected void handleClick() { - final int oldValue = (Integer) mState.value; - final int newValue = - oldValue == AudioManager.RINGER_MODE_NORMAL ? AudioManager.RINGER_MODE_VIBRATE - : oldValue == AudioManager.RINGER_MODE_VIBRATE ? AudioManager.RINGER_MODE_SILENT - : AudioManager.RINGER_MODE_NORMAL; - - mAudioManager.setRingerMode(newValue); - } - - @Override - protected void handleUpdateState(IntState state, Object arg) { - final int ringerMode = mAudioManager.getRingerMode(); - state.visible = true; - state.value = ringerMode; - if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { - state.iconId = R.drawable.ic_qs_ringer_vibrate; - state.label = "Vibrate"; - } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - state.iconId = R.drawable.ic_qs_ringer_silent; - state.label = "Silent"; - } else { - state.iconId = R.drawable.ic_qs_ringer_audible; - state.label = "Audible"; - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { - refreshState(); - } - } - }; - - public static class IntState extends QSTile.State { - public int value; - - @Override - public boolean copyTo(State other) { - final IntState o = (IntState) other; - final boolean changed = o.value != value; - o.value = value; - return super.copyTo(other) || changed; - } - - @Override - protected StringBuilder toStringBuilder() { - final StringBuilder rt = super.toStringBuilder(); - rt.insert(rt.length() - 1, ",value=" + value); - return rt; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index ef7fb89..a1e70b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -83,7 +83,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.enabledDesc != null); boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null); - state.enabled = wifiConnected; + state.enabled = cb.enabled; state.connected = wifiConnected; state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java deleted file mode 100644 index f30f791..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.service.notification.Condition; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.RelativeLayout; -import android.widget.Switch; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.util.HashSet; - -/** Quick settings control panel: Zen mode **/ -public class ZenModeDetail extends RelativeLayout { - private static final String TAG = "ZenModeDetail"; - private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); - private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; - - private final H mHandler = new H(); - - private int mMinutesIndex = 3; - private Context mContext; - private ZenModeTile mTile; - private QSTile.Host mHost; - private ZenModeController mController; - - private Switch mSwitch; - private ConditionAdapter mAdapter; - - public ZenModeDetail(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void init(ZenModeTile tile) { - mTile = tile; - mHost = mTile.getHost(); - mContext = getContext(); - mController = mHost.getZenModeController(); - - final ImageView close = (ImageView) findViewById(android.R.id.button1); - close.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mTile.showDetail(false); - } - }); - mSwitch = (Switch) findViewById(android.R.id.checkbox); - mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mController.setZen(isChecked); - } - }); - mSwitch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final boolean isChecked = mSwitch.isChecked(); - mController.setZen(isChecked); - if (!isChecked) { - mTile.showDetail(false); - } - } - }); - - final View moreSettings = findViewById(android.R.id.button2); - moreSettings.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mHost.startSettingsActivity(ZEN_SETTINGS); - mTile.showDetail(false); - } - }); - final ListView conditions = (ListView) findViewById(android.R.id.content); - mAdapter = new ConditionAdapter(mContext); - conditions.setAdapter(mAdapter); - mAdapter.add(updateTimeCondition()); - - updateZen(mController.isZen()); - } - - private Condition updateTimeCondition() { - final int minutes = MINUTES[mMinutesIndex]; - final long millis = System.currentTimeMillis() + minutes * 60 * 1000; - final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android") - .appendPath("countdown").appendPath(Long.toString(millis)).build(); - final int num = minutes < 60 ? minutes : minutes / 60; - final String units = minutes < 60 ? "minutes" : minutes == 60 ? "hour" : "hours"; - return new Condition(id, "For " + num + " " + units, "", "", 0, Condition.STATE_TRUE, - Condition.FLAG_RELEVANT_NOW); - } - - private void editTimeCondition(int delta) { - final int i = mMinutesIndex + delta; - if (i < 0 || i >= MINUTES.length) return; - mMinutesIndex = i; - mAdapter.remove(mAdapter.getItem(0)); - final Condition c = updateTimeCondition(); - mAdapter.insert(c, 0); - select(c); - } - - private void select(Condition condition) { - mController.select(condition); - } - - private void updateZen(boolean zen) { - mHandler.obtainMessage(H.UPDATE_ZEN, zen ? 1 : 0, 0).sendToTarget(); - } - - private void updateConditions(Condition[] conditions) { - if (conditions == null) return; - mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); - } - - private void handleUpdateZen(boolean zen) { - mSwitch.setChecked(zen); - } - - private void handleUpdateConditions(Condition[] conditions) { - for (int i = mAdapter.getCount() - 1; i > 0; i--) { - mAdapter.remove(mAdapter.getItem(i)); - } - for (Condition condition : conditions) { - mAdapter.add(condition); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mController.addCallback(mCallback); - mController.requestConditions(true); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mController.removeCallback(mCallback); - mController.requestConditions(false); - } - - private final class H extends Handler { - private static final int UPDATE_ZEN = 1; - private static final int UPDATE_CONDITIONS = 2; - - public H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == UPDATE_ZEN) { - handleUpdateZen(msg.arg1 == 1); - } else if (msg.what == UPDATE_CONDITIONS) { - handleUpdateConditions((Condition[])msg.obj); - } - } - } - - private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { - @Override - public void onZenChanged(boolean zen) { - updateZen(zen); - } - public void onConditionsChanged(Condition[] conditions) { - updateConditions(conditions); - } - }; - - private final class ConditionAdapter extends ArrayAdapter<Condition> { - private final LayoutInflater mInflater; - private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>(); - - public ConditionAdapter(Context context) { - super(context, 0); - mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Condition condition = getItem(position); - final boolean enabled = condition.state == Condition.STATE_TRUE; - - final View row = convertView != null ? convertView : mInflater - .inflate(R.layout.qs_zen_mode_detail_condition, parent, false); - final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox); - mRadioButtons.add(rb); - rb.setEnabled(enabled); - rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - for (RadioButton otherButton : mRadioButtons) { - if (otherButton == rb) continue; - otherButton.setChecked(false); - } - select(condition); - } - } - }); - final TextView title = (TextView) row.findViewById(android.R.id.title); - title.setText(condition.summary); - title.setEnabled(enabled); - title.setAlpha(enabled ? 1 : .5f); - final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); - button1.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - editTimeCondition(-1); - } - }); - - final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); - button2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - editTimeCondition(1); - } - }); - title.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - } - }); - if (position != 0) { - button1.setVisibility(View.GONE); - button2.setVisibility(View.GONE); - } - if (position == 0 && mRadioButtons.size() == 1) { - rb.setChecked(true); - } - return row; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java deleted file mode 100644 index bfa9c19..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.content.Context; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; -import com.android.systemui.statusbar.policy.ZenModeController; - -/** Quick settings tile: Zen mode **/ -public class ZenModeTile extends QSTile<QSTile.BooleanState> { - private final ZenModeController mController; - - public ZenModeTile(Host host) { - super(host); - mController = host.getZenModeController(); - } - - @Override - public View createDetailView(Context context, ViewGroup root) { - final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme); - final ZenModeDetail v = (ZenModeDetail) LayoutInflater.from(themedContext) - .inflate(R.layout.qs_zen_mode_detail, root, false); - v.init(this); - return v; - } - - @Override - protected BooleanState newTileState() { - return new BooleanState(); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - mController.addCallback(mCallback); - } else { - mController.removeCallback(mCallback); - } - } - - @Override - protected void handleClick() { - final boolean newZen = !mState.value; - mController.setZen(newZen); - if (newZen) { - showDetail(true); - } - } - - @Override - protected void handleUpdateState(BooleanState state, Object arg) { - final boolean zen = arg instanceof Boolean ? (Boolean)arg : mController.isZen(); - state.value = zen; - state.visible = true; - state.iconId = zen ? R.drawable.ic_qs_zen_on : R.drawable.ic_qs_zen_off; - state.label = mContext.getString(R.string.zen_mode_title); - } - - private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { - @Override - public void onZenChanged(boolean zen) { - if (DEBUG) Log.d(TAG, "onZenChanged " + zen); - refreshState(zen); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index 27881c4..65e1cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -72,8 +72,7 @@ public class BrightnessDialog extends Dialog implements window.setGravity(Gravity.TOP); WindowManager.LayoutParams lp = window.getAttributes(); // Offset from the top - lp.y = getContext().getResources().getDimensionPixelOffset( - com.android.internal.R.dimen.volume_panel_top); + lp.y = getContext().getResources().getDimensionPixelOffset(R.dimen.volume_panel_top); lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index e3dac4a..dcd187c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -149,6 +149,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView super.onFinishInflate(); mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); + updateBackground(); updateBackgroundResources(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java new file mode 100644 index 0000000..06dc4e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * An ImageView which does not have overlapping renderings commands and therefore does not need a + * layer when alpha is changed. + */ +public class AlphaImageView extends ImageView { + public AlphaImageView(Context context) { + super(context); + } + + public AlphaImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index f4db625..fbe76f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -48,6 +48,7 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; @@ -77,11 +78,12 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Locale; import static com.android.keyguard.KeyguardHostView.OnDismissAction; @@ -91,7 +93,7 @@ public abstract class BaseStatusBar extends SystemUI implements public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; - private static final boolean USE_NOTIFICATION_LISTENER = false; + private static final boolean USE_NOTIFICATION_LISTENER = true; protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; @@ -194,7 +196,7 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); if (provisioned != mDeviceProvisioned) { mDeviceProvisioned = provisioned; - updateNotificationIcons(); + updateNotifications(); } final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); @@ -209,7 +211,7 @@ public abstract class BaseStatusBar extends SystemUI implements // so we just dump our cache ... mUsersAllowingPrivateNotifications.clear(); // ... and refresh all the notifications - updateNotificationIcons(); + updateNotifications(); } }; @@ -280,11 +282,12 @@ public abstract class BaseStatusBar extends SystemUI implements public void onListenerConnected() { if (DEBUG) Log.d(TAG, "onListenerConnected"); final StatusBarNotification[] notifications = getActiveNotifications(); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { for (StatusBarNotification sbn : notifications) { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -293,13 +296,14 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationPosted(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { if (mNotificationData.findByKey(sbn.getKey()) != null) { - updateNotificationInternal(sbn); + updateNotificationInternal(sbn, currentRanking); } else { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -308,10 +312,24 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationRemoved(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { - removeNotificationInternal(sbn.getKey()); + removeNotificationInternal(sbn.getKey(), currentRanking); + } + }); + } + + @Override + public void onNotificationRankingUpdate() { + if (DEBUG) Log.d(TAG, "onRankingUpdate"); + final Ranking currentRanking = getCurrentRanking(); + mHandler.post(new Runnable() { + @Override + public void run() { + mNotificationData.updateRanking(currentRanking); + updateNotifications(); } }); } @@ -1113,19 +1131,13 @@ public abstract class BaseStatusBar extends SystemUI implements } } - protected StatusBarNotification removeNotificationViews(String key) { - NotificationData.Entry entry = mNotificationData.remove(key); + protected StatusBarNotification removeNotificationViews(String key, Ranking ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); if (entry == null) { Log.w(TAG, "removeNotification for unknown key: " + key); return null; } - // Remove the expanded view. - ViewGroup rowParent = (ViewGroup)entry.row.getParent(); - if (rowParent != null) rowParent.removeView(entry.row); - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - + updateNotifications(); return entry.notification; } @@ -1159,35 +1171,17 @@ public abstract class BaseStatusBar extends SystemUI implements return entry; } - protected void addNotificationViews(NotificationData.Entry entry) { + protected void addNotificationViews(Entry entry, Ranking ranking) { if (entry == null) { return; } // Add the expanded view and icon. - int pos = mNotificationData.add(entry); - if (DEBUG) { - Log.d(TAG, "addNotificationViews: added at " + pos); - } - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - } - - protected void updateSpeedBump() { - int n = mNotificationData.size(); - int speedBumpIndex = -1; - for (int i = n-1; i >= 0; i--) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 - && entry.row.isBelowSpeedBump() ) { - speedBumpIndex = n - 1 - i; - } - } - mStackScroller.updateSpeedBumpIndex(speedBumpIndex); + mNotificationData.add(entry, ranking); + updateNotifications(); } - private void addNotificationViews(StatusBarNotification notification) { - addNotificationViews(createNotificationViews(notification)); + private void addNotificationViews(StatusBarNotification notification, Ranking ranking) { + addNotificationViews(createNotificationViews(notification), ranking); } /** @@ -1201,17 +1195,17 @@ public abstract class BaseStatusBar extends SystemUI implements protected void updateRowStates() { int maxKeyguardNotifications = getMaxKeyguardNotifications(); mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); - int n = mNotificationData.size(); + final int N = mNotificationData.size(); int visibleNotifications = 0; boolean onKeyguard = mState == StatusBarState.KEYGUARD; - for (int i = n-1; i >= 0; i--) { + for (int i = 0; i < N; i++) { NotificationData.Entry entry = mNotificationData.get(i); if (onKeyguard) { entry.row.setExpansionDisabled(true); } else { entry.row.setExpansionDisabled(false); if (!entry.row.isUserLocked()) { - boolean top = (i == n-1); + boolean top = (i == 0); entry.row.setSystemExpanded(top); } } @@ -1238,6 +1232,9 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } + // Move overflow container to last position. + mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, + mStackScroller.getChildCount() - 1); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -1247,46 +1244,42 @@ public abstract class BaseStatusBar extends SystemUI implements protected void setZenMode(int mode) { if (!isDeviceProvisioned()) return; mZenMode = mode; - updateNotificationIcons(); + updateNotifications(); } protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); - protected abstract void updateNotificationIcons(); + protected abstract void updateNotifications(); protected abstract void tick(StatusBarNotification n, boolean firstTime); protected abstract void updateExpandedViewPos(int expandedPosition); protected abstract boolean shouldDisableNavbarGestures(); - protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { - return parent != null && parent.indexOfChild(entry.row) == 0; - } - - @Override public void addNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - addNotificationInternal(notification); + addNotificationInternal(notification, null); } } - public abstract void addNotificationInternal(StatusBarNotification notification); + public abstract void addNotificationInternal(StatusBarNotification notification, + Ranking ranking); @Override public void removeNotification(String key) { if (!USE_NOTIFICATION_LISTENER) { - removeNotificationInternal(key); + removeNotificationInternal(key, null); } } - protected abstract void removeNotificationInternal(String key); + protected abstract void removeNotificationInternal(String key, Ranking ranking); public void updateNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - updateNotificationInternal(notification); + updateNotificationInternal(notification, null); } } - public void updateNotificationInternal(StatusBarNotification notification) { + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final NotificationData.Entry oldEntry = mNotificationData.findByKey(notification.getKey()); @@ -1358,18 +1351,12 @@ public abstract class BaseStatusBar extends SystemUI implements && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); - boolean orderUnchanged = - notification.getNotification().when == oldNotification.getNotification().when - && notification.getScore() == oldNotification.getScore(); - // score now encompasses/supersedes isOngoing() boolean updateTicker = notification.getNotification().tickerText != null && !TextUtils.equals(notification.getNotification().tickerText, oldEntry.notification.getNotification().tickerText); - boolean isTopAnyway = isTopNotification(rowParent, oldEntry); - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged - && (orderUnchanged || isTopAnyway)) { + if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged) { if (DEBUG) Log.d(TAG, "reusing notification for key: " + notification.getKey()); oldEntry.notification = notification; try { @@ -1397,22 +1384,20 @@ public abstract class BaseStatusBar extends SystemUI implements handleNotificationError(notification, "Couldn't update icon: " + ic); return; } - updateRowStates(); - updateSpeedBump(); + mNotificationData.updateRanking(ranking); + updateNotifications(); } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); } } else { if (DEBUG) Log.d(TAG, "not reusing notification for key: " + notification.getKey()); if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); // will also replace the heads up + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); // will also replace the heads up final NotificationData.Entry newEntry = mNotificationData.findByKey( notification.getKey()); final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); @@ -1559,5 +1544,12 @@ public abstract class BaseStatusBar extends SystemUI implements mWindowManager.removeViewImmediate(mSearchPanelView); } mContext.unregisterReceiver(mBroadcastReceiver); + if (USE_NOTIFICATION_LISTENER) { + try { + mNotificationListener.unregisterAsSystemService(); + } catch (RemoteException e) { + // Ignore. + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 7d576cb..5f1325b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.animation.ValueAnimator; import android.content.Context; +import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -46,6 +47,8 @@ public class FlingAnimationUtils { private float mMaxLengthSeconds; private float mHighVelocityPxPerSecond; + private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); + public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { mMaxLengthSeconds = maxLengthSeconds; mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); @@ -80,18 +83,59 @@ public class FlingAnimationUtils { * @param currValue the current value * @param endValue the end value of the animator * @param velocity the current velocity of the motion + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity) { + apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion * @param maxDistance the maximum distance for this interaction; the maximum animation length * gets multiplied by the ratio between the actual distance and this value */ public void apply(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getProperties(float currValue, + float endValue, float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); float diff = Math.abs(endValue - currValue); float velAbs = Math.abs(velocity); float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutSlowIn); + mAnimatorProperties.interpolator = mLinearOutSlowIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between fast-out-slow-in and linear interpolator with current velocity. @@ -100,14 +144,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutSlowIn); + mAnimatorProperties.interpolator = mFastOutSlowIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -124,6 +169,34 @@ public class FlingAnimationUtils { */ public void applyDismissing(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion for the case when the animation is making something + * disappear. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getDismissingProperties(float currValue, float endValue, + float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); float diff = Math.abs(endValue - currValue); @@ -135,7 +208,7 @@ public class FlingAnimationUtils { Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, 1, y2); float durationSeconds = startGradient * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutFasterIn); + mAnimatorProperties.interpolator = mLinearOutFasterIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between linear-out-faster-in and linear interpolator with current @@ -145,14 +218,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutLinearIn); + mAnimatorProperties.interpolator = mFastOutLinearIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -221,4 +295,10 @@ public class FlingAnimationUtils { return time * mVelocity / mDiff; } } + + private static class AnimatorProperties { + Interpolator interpolator; + long duration; + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java index 0555879..24da5c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java @@ -31,7 +31,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar; public class InterceptedNotifications { private static final String TAG = "InterceptedNotifications"; private static final String EXTRA_INTERCEPT = "android.intercept"; - private static final String SYNTHETIC_KEY = "InterceptedNotifications.SYNTHETIC_KEY"; private final Context mContext; private final PhoneStatusBar mBar; @@ -50,7 +49,7 @@ public class InterceptedNotifications { for (int i = 0; i < n; i++) { final StatusBarNotification sbn = mIntercepted.valueAt(i); sbn.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } mIntercepted.clear(); updateSyntheticNotification(); @@ -71,7 +70,7 @@ public class InterceptedNotifications { } public boolean isSyntheticEntry(Entry ent) { - return ent.key.equals(SYNTHETIC_KEY); + return ent.key.equals(mSynKey); } public void update(StatusBarNotification notification) { @@ -88,7 +87,7 @@ public class InterceptedNotifications { private void updateSyntheticNotification() { if (mIntercepted.isEmpty()) { if (mSynKey != null) { - mBar.removeNotificationInternal(mSynKey); + mBar.removeNotificationInternal(mSynKey, null); mSynKey = null; } return; @@ -107,9 +106,9 @@ public class InterceptedNotifications { mBar.getCurrentUserHandle()); if (mSynKey == null) { mSynKey = sbn.getKey(); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } else { - mBar.updateNotificationInternal(sbn); + mBar.updateNotificationInternal(sbn, null); } final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey); entry.row.setOnClickListener(mSynClickListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 5696246..d829ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -16,15 +16,19 @@ package com.android.systemui.statusbar; +import android.app.Notification; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; -import android.widget.ImageView; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; /** * The list of currently displaying notifications. + * + * TODO: Rename to NotificationList. */ public class NotificationData { public static final class Entry { @@ -34,7 +38,6 @@ public class NotificationData { public ExpandableNotificationRow row; // the outer expanded view public View expanded; // the inflated RemoteViews public View expandedPublic; // for insecure lockscreens - public ImageView largeIcon; public View expandedBig; private boolean interruption; public Entry() {} @@ -64,18 +67,23 @@ public class NotificationData { } private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); - private final Comparator<Entry> mEntryCmp = new Comparator<Entry>() { - // sort first by score, then by when + private Ranking mRanking; + private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { + @Override public int compare(Entry a, Entry b) { + if (mRanking != null) { + return mRanking.getRank(a.key) - mRanking.getRank(b.key); + } + final StatusBarNotification na = a.notification; final StatusBarNotification nb = b.notification; - int d = na.getScore() - nb.getScore(); + int d = nb.getScore() - na.getScore(); if (a.interruption != b.interruption) { - return a.interruption ? 1 : -1; + return a.interruption ? -1 : 1; } else if (d != 0) { return d; } else { - return (int) (na.getNotification().when - nb.getNotification().when); + return (int) (nb.getNotification().when - na.getNotification().when); } } }; @@ -97,26 +105,47 @@ public class NotificationData { return null; } - public int add(Entry entry) { - int i; - int N = mEntries.size(); - for (i = 0; i < N; i++) { - if (mEntryCmp.compare(mEntries.get(i), entry) > 0) { - break; - } - } - mEntries.add(i, entry); - return i; + public void add(Entry entry, Ranking ranking) { + mEntries.add(entry); + updateRankingAndSort(ranking); } - public Entry remove(String key) { + public Entry remove(String key, Ranking ranking) { Entry e = findByKey(key); - if (e != null) { - mEntries.remove(e); + if (e == null) { + return null; } + mEntries.remove(e); + updateRankingAndSort(ranking); return e; } + public void updateRanking(Ranking ranking) { + updateRankingAndSort(ranking); + } + + public boolean isAmbient(String key) { + // TODO: Remove when switching to NotificationListener. + if (mRanking == null) { + for (Entry entry : mEntries) { + if (key.equals(entry.key)) { + return entry.notification.getNotification().priority == + Notification.PRIORITY_MIN; + } + } + } else { + return mRanking.isAmbient(key); + } + return false; + } + + private void updateRankingAndSort(Ranking ranking) { + if (ranking != null) { + mRanking = ranking; + } + Collections.sort(mEntries, mRankingComparator); + } + /** * Return whether there are any visible items (i.e. items without an error). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 714ad06..994b329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; @@ -33,26 +32,23 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; - import com.android.systemui.R; /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. */ -public class KeyguardBottomAreaView extends FrameLayout - implements SwipeAffordanceView.AffordanceListener, +public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, UnlockMethodCache.OnUnlockMethodChangedListener { final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); - private SwipeAffordanceView mCameraButton; - private SwipeAffordanceView mPhoneButton; + private ImageView mCameraImageView; + private ImageView mPhoneImageView; private ImageView mLockIcon; - private PowerManager mPowerManager; private ActivityStarter mActivityStarter; private UnlockMethodCache mUnlockMethodCache; @@ -76,11 +72,9 @@ public class KeyguardBottomAreaView extends FrameLayout @Override protected void onFinishInflate() { super.onFinishInflate(); - mCameraButton = (SwipeAffordanceView) findViewById(R.id.camera_button); - mPhoneButton = (SwipeAffordanceView) findViewById(R.id.phone_button); + mCameraImageView = (ImageView) findViewById(R.id.camera_button); + mPhoneImageView = (ImageView) findViewById(R.id.phone_button); mLockIcon = (ImageView) findViewById(R.id.lock_icon); - mCameraButton.setAffordanceListener(this); - mPhoneButton.setAffordanceListener(this); watchForDevicePolicyChanges(); watchForAccessibilityChanges(); updateCameraVisibility(); @@ -88,7 +82,6 @@ public class KeyguardBottomAreaView extends FrameLayout mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); updateTrust(); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } public void setActivityStarter(ActivityStarter activityStarter) { @@ -97,12 +90,12 @@ public class KeyguardBottomAreaView extends FrameLayout private void updateCameraVisibility() { boolean visible = !isCameraDisabledByDpm(); - mCameraButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private void updatePhoneVisibility() { boolean visible = isPhoneVisible(); - mPhoneButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private boolean isPhoneVisible() { @@ -162,33 +155,31 @@ public class KeyguardBottomAreaView extends FrameLayout } private void enableAccessibility(boolean touchExplorationEnabled) { - mCameraButton.enableAccessibility(touchExplorationEnabled); - mPhoneButton.enableAccessibility(touchExplorationEnabled); + mCameraImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mCameraImageView.setClickable(touchExplorationEnabled); + mPhoneImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mPhoneImageView.setClickable(touchExplorationEnabled); } - private void launchCamera() { + @Override + public void onClick(View v) { + if (v == mCameraImageView) { + launchCamera(); + } else if (v == mPhoneImageView) { + launchPhone(); + } + } + + public void launchCamera() { mContext.startActivityAsUser( new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), UserHandle.CURRENT); } - private void launchPhone() { + public void launchPhone() { mActivityStarter.startActivity(PHONE_INTENT); } - @Override - public void onUserActivity(long when) { - mPowerManager.userActivity(when, false); - } - - @Override - public void onActionPerformed(SwipeAffordanceView view) { - if (view == mCameraButton) { - launchCamera(); - } else if (view == mPhoneButton) { - launchPhone(); - } - } @Override protected void onVisibilityChanged(View changedView, int visibility) { @@ -208,6 +199,18 @@ public class KeyguardBottomAreaView extends FrameLayout mLockIcon.setImageResource(iconRes); } + public ImageView getPhoneImageView() { + return mPhoneImageView; + } + + public ImageView getCameraImageView() { + return mCameraImageView; + } + + public ImageView getLockIcon() { + return mLockIcon; + } + @Override public void onMethodSecureChanged(boolean methodSecure) { updateTrust(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 2bb80bf..b7a7b0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -56,12 +56,16 @@ public class KeyguardBouncer { public void show() { ensureView(); + if (mRoot.getVisibility() == View.VISIBLE) { + return; + } // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole // Keyguard. If we need to authenticate, show the bouncer. if (!mKeyguardView.dismiss()) { mRoot.setVisibility(View.VISIBLE); mKeyguardView.onResume(); + mKeyguardView.startAppearAnimation(); } } @@ -109,6 +113,10 @@ public class KeyguardBouncer { return mRoot != null && mRoot.getVisibility() == View.VISIBLE; } + public void prepare() { + ensureView(); + } + private void ensureView() { if (mRoot == null) { inflateView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 769b1b1..c5c3fff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.TextView; /** @@ -50,7 +52,12 @@ public class KeyguardIndicationTextView extends TextView { public void switchIndication(CharSequence text) { // TODO: Animation, make sure that we will show one indication long enough. - setText(text); + if (TextUtils.isEmpty(text)) { + setVisibility(View.INVISIBLE); + } else { + setVisibility(View.VISIBLE); + setText(text); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java new file mode 100644 index 0000000..b4f4865 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.os.PowerManager; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import com.android.systemui.R; +import com.android.systemui.statusbar.FlingAnimationUtils; + +import java.util.ArrayList; + +/** + * A touch handler of the Keyguard which is responsible for swiping the content left or right. + */ +public class KeyguardPageSwipeHelper { + + private static final float SWIPE_MAX_ICON_SCALE_AMOUNT = 2.0f; + private static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.7f; + private final Context mContext; + + private FlingAnimationUtils mFlingAnimationUtils; + private Callback mCallback; + private int mTrackingPointer; + private VelocityTracker mVelocityTracker; + private boolean mSwipingInProgress; + private float mInitialTouchX; + private float mInitialTouchY; + private float mTranslation; + private float mTranslationOnDown; + private int mTouchSlop; + private int mMinTranslationAmount; + private int mMinFlingVelocity; + private PowerManager mPowerManager; + private final View mLeftIcon; + private final View mCenterIcon; + private final View mRightIcon; + private Interpolator mFastOutSlowIn; + private Animator mSwipeAnimator; + private boolean mCallbackCalled; + + KeyguardPageSwipeHelper(Callback callback, Context context) { + mContext = context; + mCallback = callback; + mLeftIcon = mCallback.getLeftIcon(); + mCenterIcon = mCallback.getCenterIcon(); + mRightIcon = mCallback.getRightIcon(); + updateIcon(mLeftIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mCenterIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mRightIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + initDimens(); + } + + private void initDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(mContext); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + mMinTranslationAmount = mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_min_swipe_amount); + mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f); + mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_slow_in); + } + + public boolean onTouchEvent(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (mSwipingInProgress) { + cancelAnimations(); + } + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + initVelocityTracker(); + trackMovement(event); + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchY = newY; + mInitialTouchX = newX; + mTranslationOnDown = mTranslation; + } + break; + + case MotionEvent.ACTION_MOVE: + final float w = x - mInitialTouchX; + trackMovement(event); + if (((leftSwipePossible() && w > mTouchSlop) + || (rightSwipePossible() && w < -mTouchSlop)) + && Math.abs(w) > Math.abs(y - mInitialTouchY) + && !mSwipingInProgress) { + cancelAnimations(); + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + mSwipingInProgress = true; + } + if (mSwipingInProgress) { + setTranslation(mTranslationOnDown + x - mInitialTouchX, false); + onUserActivity(event.getEventTime()); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTrackingPointer = -1; + trackMovement(event); + if (mSwipingInProgress) { + flingWithCurrentVelocity(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; + } + + private boolean rightSwipePossible() { + return mRightIcon.getVisibility() == View.VISIBLE; + } + + private boolean leftSwipePossible() { + return mLeftIcon.getVisibility() == View.VISIBLE; + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + private void onUserActivity(long when) { + mPowerManager.userActivity(when, false); + } + + private void cancelAnimations() { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View target : targetViews) { + target.animate().cancel(); + } + View targetView = mTranslation > 0 ? mLeftIcon : mRightIcon; + targetView.animate().cancel(); + if (mSwipeAnimator != null) { + mSwipeAnimator.removeAllListeners(); + mSwipeAnimator.cancel(); + hideInactiveIcons(true); + } + } + + private void flingWithCurrentVelocity() { + float vel = getCurrentVelocity(); + + // We snap back if the current translation is not far enough + boolean snapBack = Math.abs(mTranslation) < mMinTranslationAmount; + + // or if the velocity is in the opposite direction. + boolean velIsInWrongDirection = vel * mTranslation < 0; + snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection; + vel = snapBack ^ velIsInWrongDirection ? 0 : vel; + fling(vel, snapBack); + } + + private void fling(float vel, final boolean snapBack) { + float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth(); + target = snapBack ? 0 : target; + + // translation Animation + startTranslationAnimations(vel, target); + + // animate left / right icon + startIconAnimation(vel, snapBack, target); + + ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mTranslation = (float) animation.getAnimatedValue(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeAnimator = null; + mSwipingInProgress = false; + if (!snapBack && !mCallbackCalled) { + + // ensure that the callback is called eventually + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + } + }); + if (!snapBack) { + mCallbackCalled = false; + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + int frameNumber; + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (frameNumber == 2 && !mCallbackCalled) { + + // we have to wait for the second frame for this call, + // until the render thread has definitely kicked in, to avoid a lag. + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + frameNumber++; + } + }); + } else { + showAllIcons(true); + } + animator.start(); + mSwipeAnimator = animator; + } + + private void startTranslationAnimations(float vel, float target) { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View targetView : targetViews) { + ViewPropertyAnimator animator = targetView.animate(); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.translationX(target); + } + } + + private void startIconAnimation(float vel, boolean snapBack, float target) { + float scale = snapBack ? 1.0f : SWIPE_MAX_ICON_SCALE_AMOUNT; + float alpha = snapBack ? SWIPE_RESTING_ALPHA_AMOUNT : 1.0f; + View targetView = mTranslation > 0 + ? mLeftIcon + : mRightIcon; + if (targetView.getVisibility() == View.VISIBLE) { + ViewPropertyAnimator iconAnimator = targetView.animate(); + mFlingAnimationUtils.apply(iconAnimator, mTranslation, target, vel); + iconAnimator.scaleX(scale); + iconAnimator.scaleY(scale); + iconAnimator.alpha(alpha); + } + } + + private void setTranslation(float translation, boolean isReset) { + translation = rightSwipePossible() ? translation : Math.max(0, translation); + translation = leftSwipePossible() ? translation : Math.min(0, translation); + if (translation != mTranslation) { + ArrayList<View> translatedViews = mCallback.getTranslationViews(); + for (View view : translatedViews) { + view.setTranslationX(translation); + } + if (translation == 0.0f) { + boolean animate = !isReset; + showAllIcons(animate); + } else { + View targetView = translation > 0 ? mLeftIcon : mRightIcon; + float progress = Math.abs(translation) / mCallback.getPageWidth(); + progress = Math.min(progress, 1.0f); + float alpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - progress) + progress; + float scale = (1.0f - progress) + progress * SWIPE_MAX_ICON_SCALE_AMOUNT; + updateIcon(targetView, scale, alpha, false); + View otherView = translation < 0 ? mLeftIcon : mRightIcon; + if (mTranslation * translation <= 0) { + // The sign of the translation has changed so we need to hide the other icons + updateIcon(otherView, 0, 0, true); + updateIcon(mCenterIcon, 0, 0, true); + } + } + mTranslation = translation; + } + } + + private void showAllIcons(boolean animate) { + float scale = 1.0f; + float alpha = SWIPE_RESTING_ALPHA_AMOUNT; + updateIcon(mRightIcon, scale, alpha, animate); + updateIcon(mCenterIcon, scale, alpha, animate); + updateIcon(mLeftIcon, scale, alpha, animate); + } + + private void hideInactiveIcons(boolean animate){ + View otherView = mTranslation < 0 ? mLeftIcon : mRightIcon; + updateIcon(otherView, 0, 0, animate); + updateIcon(mCenterIcon, 0, 0, animate); + } + + private void updateIcon(View view, float scale, float alpha, boolean animate) { + if (view.getVisibility() != View.VISIBLE) { + return; + } + if (!animate) { + view.setAlpha(alpha); + view.setScaleX(scale); + view.setScaleY(scale); + // TODO: remove this invalidate once the property setters invalidate it properly + view.invalidate(); + } else { + if (view.getAlpha() != alpha || view.getScaleX() != scale) { + view.animate() + .setInterpolator(mFastOutSlowIn) + .alpha(alpha) + .scaleX(scale) + .scaleY(scale); + } + } + } + + private void trackMovement(MotionEvent event) { + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + } + } + + private void initVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + } + mVelocityTracker = VelocityTracker.obtain(); + } + + private float getCurrentVelocity() { + if (mVelocityTracker == null) { + return 0; + } + mVelocityTracker.computeCurrentVelocity(1000); + return mVelocityTracker.getXVelocity(); + } + + public void onConfigurationChanged() { + initDimens(); + } + + public void reset() { + setTranslation(0.0f, true); + mSwipingInProgress = false; + } + + public boolean isSwipingInProgress() { + return mSwipingInProgress; + } + + public interface Callback { + + /** + * Notifies the callback when an animation to a side page was started. + * + * @param rightPage Is the page animated to the right page? + */ + void onAnimationToSideStarted(boolean rightPage); + + float getPageWidth(); + + ArrayList<View> getTranslationViews(); + + View getLeftIcon(); + + View getCenterIcon(); + + View getRightIcon(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index c83b479..3753a72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -30,7 +30,6 @@ import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { - private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f; private static final int CONTENT_FADE_DURATION = 200; private final NavigationBarView mView; @@ -81,8 +80,6 @@ public final class NavigationBarTransitions extends BarTransitions { setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate); - applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out @@ -96,7 +93,6 @@ public final class NavigationBarTransitions extends BarTransitions { public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { float backAlpha = 0; - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); @@ -116,7 +112,6 @@ public final class NavigationBarTransitions extends BarTransitions { public void setContentVisible(boolean visible) { final float alpha = visible ? 1 : 0; fadeContent(mView.getBackButton(), alpha); - fadeContent(mView.getSearchLight(), alpha); } private void fadeContent(View v, float alpha) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 089757a..21842bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -133,16 +133,6 @@ public class NavigationBarView extends LinearLayout { } } - // simplified click handler to be used when device is in accessibility mode - private final OnClickListener mAccessibilityClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (v.getId() == R.id.search_light) { - KeyguardTouchDelegate.getInstance(getContext()).showAssistant(); - } - } - }; - private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { @Override public void onClick(View view) { @@ -246,11 +236,6 @@ public class NavigationBarView extends LinearLayout { return mCurrentView.findViewById(R.id.ime_switcher); } - // for when home is disabled, but search isn't - public View getSearchLight() { - return mCurrentView.findViewById(R.id.search_light); - } - private void getIcons(Resources res) { mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); @@ -345,9 +330,6 @@ public class NavigationBarView extends LinearLayout { getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - final boolean showSearch = disableHome && !disableSearch; - setVisibleOrGone(getSearchLight(), showSearch); - mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); } @@ -402,38 +384,6 @@ public class NavigationBarView extends LinearLayout { mCurrentView = mRotatedViews[Surface.ROTATION_0]; getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); - - watchForAccessibilityChanges(); - } - - private void watchForAccessibilityChanges() { - final AccessibilityManager am = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - // Set the initial state - enableAccessibility(am.isTouchExplorationEnabled()); - - // Watch for changes - am.addTouchExplorationStateChangeListener(new TouchExplorationStateChangeListener() { - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - enableAccessibility(enabled); - } - }); - } - - private void enableAccessibility(boolean touchEnabled) { - Log.v(TAG, "touchEnabled:" + touchEnabled); - - // Add a touch handler or accessibility click listener for camera and search buttons - // for all view orientations. - final OnClickListener onClickListener = touchEnabled ? mAccessibilityClickListener : null; - for (int i = 0; i < mRotatedViews.length; i++) { - final View searchLight = mRotatedViews[i].findViewById(R.id.search_light); - if (searchLight != null) { - searchLight.setOnClickListener(onClickListener); - } - } } public boolean isVertical() { @@ -571,7 +521,6 @@ public class NavigationBarView extends LinearLayout { dumpButton(pw, "home", getHomeButton()); dumpButton(pw, "rcnt", getRecentsButton()); dumpButton(pw, "menu", getMenuButton()); - dumpButton(pw, "srch", getSearchLight()); pw.println(" }"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index f5252a3..802e5e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -40,10 +41,13 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.util.ArrayList; + public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, - View.OnClickListener { + View.OnClickListener, KeyguardPageSwipeHelper.Callback { + private KeyguardPageSwipeHelper mPageSwiper; PhoneStatusBar mStatusBar; private StatusBarHeaderView mHeader; private View mQsContainer; @@ -58,7 +62,7 @@ public class NotificationPanelView extends PanelView implements private int mTrackingPointer; private VelocityTracker mVelocityTracker; - private boolean mTracking; + private boolean mQsTracking; /** * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't @@ -92,6 +96,11 @@ public class NotificationPanelView extends PanelView implements new KeyguardClockPositionAlgorithm(); private KeyguardClockPositionAlgorithm.Result mClockPositionResult = new KeyguardClockPositionAlgorithm.Result(); + private boolean mIsSwipedHorizontally; + private boolean mIsExpanding; + private KeyguardBottomAreaView mKeyguardBottomArea; + private boolean mBlockTouches; + private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -129,6 +138,10 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in); + mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); + mSwipeTranslationViews.add(mNotificationStackScroller); + mSwipeTranslationViews.add(mKeyguardStatusView); + mPageSwiper = new KeyguardPageSwipeHelper(this, getContext()); } @Override @@ -151,7 +164,9 @@ public class NotificationPanelView extends PanelView implements // Calculate quick setting heights. mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight; mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); - if (!mQsExpanded) { + if (mQsExpanded) { + setQsStackScrollerPadding(mQsMaxExpansionHeight); + } else { setQsExpansion(mQsMinExpansionHeight); positionClockAndNotifications(); mNotificationStackScroller.setStackHeight(getExpandedHeight()); @@ -247,6 +262,13 @@ public class NotificationPanelView extends PanelView implements mQsExpansionEnabled = qsExpansionEnabled; } + @Override + public void resetViews() { + mBlockTouches = false; + mPageSwiper.reset(); + closeQs(); + } + public void closeQs() { cancelAnimation(); setQsExpansion(mQsMinExpansionHeight); @@ -263,9 +285,7 @@ public class NotificationPanelView extends PanelView implements public void fling(float vel, boolean always) { GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); if (gr != null) { - gr.tag( - "fling " + ((vel > 0) ? "open" : "closed"), - "notifications,v=" + vel); + gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } super.fling(vel, always); } @@ -283,6 +303,9 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -298,7 +321,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) { + if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { getParent().requestDisallowInterceptTouchEvent(true); } break; @@ -316,7 +339,7 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); - if (mTracking) { + if (mQsTracking) { // Already tracking because onOverscrolled was called. We need to update here // so we don't stop for a frame until the next touch event gets handled in @@ -327,12 +350,12 @@ public class NotificationPanelView extends PanelView implements return true; } if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) - && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) { + && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = y; mInitialTouchX = x; - mTracking = true; + mQsTracking = true; mIntercepting = false; return true; } @@ -341,9 +364,9 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: trackMovement(event); - if (mTracking) { - flingWithCurrentVelocity(); - mTracking = false; + if (mQsTracking) { + flingQsWithCurrentVelocity(); + mQsTracking = false; } mIntercepting = false; break; @@ -362,7 +385,7 @@ public class NotificationPanelView extends PanelView implements super.requestDisallowInterceptTouchEvent(disallowIntercept); } - private void flingWithCurrentVelocity() { + private void flingQsWithCurrentVelocity() { float vel = getCurrentVelocity(); // TODO: Better logic whether we should expand or not. @@ -371,65 +394,83 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference // implementation. - if (mTracking) { - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); + if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { + mPageSwiper.onTouchEvent(event); + if (mPageSwiper.isSwipingInProgress()) { + return true; } - final float y = event.getY(pointerIndex); - final float x = event.getX(pointerIndex); + } + if (mQsTracking || mQsExpanded) { + return onQsTouch(event); + } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mTracking = true; - mInitialTouchY = y; - mInitialTouchX = x; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - initVelocityTracker(); - trackMovement(event); - break; - - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mTrackingPointer = event.getPointerId(newIndex); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; - } - break; + super.onTouchEvent(event); + return true; + } - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY; - setQsExpansion(h + mInitialHeightOnTouch); - trackMovement(event); - break; + @Override + protected boolean hasConflictingGestures() { + return mStatusBar.getBarState() != StatusBarState.SHADE; + } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mTracking = false; - mTrackingPointer = -1; - trackMovement(event); - flingWithCurrentVelocity(); - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - return true; + private boolean onQsTouch(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mQsTracking = true; + mInitialTouchY = y; + mInitialTouchX = x; + onQsExpansionStarted(); + mInitialHeightOnTouch = mQsExpansionHeight; + initVelocityTracker(); + trackMovement(event); + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialHeightOnTouch = mQsExpansionHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + } + break; + + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + setQsExpansion(h + mInitialHeightOnTouch); + trackMovement(event); + break; - // Consume touch events when QS are expanded. - return mQsExpanded || super.onTouchEvent(event); + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mQsTracking = false; + mTrackingPointer = -1; + trackMovement(event); + flingQsWithCurrentVelocity(); + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; } @Override @@ -439,7 +480,7 @@ public class NotificationPanelView extends PanelView implements mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = mLastTouchY; mInitialTouchX = mLastTouchX; - mTracking = true; + mQsTracking = true; } } @@ -569,7 +610,7 @@ public class NotificationPanelView extends PanelView implements /** * @return Whether we should intercept a gesture to open Quick Settings. */ - private boolean shouldIntercept(float x, float y, float yDiff) { + private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { if (!mQsExpansionEnabled) { return false; } @@ -647,12 +688,14 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingStarted() { super.onExpandingStarted(); mNotificationStackScroller.onExpansionStarted(); + mIsExpanding = true; } @Override protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mIsExpanding = false; } @Override @@ -664,8 +707,8 @@ public class NotificationPanelView extends PanelView implements } @Override - protected void onTrackingStopped() { - super.onTrackingStopped(); + protected void onTrackingStopped(boolean expand) { + super.onTrackingStopped(expand); mOverExpansion = 0.0f; mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, true /* animate */); @@ -686,6 +729,12 @@ public class NotificationPanelView extends PanelView implements } @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mPageSwiper.onConfigurationChanged(); + } + + @Override public void onClick(View v) { if (v == mHeader.getBackgroundView()) { onQsExpansionStarted(); @@ -696,4 +745,40 @@ public class NotificationPanelView extends PanelView implements } } } + + @Override + public void onAnimationToSideStarted(boolean rightPage) { + if (rightPage) { + mKeyguardBottomArea.launchCamera(); + } else { + mKeyguardBottomArea.launchPhone(); + } + mBlockTouches = true; + } + + + @Override + public float getPageWidth() { + return getWidth(); + } + + @Override + public ArrayList<View> getTranslationViews() { + return mSwipeTranslationViews; + } + + @Override + public View getLeftIcon() { + return mKeyguardBottomArea.getPhoneImageView(); + } + + @Override + public View getCenterIcon() { + return mKeyguardBottomArea.getLockIcon(); + } + + @Override + public View getRightIcon() { + return mKeyguardBottomArea.getCameraImageView(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 8800625..b94f6f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -191,6 +191,7 @@ public class PanelBar extends FrameLayout { pv.setExpandedFraction(0); // just in case pv.setVisibility(View.GONE); pv.cancelPeek(); + pv.resetViews(); } } if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting); @@ -222,7 +223,11 @@ public class PanelBar extends FrameLayout { } } - public void onTrackingStopped(PanelView panel) { + public void onTrackingStopped(PanelView panel, boolean expand) { mTracking = false; } + + public void onExpandingFinished() { + + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 7c1f2cf..c5a9b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -35,7 +35,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.FileDescriptor; import java.io.PrintWriter; -public class PanelView extends FrameLayout { +public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); protected float mOverExpansion; @@ -67,6 +67,7 @@ public class PanelView extends FrameLayout { private float mInitialTouchX; protected void onExpandingFinished() { + mBar.onExpandingFinished(); } protected void onExpandingStarted() { @@ -129,21 +130,24 @@ public class PanelView extends FrameLayout { final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + boolean waitForTouchSlop = hasConflictingGestures(); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - mTracking = true; mInitialTouchY = y; mInitialTouchX = x; + mInitialOffsetOnTouch = mExpandedHeight; if (mVelocityTracker == null) { initVelocityTracker(); } trackMovement(event); - if (mHeightAnimator != null) { - mHeightAnimator.cancel(); // end any outstanding animations + if (!waitForTouchSlop || mHeightAnimator != null) { + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); } - onTrackingStarted(); - mInitialOffsetOnTouch = mExpandedHeight; if (mExpandedHeight == 0) { mJustPeeked = true; runPeekAnimation(); @@ -165,15 +169,27 @@ public class PanelView extends FrameLayout { break; case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY + mInitialOffsetOnTouch; - if (h > mPeekHeight) { + float h = y - mInitialTouchY; + if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop + && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + mInitialOffsetOnTouch = mExpandedHeight; + mInitialTouchX = x; + mInitialTouchY = y; + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); + h = 0; + } + final float newHeight = h + mInitialOffsetOnTouch; + if (newHeight > mPeekHeight) { if (mPeekAnimator != null && mPeekAnimator.isStarted()) { mPeekAnimator.cancel(); } mJustPeeked = false; } - if (!mJustPeeked) { - setExpandedHeightInternal(h); + if (!mJustPeeked && (!waitForTouchSlop || mTracking)) { + setExpandedHeightInternal(newHeight); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); } @@ -182,25 +198,28 @@ public class PanelView extends FrameLayout { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTracking = false; mTrackingPointer = -1; - onTrackingStopped(); trackMovement(event); - flingWithCurrentVelocity(); + boolean expand = flingWithCurrentVelocity(); + onTrackingStopped(expand); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } - return true; + return !waitForTouchSlop || mTracking; } - protected void onTrackingStopped() { - mBar.onTrackingStopped(PanelView.this); + protected abstract boolean hasConflictingGestures(); + + protected void onTrackingStopped(boolean expand) { + mTracking = false; + mBar.onTrackingStopped(PanelView.this, expand); } protected void onTrackingStarted() { + mTracking = true; mBar.onTrackingStarted(PanelView.this); onExpandingStarted(); } @@ -303,7 +322,10 @@ public class PanelView extends FrameLayout { mMaxPanelHeight = -1; } - private void flingWithCurrentVelocity() { + /** + * @return whether the panel will be expanded after the animation + */ + private boolean flingWithCurrentVelocity() { float vel = getCurrentVelocity(); boolean expand; if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { @@ -312,11 +334,16 @@ public class PanelView extends FrameLayout { expand = vel > 0; } fling(vel, expand); + return expand; } protected void fling(float vel, boolean expand) { cancelPeek(); float target = expand ? getMaxPanelHeight() : 0.0f; + if (target == mExpandedHeight) { + onExpandingFinished(); + return; + } ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, target); if (expand) { mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); @@ -513,4 +540,6 @@ public class PanelView extends FrameLayout { mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"") )); } + + public abstract void resetViews(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index b36c2ef..5dcd61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -64,6 +64,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.DisplayMetrics; @@ -86,7 +87,6 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -121,10 +121,12 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.LocationControllerImpl; import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; +import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; +import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -196,8 +198,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, NetworkControllerImpl mNetworkController; RotationLockControllerImpl mRotationLockController; UserInfoController mUserInfoController; - ZenModeControllerImpl mZenModeController; + ZenModeController mZenModeController; CastControllerImpl mCastController; + VolumeComponent mVolumeComponent; int mNaturalBarHeight = -1; int mIconSize = -1; @@ -209,6 +212,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private StatusBarWindowManager mStatusBarWindowManager; + private UnlockMethodCache mUnlockMethodCache; int mPixelFormat; Object mQueueLock = new Object(); @@ -523,6 +527,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, mHeadsUpObserver); } + mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); startKeyguard(); } @@ -684,7 +689,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mRotationLockController = new RotationLockControllerImpl(mContext); } mUserInfoController = new UserInfoController(mContext); - mZenModeController = new ZenModeControllerImpl(mContext, mHandler); + mVolumeComponent = getComponent(VolumeComponent.class); + mZenModeController = mVolumeComponent.getZenController(); mCastController = new CastControllerImpl(mContext); final SignalClusterView signalCluster = (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster); @@ -747,7 +753,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final QSTileHost qsh = new QSTileHost(mContext, this, mBluetoothController, mLocationController, mRotationLockController, mNetworkController, mZenModeController, null /*tethering*/, - mCastController); + mCastController, mVolumeComponent); for (QSTile<?> tile : qsh.getTiles()) { mQSPanel.addTile(tile); } @@ -884,7 +890,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { + View.OnTouchListener mHomeActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -919,8 +925,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); - mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener); - mNavigationBarView.getSearchLight().setOnTouchListener(mHomeSearchActionListener); + mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); updateSearchPanel(); } @@ -1033,13 +1038,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore()); Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification)) { + // Forward the ranking so we can sort the new notification. + mNotificationData.updateRanking(ranking); return; } if (mUseHeadsUp && shouldInterrupt(notification)) { @@ -1079,7 +1086,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, tick(notification, true); } } - addNotificationViews(shadeEntry); + addNotificationViews(shadeEntry, ranking); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); @@ -1095,14 +1102,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void updateNotification(StatusBarNotification notification) { - super.updateNotification(notification); + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { + super.updateNotificationInternal(notification, ranking); mIntercepted.update(notification); } @Override - public void removeNotificationInternal(String key) { - StatusBarNotification old = removeNotificationViews(key); + public void removeNotificationInternal(String key, Ranking ranking) { + StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { @@ -1139,7 +1146,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.integer.config_show_search_delay); } - private void loadNotificationShade() { + private void updateNotificationShade() { if (mStackScroller == null) return; int N = mNotificationData.size(); @@ -1149,7 +1156,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!(provisioned || showNotificationEvenIfUnprovisioned(ent.notification))) continue; // TODO How do we want to badge notifcations from profiles. @@ -1180,26 +1187,75 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { - mStackScroller.addView(v, i); + mStackScroller.addView(v); } } + // So after all this work notifications still aren't sorted correctly. + // Let's do that now by advancing through toShow and mStackScroller in + // lock-step, making sure mStackScroller matches what we see in toShow. + int j = 0; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View child = mStackScroller.getChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + if (child == toShow.get(j)) { + // Everything is well, advance both lists. + j++; + continue; + } + + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(toShow.get(j), i); + j++; + } + updateRowStates(); + updateSpeedbump(); mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup); } + private void updateSpeedbump() { + int speedbumpIndex = -1; + int currentIndex = 0; + for (int i = 0; i < mNotificationData.size(); i++) { + Entry entry = mNotificationData.get(i); + if (entry.row.getParent() == null) { + // This view isn't even added, so the stack scroller doesn't + // know about it. Ignore completely. + continue; + } + if (entry.row.getVisibility() != View.GONE && + mNotificationData.isAmbient(entry.key)) { + speedbumpIndex = currentIndex; + break; + } + currentIndex++; + } + mStackScroller.updateSpeedBumpIndex(speedbumpIndex); + } + @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { + // TODO: Move this into updateNotificationIcons()? if (mNotificationIcons == null) return; - loadNotificationShade(); + updateNotificationShade(); + updateNotificationIcons(); + } + private void updateNotificationIcons() { final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); int N = mNotificationData.size(); if (DEBUG) { - Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); + Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + + mNotificationIcons); } ArrayList<View> toShow = new ArrayList<View>(); @@ -1207,7 +1263,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(ent.notification))) continue; if (!notificationIsForCurrentProfiles(ent.notification)) continue; @@ -2384,7 +2440,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void userSwitched(int newUserId) { if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); - updateNotificationIcons(); + updateNotifications(); resetUserSetupObserver(); } @@ -2770,7 +2826,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardStatusView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); - mNotificationPanel.closeQs(); + mNotificationPanel.resetViews(); } else { mKeyguardStatusView.setVisibility(View.GONE); mKeyguardIndicationTextView.setVisibility(View.GONE); @@ -2789,10 +2845,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(); updatePublicMode(); - updateRowStates(); - updateSpeedBump(); + updateNotifications(); checkBarModes(); - updateNotificationIcons(); updateCarrierLabelVisibility(false); } @@ -2882,10 +2936,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - public void onTrackingStopped() { + public void onTrackingStopped(boolean expand) { if (mState == StatusBarState.KEYGUARD) { mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); } + if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { + if (!expand && !mUnlockMethodCache.isMethodInsecure()) { + showBouncer(); + } + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 5fdf5bf..910d88c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -148,9 +148,15 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public void onTrackingStopped(PanelView panel) { - super.onTrackingStopped(panel); - mBar.onTrackingStopped(); + public void onTrackingStopped(PanelView panel, boolean expand) { + super.onTrackingStopped(panel, expand); + mBar.onTrackingStopped(expand); + } + + @Override + public void onExpandingFinished() { + super.onExpandingFinished(); + mScrimController.onExpandingFinished(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 7029898..1344703 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.os.HandlerThread; import android.os.Looper; +import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.tiles.AirplaneModeTile; import com.android.systemui.qs.tiles.BluetoothTile; @@ -29,11 +30,10 @@ import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.LocationTile; -import com.android.systemui.qs.tiles.RingerModeTile; +import com.android.systemui.qs.tiles.NotificationsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.HotspotTile; import com.android.systemui.qs.tiles.WifiTile; -import com.android.systemui.qs.tiles.ZenModeTile; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.TetheringController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; import java.util.ArrayList; import java.util.List; @@ -60,13 +61,15 @@ public class QSTileHost implements QSTile.Host { private final CastController mCast; private final Looper mLooper; private final CurrentUserTracker mUserTracker; + private final VolumeComponent mVolume; private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>(); + private final int mFeedbackStartDelay; public QSTileHost(Context context, PhoneStatusBar statusBar, BluetoothController bluetooth, LocationController location, RotationLockController rotation, NetworkController network, ZenModeController zen, TetheringController tethering, - CastController cast) { + CastController cast, VolumeComponent volume) { mContext = context; mStatusBar = statusBar; mBluetooth = bluetooth; @@ -76,6 +79,7 @@ public class QSTileHost implements QSTile.Host { mZen = zen; mTethering = tethering; mCast = cast; + mVolume = volume; final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName()); ht.start(); @@ -86,8 +90,7 @@ public class QSTileHost implements QSTile.Host { mTiles.add(new ColorInversionTile(this)); mTiles.add(new CellularTile(this)); mTiles.add(new AirplaneModeTile(this)); - mTiles.add(new ZenModeTile(this)); - mTiles.add(new RingerModeTile(this)); + mTiles.add(new NotificationsTile(this)); mTiles.add(new RotationLockTile(this)); mTiles.add(new LocationTile(this)); mTiles.add(new CastTile(this)); @@ -103,6 +106,7 @@ public class QSTileHost implements QSTile.Host { } }; mUserTracker.startTracking(); + mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay); } @Override @@ -112,7 +116,7 @@ public class QSTileHost implements QSTile.Host { @Override public void startSettingsActivity(final Intent intent) { - mStatusBar.postStartSettingsActivity(intent, QSTile.FEEDBACK_START_DELAY); + mStatusBar.postStartSettingsActivity(intent, mFeedbackStartDelay); } @Override @@ -169,4 +173,9 @@ public class QSTileHost implements QSTile.Host { public CastController getCastController() { return mCast; } + + @Override + public VolumeComponent getVolumeComponent() { + return mVolume; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 95255d5..6156fc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -52,6 +52,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private boolean mBouncerShowing; private boolean mAnimateChange; private boolean mUpdatePending; + private boolean mExpanding; private final Interpolator mInterpolator = new DecelerateInterpolator(); @@ -67,9 +68,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } public void onTrackingStarted() { + mExpanding = true; mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure(); } + public void onExpandingFinished() { + mExpanding = false; + } + public void setPanelExpansion(float fraction) { mFraction = fraction; scheduleUpdate(); @@ -77,7 +83,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { public void setBouncerShowing(boolean showing) { mBouncerShowing = showing; - mAnimateChange = true; + mAnimateChange = !mExpanding; scheduleUpdate(); } @@ -98,14 +104,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } private void updateScrimKeyguard() { - if (mBouncerShowing) { - setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); - setScrimBehindColor(0f); - } else if (mDarkenWhileDragging) { + if (mExpanding && mDarkenWhileDragging) { float behindFraction = Math.max(0, Math.min(mFraction, 1)); float fraction = 1 - behindFraction; setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA); setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD); + } else if (mBouncerShowing) { + setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); + setScrimBehindColor(0f); } else { setScrimInFrontColor(0f); setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c3430c3..d5551b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -102,6 +102,7 @@ public class StatusBarKeyguardViewManager { } else { mPhoneStatusBar.showKeyguard(); mBouncer.hide(false /* destroyView */); + mBouncer.prepare(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java deleted file mode 100644 index 049c5fc..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.Button; - -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.KeyButtonView; - -/** - * A swipeable button for affordances on the lockscreen. This is used for the camera and phone - * affordance. - */ -public class SwipeAffordanceView extends KeyButtonView { - - private static final int SWIPE_DIRECTION_START = 0; - private static final int SWIPE_DIRECTION_END = 1; - - private static final int SWIPE_DIRECTION_LEFT = 0; - private static final int SWIPE_DIRECTION_RIGHT = 1; - - private AffordanceListener mListener; - private int mScaledTouchSlop; - private float mDragDistance; - private int mResolvedSwipeDirection; - private int mSwipeDirection; - - public SwipeAffordanceView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SwipeAffordanceView, - 0, 0); - try { - mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0); - } finally { - a.recycle(); - } - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - if (!isLayoutRtl()) { - mResolvedSwipeDirection = mSwipeDirection; - } else { - mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START - ? SWIPE_DIRECTION_RIGHT - : SWIPE_DIRECTION_LEFT; - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance); - mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - } - - public void enableAccessibility(boolean touchExplorationEnabled) { - - // Add a touch handler or accessibility click listener for camera button. - if (touchExplorationEnabled) { - setOnTouchListener(null); - setOnClickListener(mClickListener); - } else { - setOnTouchListener(mTouchListener); - setOnClickListener(null); - } - } - - public void setAffordanceListener(AffordanceListener listener) { - mListener = listener; - } - - private void onActionPerformed() { - if (mListener != null) { - mListener.onActionPerformed(this); - } - } - - private void onUserActivity(long when) { - if (mListener != null) { - mListener.onUserActivity(when); - } - } - - private final OnClickListener mClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - onActionPerformed(); - } - }; - - private final OnTouchListener mTouchListener = new OnTouchListener() { - private float mStartX; - private boolean mTouchSlopReached; - private boolean mSkipCancelAnimation; - - @Override - public boolean onTouch(final View view, MotionEvent event) { - float realX = event.getRawX(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mStartX = realX; - mTouchSlopReached = false; - mSkipCancelAnimation = false; - break; - case MotionEvent.ACTION_MOVE: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX > mStartX - : realX < mStartX) { - realX = mStartX; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - view.setPressed(true); - onUserActivity(event.getEventTime()); - } else { - view.setPressed(false); - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? Math.max(realX - mStartX, -mDragDistance) - : Math.min(realX - mStartX, mDragDistance)); - break; - case MotionEvent.ACTION_UP: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - onActionPerformed(); - view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -view.getWidth() - : ((View) view.getParent()).getWidth() + view.getWidth()) - .setInterpolator(new AccelerateInterpolator(2f)).withEndAction( - new Runnable() { - @Override - public void run() { - view.setTranslationX(0); - } - }); - mSkipCancelAnimation = true; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - if (!mTouchSlopReached) { - mSkipCancelAnimation = true; - view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -mDragDistance / 2 - : mDragDistance / 2). - setInterpolator(new DecelerateInterpolator()).withEndAction( - new Runnable() { - @Override - public void run() { - view.animate().translationX(0). - setInterpolator(new AccelerateInterpolator()); - } - }); - } - case MotionEvent.ACTION_CANCEL: - view.setPressed(false); - if (!mSkipCancelAnimation) { - view.animate().translationX(0) - .setInterpolator(new AccelerateInterpolator(2f)); - } - break; - } - return true; - } - }; - - public interface AffordanceListener { - - /** - * Called when the view would like to report user activity. - * - * @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time - * base. - */ - void onUserActivity(long when); - - /** - * Called when the action of the affordance has been performed. - */ - void onActionPerformed(SwipeAffordanceView view); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 966c0b0..56402a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -339,7 +339,7 @@ public class NetworkControllerImpl extends BroadcastReceiver boolean wifiOut = wifiEnabled && mWifiSsid != null && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT || mWifiActivity == WifiManager.DATA_ACTIVITY_OUT); - cb.onWifiSignalChanged(wifiEnabled, mQSWifiIconId, wifiIn, wifiOut, + cb.onWifiSignalChanged(mWifiEnabled, mQSWifiIconId, wifiIn, wifiOut, mContentDescriptionWifi, wifiDesc); boolean mobileIn = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 5e2d06b..cf56fa57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -27,6 +27,7 @@ public class AnimationFilter { boolean animateZ; boolean animateScale; boolean animateHeight; + boolean animateTopInset; boolean animateDimmed; boolean hasDelays; @@ -60,6 +61,11 @@ public class AnimationFilter { return this; } + public AnimationFilter animateTopInset() { + animateTopInset = true; + return this; + } + public AnimationFilter animateDimmed() { animateDimmed = true; return this; @@ -84,6 +90,7 @@ public class AnimationFilter { animateZ |= filter.animateZ; animateScale |= filter.animateScale; animateHeight |= filter.animateHeight; + animateTopInset |= filter.animateTopInset; animateDimmed |= filter.animateDimmed; hasDelays |= filter.hasDelays; } @@ -94,6 +101,7 @@ public class AnimationFilter { animateZ = false; animateScale = false; animateHeight = false; + animateTopInset = false; animateDimmed = false; hasDelays = false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 079b184..58176b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1599,6 +1599,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1607,6 +1608,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1615,6 +1617,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1623,6 +1626,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateDimmed() .animateScale() @@ -1651,6 +1655,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index bd2541a..2b52c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -49,6 +49,7 @@ public class StackScrollAlgorithm { private int mBottomStackPeekSize; private int mZDistanceBetweenElements; private int mZBasicHeight; + private int mRoundedRectCornerRadius; private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; @@ -111,6 +112,8 @@ public class StackScrollAlgorithm { mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; mBottomStackSlowDownLength = context.getResources() .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); + mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } @@ -146,6 +149,67 @@ public class StackScrollAlgorithm { handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivated(ambientState, resultState, algorithmState); + updateClipping(resultState, algorithmState); + } + + private void updateClipping(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + float previousNotificationEnd = 0; + float previousNotificationStart = 0; + boolean previousNotificationIsSwiped = false; + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + StackScrollState.ViewState state = resultState.getViewStateForView(child); + float newYTranslation = state.yTranslation; + int newHeight = state.height; + // apply clipping and shadow + float newNotificationEnd = newYTranslation + newHeight; + + // In the unlocked shade we have to clip a little bit higher because of the rounded + // corners of the notifications. + float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; + + // When the previous notification is swiped, we don't clip the content to the + // bottom of it. + float clipHeight = previousNotificationIsSwiped + ? newHeight + : newNotificationEnd - (previousNotificationEnd - clippingCorrection); + + updateChildClippingAndBackground(state, newHeight, clipHeight, + (int) (newHeight - (previousNotificationStart - newYTranslation))); + + if (!child.isTransparent()) { + // Only update the previous values if we are not transparent, + // otherwise we would clip to a transparent view. + previousNotificationStart = newYTranslation + child.getClipTopAmount(); + previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; + } + } + } + + /** + * Updates the shadow outline and the clipping for a view. + * + * @param state the viewState to update + * @param realHeight the currently applied height of the view + * @param clipHeight the desired clip height, the rest of the view will be clipped from the top + * @param backgroundHeight the desired background height. The shadows of the view will be + * based on this height and the content will be clipped from the top + */ + private void updateChildClippingAndBackground(StackScrollState.ViewState state, int realHeight, + float clipHeight, int backgroundHeight) { + if (realHeight > clipHeight) { + state.topOverLap = (int) (realHeight - clipHeight); + } else { + state.topOverLap = 0; + } + if (realHeight > backgroundHeight) { + state.clipTopAmount = (realHeight - backgroundHeight); + } else { + state.clipTopAmount = 0; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 44e10be..94cb16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.stack; -import android.graphics.Outline; import android.graphics.Rect; import android.util.Log; import android.view.View; @@ -37,15 +36,12 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private final int mRoundedRectCornerRadius; private Map<ExpandableView, ViewState> mStateMap; private final Rect mClipRect = new Rect(); public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); - mRoundedRectCornerRadius = mHostView.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } public ViewGroup getHostView() { @@ -83,9 +79,6 @@ public class StackScrollState { */ public void apply() { int numChildren = mHostView.getChildCount(); - float previousNotificationEnd = 0; - float previousNotificationStart = 0; - boolean previousNotificationIsSwiped = false; for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState state = mStateMap.get(child); @@ -155,39 +148,41 @@ public class StackScrollState { // apply dimming child.setDimmed(state.dimmed, false /* animate */); - // apply clipping and shadow - float newNotificationEnd = newYTranslation + newHeight; - - // In the unlocked shade we have to clip a little bit higher because of the rounded - // corners of the notifications. - float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; - - // When the previous notification is swiped, we don't clip the content to the - // bottom of it. - float clipHeight = previousNotificationIsSwiped - ? newHeight - : newNotificationEnd - (previousNotificationEnd - clippingCorrection); - - updateChildClippingAndBackground(child, newHeight, - clipHeight, - (int) (newHeight - (previousNotificationStart - newYTranslation))); - - if (!child.isTransparent()) { - // Only update the previous values if we are not transparent, - // otherwise we would clip to a transparent view. - previousNotificationStart = newYTranslation + child.getClipTopAmount(); - previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + float oldClipTopAmount = child.getClipTopAmount(); + if (oldClipTopAmount != state.clipTopAmount) { + child.setClipTopAmount(state.clipTopAmount); + } + + if (state.topOverLap != 0) { + updateChildClip(child, newHeight, state.topOverLap); + } else { + child.setClipBounds(null); } if(child instanceof SpeedBumpView) { - performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd, + float speedBumpEnd = newYTranslation + newHeight; + performSpeedBumpAnimation(i, (SpeedBumpView) child, speedBumpEnd, newYTranslation); } } } } + /** + * Updates the clipping of a view + * + * @param child the view to update + * @param height the currently applied height of the view + * @param clipInset how much should this view be clipped from the top + */ + private void updateChildClip(View child, int height, int clipInset) { + mClipRect.set(0, + clipInset, + child.getWidth(), + height); + child.setClipBounds(mClipRect); + } + private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd, float speedBumpStart) { View nextChild = getNextChildNotGone(i); @@ -216,45 +211,6 @@ public class StackScrollState { return null; } - /** - * Updates the shadow outline and the clipping for a view. - * - * @param child the view to update - * @param realHeight the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - * @param backgroundHeight the desired background height. The shadows of the view will be - * based on this height and the content will be clipped from the top - */ - private void updateChildClippingAndBackground(ExpandableView child, int realHeight, - float clipHeight, int backgroundHeight) { - if (realHeight > clipHeight) { - updateChildClip(child, realHeight, clipHeight); - } else { - child.setClipBounds(null); - } - if (realHeight > backgroundHeight) { - child.setClipTopAmount(realHeight - backgroundHeight); - } else { - child.setClipTopAmount(0); - } - } - - /** - * Updates the clipping of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - */ - private void updateChildClip(View child, int height, float clipHeight) { - int clipInset = (int) (height - clipHeight); - mClipRect.set(0, - clipInset, - child.getWidth(), - height); - child.setClipBounds(mClipRect); - } - public static class ViewState { // These are flags such that we can create masks for filtering. @@ -276,6 +232,18 @@ public class StackScrollState { boolean dimmed; /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + int topOverLap; + + /** * The index of the view, only accounting for views not equal to GONE */ int notGoneIndex; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index f019e6c..f41ab3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -133,10 +133,11 @@ public class StackStateAnimator { boolean scaleChanging = child.getScaleX() != viewState.scale; boolean alphaChanging = alpha != child.getAlpha(); boolean heightChanging = viewState.height != child.getActualHeight(); + boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || - alphaChanging || heightChanging; + alphaChanging || heightChanging || topInsetChanging; long delay = 0; if (hasDelays && isDelayRelevant || wasAdded) { delay = calculateChildAnimationDelay(viewState, finalState); @@ -167,6 +168,11 @@ public class StackStateAnimator { startHeightAnimation(child, viewState, delay); } + // start top inset animation + if (topInsetChanging) { + startInsetAnimation(child, viewState, delay); + } + // start dimmed animation child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); @@ -280,6 +286,64 @@ public class StackStateAnimator { child.setTag(TAG_END_HEIGHT, newEndValue); } + private void startInsetAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, long delay) { + Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); + Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); + int newEndValue = viewState.clipTopAmount; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); + if (!mAnimationFilter.animateTopInset) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_TOP_INSET, newStartValue); + child.setTag(TAG_END_TOP_INSET, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setClipTopAmount(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setClipTopAmount((int) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(mFastOutSlowInInterpolator); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); + animator.setDuration(newDuration); + if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { + animator.setStartDelay(delay); + } + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TOP_INSET, null); + child.setTag(TAG_START_TOP_INSET, null); + child.setTag(TAG_END_TOP_INSET, null); + } + }); + startAnimator(animator); + child.setTag(TAG_ANIMATOR_TOP_INSET, animator); + child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); + child.setTag(TAG_END_TOP_INSET, newEndValue); + } + private void startAlphaAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 25147b4..c2bd1cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -17,12 +17,12 @@ package com.android.systemui.statusbar.tv; import android.os.IBinder; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; -import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.BaseStatusBar; @@ -50,7 +50,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { } @Override @@ -58,7 +58,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void removeNotificationInternal(String key) { + protected void removeNotificationInternal(String key, Ranking ranking) { } @Override @@ -117,7 +117,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java new file mode 100644 index 0000000..5ee8925 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import com.android.systemui.statusbar.policy.ZenModeController; + +public interface VolumeComponent { + ZenModeController getZenController(); + void setVolumePanel(VolumePanel panel); +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index 8657e07..67f3a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -16,14 +16,12 @@ package com.android.systemui.volume; -import com.android.internal.R; - import android.app.AlertDialog; import android.app.Dialog; -import android.content.DialogInterface.OnDismissListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; @@ -42,6 +40,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; @@ -49,6 +48,9 @@ import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; +import com.android.internal.R; +import com.android.systemui.statusbar.policy.ZenModeController; + import java.util.HashMap; /** @@ -57,7 +59,6 @@ import java.util.HashMap; * @hide */ public class VolumePanel extends Handler { - private static final String TAG = VolumePanel.class.getSimpleName(); private static boolean LOGD = false; private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY; @@ -88,33 +89,48 @@ public class VolumePanel extends Handler { private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; + private static final int MSG_LAYOUT_DIRECTION = 12; + private static final int MSG_ZEN_MODE_CHANGED = 13; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC + private final String mTag; protected final Context mContext; private final AudioManager mAudioManager; + private final ZenModeController mZenController; private boolean mRingIsSilent; - private boolean mShowCombinedVolumes; private boolean mVoiceCapable; + private boolean mZenModeCapable; // True if we want to play tones on the system stream when the master stream is specified. private final boolean mPlayMasterStreamTones; - /** Dialog containing all the sliders */ - private final Dialog mDialog; - /** Dialog's content view */ + + /** Volume panel content view */ private final View mView; + /** Dialog hosting the panel, if not embedded */ + private final Dialog mDialog; + /** Parent view hosting the panel, if embedded */ + private final ViewGroup mParent; /** The visible portion of the volume overlay */ private final ViewGroup mPanel; - /** Contains the sliders and their touchable icons */ - private final ViewGroup mSliderGroup; - /** The button that expands the dialog to show all sliders */ - private final View mMoreButton; - /** Dummy divider icon that needs to vanish with the more button */ - private final View mDivider; + /** Contains the slider and its touchable icons */ + private final ViewGroup mSliderPanel; + /** The button that expands the dialog to show the zen panel */ + private final ImageView mExpandButton; + /** Dummy divider icon that needs to vanish with the expand button */ + private final View mExpandDivider; + /** The zen mode configuration panel view stub */ + private final ViewStub mZenPanelStub; + /** The zen mode configuration panel view, once inflated */ + private ZenModePanel mZenPanel; + /** Dummy divider icon that needs to vanish with the zen panel */ + private final View mZenPanelDivider; + + private ZenModePanel.Callback mZenPanelCallback; /** Currently active stream that shows up at the top of the list of sliders */ private int mActiveStreamType = -1; @@ -129,8 +145,8 @@ public class VolumePanel extends Handler { false), RingerStream(AudioManager.STREAM_RING, R.string.volume_icon_description_ringer, - R.drawable.ic_audio_ring_notif, - R.drawable.ic_audio_ring_notif_mute, + com.android.systemui.R.drawable.ic_ringer_audible, + com.android.systemui.R.drawable.ic_ringer_silent, false), VoiceStream(AudioManager.STREAM_VOICE_CALL, R.string.volume_icon_description_incall, @@ -149,8 +165,8 @@ public class VolumePanel extends Handler { true), NotificationStream(AudioManager.STREAM_NOTIFICATION, R.string.volume_icon_description_notification, - R.drawable.ic_audio_notification, - R.drawable.ic_audio_notification_mute, + com.android.systemui.R.drawable.ic_ringer_audible, + com.android.systemui.R.drawable.ic_ringer_silent, true), // for now, use media resources for master volume MasterStream(STREAM_MASTER, @@ -245,8 +261,11 @@ public class VolumePanel extends Handler { } - public VolumePanel(Context context) { + public VolumePanel(Context context, ViewGroup parent, ZenModeController zenController) { + mTag = String.format("VolumePanel%s.%08x", parent == null ? "Dialog" : "", hashCode()); mContext = context; + mParent = parent; + mZenController = zenController; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // For now, only show master volume if master volume is supported @@ -258,74 +277,81 @@ public class VolumePanel extends Handler { streamRes.show = (streamRes.streamType == STREAM_MASTER); } } - - mDialog = new Dialog(context) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && - sConfirmSafeVolumeDialog == null) { - forceTimeout(); - return true; + if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null)); + final int layoutId = com.android.systemui.R.layout.volume_panel; + if (parent == null) { + // dialog mode + mDialog = new Dialog(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && + sConfirmSafeVolumeDialog == null) { + forceTimeout(); + return true; + } + return false; } - return false; - } - }; - - // Change some window properties - final Window window = mDialog.getWindow(); - final LayoutParams lp = window.getAttributes(); - lp.token = null; - // Offset from the top - lp.y = res.getDimensionPixelOffset(R.dimen.volume_panel_top); - lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; - lp.windowAnimations = R.style.Animation_VolumePanel; - window.setAttributes(lp); - window.setGravity(Gravity.TOP); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.requestFeature(Window.FEATURE_NO_TITLE); - window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE - | LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); - - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(R.layout.volume_adjust); - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mActiveStreamType = -1; - mAudioManager.forceVolumeControlStream(mActiveStreamType); - } - }); + }; + + // Change some window properties + final Window window = mDialog.getWindow(); + final LayoutParams lp = window.getAttributes(); + lp.token = null; + // Offset from the top + lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); + lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_width); + lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; + lp.windowAnimations = R.style.Animation_VolumePanel; + window.setBackgroundDrawableResource(com.android.systemui.R.drawable.qs_panel_background); + window.setAttributes(lp); + window.setGravity(Gravity.TOP); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE + | LayoutParams.FLAG_NOT_TOUCH_MODAL + | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + mDialog.setCanceledOnTouchOutside(true); + mDialog.setContentView(layoutId); + mDialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + mActiveStreamType = -1; + mAudioManager.forceVolumeControlStream(mActiveStreamType); + } + }); - mDialog.create(); + mDialog.create(); - mView = window.findViewById(R.id.content); - mView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - resetTimeout(); - return false; - } - }); + mView = window.findViewById(R.id.content); + mView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + resetTimeout(); + return false; + } + }); - mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel); - mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group); - mMoreButton = mView.findViewById(R.id.expand_button); - mDivider = mView.findViewById(R.id.expand_button_divider); + } else { + // embedded mode + mDialog = null; + mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true); + } + mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); + mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); + mExpandButton = (ImageView) mView.findViewById(com.android.systemui.R.id.expand_button); + mExpandDivider = mView.findViewById(com.android.systemui.R.id.expand_button_divider); + mZenPanelStub = (ViewStub)mView.findViewById(com.android.systemui.R.id.zen_panel_stub); + mZenPanelDivider = mView.findViewById(com.android.systemui.R.id.zen_panel_divider); mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); - // If we don't want to show multiple volumes, hide the settings button - // and divider. - mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume; - if (!mShowCombinedVolumes) { - mMoreButton.setVisibility(View.GONE); - mDivider.setVisibility(View.GONE); - } else { - mMoreButton.setOnClickListener(mClickListener); - } + mZenModeCapable = !useMasterVolume && mZenController != null; + mZenPanelDivider.setVisibility(View.GONE); + mExpandButton.setOnClickListener(mClickListener); + updateZenMode(mZenController == null ? false : mZenController.isZen()); + mZenController.addCallback(mZenCallback); final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume); final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds); @@ -334,7 +360,7 @@ public class VolumePanel extends Handler { listenToRingerMode(); } - public void setLayoutDirection(int layoutDirection) { + private void setLayoutDirection(int layoutDirection) { mPanel.setLayoutDirection(layoutDirection); updateStates(); } @@ -406,21 +432,19 @@ public class VolumePanel extends Handler { StreamResources streamRes = STREAMS[i]; final int streamType = streamRes.streamType; - if (mVoiceCapable && streamRes == StreamResources.NotificationStream) { - streamRes = StreamResources.RingerStream; - } final StreamControl sc = new StreamControl(); sc.streamType = streamType; - sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null); + sc.group = (ViewGroup) inflater.inflate( + com.android.systemui.R.layout.volume_panel_item, null); sc.group.setTag(sc); - sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon); + sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon); sc.icon.setTag(sc); sc.icon.setContentDescription(res.getString(streamRes.descRes)); sc.iconRes = streamRes.iconRes; sc.iconMuteRes = streamRes.iconMuteRes; sc.icon.setImageResource(sc.iconRes); - sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar); + sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); @@ -431,34 +455,18 @@ public class VolumePanel extends Handler { } private void reorderSliders(int activeStreamType) { - mSliderGroup.removeAllViews(); + mSliderPanel.removeAllViews(); final StreamControl active = mStreamControls.get(activeStreamType); if (active == null) { Log.e("VolumePanel", "Missing stream type! - " + activeStreamType); mActiveStreamType = -1; } else { - mSliderGroup.addView(active.group); + mSliderPanel.addView(active.group); mActiveStreamType = activeStreamType; active.group.setVisibility(View.VISIBLE); updateSlider(active); - } - - addOtherVolumes(); - } - - private void addOtherVolumes() { - if (!mShowCombinedVolumes) return; - - for (int i = 0; i < STREAMS.length; i++) { - // Skip the phone specific ones and the active one - final int streamType = STREAMS[i].streamType; - if (!STREAMS[i].show || streamType == mActiveStreamType) { - continue; - } - StreamControl sc = mStreamControls.get(streamType); - mSliderGroup.addView(sc.group); - updateSlider(sc); + updateZenMode(mZenController == null ? false : mZenController.isZen()); } } @@ -472,7 +480,7 @@ public class VolumePanel extends Handler { if (((sc.streamType == AudioManager.STREAM_RING) || (sc.streamType == AudioManager.STREAM_NOTIFICATION)) && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { - sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); + sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate); } if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { // never disable touch interactions for remote playback, the muting is not tied to @@ -486,32 +494,70 @@ public class VolumePanel extends Handler { } } + public void setZenModePanelCallback(ZenModePanel.Callback callback) { + mZenPanelCallback = callback; + } + private void expand() { - final int count = mSliderGroup.getChildCount(); - for (int i = 0; i < count; i++) { - mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE); + if (LOGD) Log.d(mTag, "expand mZenPanel=" + mZenPanel); + if (mZenPanel == null) { + mZenPanel = (ZenModePanel) mZenPanelStub.inflate(); + mZenPanel.init(mZenController); + mZenPanel.setCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + if (mZenPanelCallback != null) { + mZenPanelCallback.onMoreSettings(); + } + } + + @Override + public void onInteraction() { + if (mZenPanelCallback != null) { + mZenPanelCallback.onInteraction(); + } + } + }); } - mMoreButton.setVisibility(View.INVISIBLE); - mDivider.setVisibility(View.INVISIBLE); + mZenPanel.setVisibility(View.VISIBLE); + mZenPanelDivider.setVisibility(View.VISIBLE); } private void collapse() { - mMoreButton.setVisibility(View.VISIBLE); - mDivider.setVisibility(View.VISIBLE); - final int count = mSliderGroup.getChildCount(); - for (int i = 1; i < count; i++) { - mSliderGroup.getChildAt(i).setVisibility(View.GONE); + if (LOGD) Log.d(mTag, "collapse mZenPanel=" + mZenPanel); + if (mZenPanel != null) { + mZenPanel.setVisibility(View.GONE); } + mZenPanelDivider.setVisibility(View.GONE); } public void updateStates() { - final int count = mSliderGroup.getChildCount(); + final int count = mSliderPanel.getChildCount(); for (int i = 0; i < count; i++) { - StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag(); + StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag(); updateSlider(sc); } } + private void updateZenMode(boolean zen) { + if (mZenModeCapable) { + final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION + || mActiveStreamType == AudioManager.STREAM_RING; + mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE); + mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE); + mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on + : com.android.systemui.R.drawable.ic_vol_zen_off); + } else { + mExpandButton.setVisibility(View.GONE); + mExpandDivider.setVisibility(View.GONE); + } + } + + public void postZenModeChanged(boolean zen) { + removeMessages(MSG_ZEN_MODE_CHANGED); + obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget(); + } + public void postVolumeChanged(int streamType, int flags) { if (hasMessages(MSG_VOLUME_CHANGED)) return; synchronized (this) { @@ -582,8 +628,12 @@ public class VolumePanel extends Handler { } public void postDismiss() { - removeMessages(MSG_TIMEOUT); - sendEmptyMessage(MSG_TIMEOUT); + forceTimeout(); + } + + public void postLayoutDirection(int layoutDirection) { + removeMessages(MSG_LAYOUT_DIRECTION); + obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget(); } /** @@ -593,7 +643,7 @@ public class VolumePanel extends Handler { */ protected void onVolumeChanged(int streamType, int flags) { - if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { synchronized (this) { @@ -622,7 +672,7 @@ public class VolumePanel extends Handler { protected void onMuteChanged(int streamType, int flags) { - if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); StreamControl sc = mStreamControls.get(streamType); if (sc != null) { @@ -638,7 +688,7 @@ public class VolumePanel extends Handler { mRingIsSilent = false; if (LOGD) { - Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType + ", flags: " + flags + "), index: " + index); } @@ -707,7 +757,7 @@ public class VolumePanel extends Handler { } case AudioService.STREAM_REMOTE_MUSIC: { - if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); } + if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); } break; } } @@ -730,16 +780,18 @@ public class VolumePanel extends Handler { } } - if (!mDialog.isShowing()) { + if (!isShowing()) { int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType; // when the stream is for remote playback, use -1 to reset the stream type evaluation mAudioManager.forceVolumeControlStream(stream); // Showing dialog - use collapsed state - if (mShowCombinedVolumes) { + if (mZenModeCapable) { collapse(); } - mDialog.show(); + if (mDialog != null) { + mDialog.show(); + } } // Do a little vibrate if applicable (only when going into vibrate mode) @@ -751,6 +803,10 @@ public class VolumePanel extends Handler { } } + private boolean isShowing() { + return mDialog != null ? mDialog.isShowing() : mParent.isAttachedToWindow(); + } + protected void onPlaySound(int streamType, int flags) { if (hasMessages(MSG_STOP_SOUNDS)) { @@ -795,9 +851,9 @@ public class VolumePanel extends Handler { // streamType is the real stream type being affected, but for the UI sliders, we // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real // stream type. - if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); - if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) { + if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) { synchronized (this) { if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) { reorderSliders(AudioService.STREAM_REMOTE_MUSIC); @@ -805,7 +861,7 @@ public class VolumePanel extends Handler { onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags); } } else { - if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); + if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); } if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { @@ -825,8 +881,8 @@ public class VolumePanel extends Handler { } protected void onRemoteVolumeUpdateIfShown() { - if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()"); - if (mDialog.isShowing() + if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()"); + if (isShowing() && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC) && (mStreamControls != null)) { onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0); @@ -842,7 +898,7 @@ public class VolumePanel extends Handler { * @param visible */ synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { - if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); + if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); boolean isVisible = (visible == 1); for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { StreamResources streamRes = STREAMS[i]; @@ -857,7 +913,7 @@ public class VolumePanel extends Handler { } protected void onDisplaySafeVolumeWarning(int flags) { - if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) { + if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || isShowing()) { synchronized (sConfirmSafeVolumeLock) { if (sConfirmSafeVolumeDialog != null) { return; @@ -907,7 +963,7 @@ public class VolumePanel extends Handler { mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); } catch (RuntimeException e) { if (LOGD) { - Log.d(TAG, "ToneGenerator constructor failed with " + Log.d(mTag, "ToneGenerator constructor failed with " + "RuntimeException: " + e); } } @@ -976,9 +1032,11 @@ public class VolumePanel extends Handler { } case MSG_TIMEOUT: { - if (mDialog.isShowing()) { - mDialog.dismiss(); - mActiveStreamType = -1; + if (isShowing()) { + if (mDialog != null) { + mDialog.dismiss(); + mActiveStreamType = -1; + } } synchronized (sConfirmSafeVolumeLock) { if (sConfirmSafeVolumeDialog != null) { @@ -988,7 +1046,7 @@ public class VolumePanel extends Handler { break; } case MSG_RINGER_MODE_CHANGED: { - if (mDialog.isShowing()) { + if (isShowing()) { updateStates(); } break; @@ -1010,17 +1068,30 @@ public class VolumePanel extends Handler { case MSG_DISPLAY_SAFE_VOLUME_WARNING: onDisplaySafeVolumeWarning(msg.arg1); break; + + case MSG_LAYOUT_DIRECTION: + setLayoutDirection(msg.arg1); + break; + + case MSG_ZEN_MODE_CHANGED: + updateZenMode(msg.arg1 != 0); + break; } } - private void resetTimeout() { + public void resetTimeout() { + if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()); removeMessages(MSG_TIMEOUT); - sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY); + sendEmptyMessageDelayed(MSG_TIMEOUT, TIMEOUT_DELAY); } private void forceTimeout() { removeMessages(MSG_TIMEOUT); - sendMessage(obtainMessage(MSG_TIMEOUT)); + sendEmptyMessage(MSG_TIMEOUT); + } + + public ZenModeController getZenController() { + return mZenController; } private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { @@ -1061,10 +1132,22 @@ public class VolumePanel extends Handler { private final View.OnClickListener mClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - if (v == mMoreButton) { - expand(); + if (v == mExpandButton && mZenController != null) { + final boolean newZen = !mZenController.isZen(); + mZenController.setZen(newZen); + if (newZen) { + expand(); + } else { + collapse(); + } } resetTimeout(); } }; + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + public void onZenChanged(boolean zen) { + updateZenMode(zen); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 9bd75b7..7da90d8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -1,16 +1,22 @@ package com.android.systemui.volume; import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioManager; import android.media.IVolumeController; import android.net.Uri; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import com.android.systemui.R; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.policy.ZenModeControllerImpl; /* * Copyright (C) 2014 The Android Open Source Project @@ -34,21 +40,21 @@ public class VolumeUI extends SystemUI { private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING); private static final int DEFAULT = 1; // enabled by default + private final Handler mHandler = new Handler(); private AudioManager mAudioManager; private VolumeController mVolumeController; @Override public void start() { mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mVolumeController = new VolumeController(mContext); + putComponent(VolumeComponent.class, mVolumeController); updateController(); mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver); } private void updateController() { if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) { - if (mVolumeController == null) { - mVolumeController = new VolumeController(mContext); - } Log.d(TAG, "Registering volume controller"); mAudioManager.setVolumeController(mVolumeController); } else { @@ -57,7 +63,7 @@ public class VolumeUI extends SystemUI { } } - private final ContentObserver mObserver = new ContentObserver(new Handler()) { + private final ContentObserver mObserver = new ContentObserver(mHandler) { public void onChange(boolean selfChange, Uri uri) { if (SETTING_URI.equals(uri)) { updateController(); @@ -66,13 +72,38 @@ public class VolumeUI extends SystemUI { }; /** For now, simply host an unmodified base volume panel in this process. */ - private final class VolumeController extends IVolumeController.Stub { - private final VolumePanel mPanel; + private final class VolumeController extends IVolumeController.Stub implements VolumeComponent { + private final VolumePanel mDialogPanel; + private VolumePanel mPanel; public VolumeController(Context context) { - mPanel = new VolumePanel(context); + mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler)); + final int delay = context.getResources().getInteger(R.integer.feedback_start_delay); + mPanel.setZenModePanelCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + mHandler.removeCallbacks(mStartZenSettings); + mHandler.postDelayed(mStartZenSettings, delay); + } + + @Override + public void onInteraction() { + mDialogPanel.resetTimeout(); + } + }); + mDialogPanel = mPanel; } + private final Runnable mStartZenSettings = new Runnable() { + @Override + public void run() { + mDialogPanel.postDismiss(); + final Intent intent = ZenModePanel.ZEN_SETTINGS; + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + } + }; + @Override public void hasNewRemotePlaybackInfo() throws RemoteException { mPanel.postHasNewRemotePlaybackInfo(); @@ -114,12 +145,22 @@ public class VolumeUI extends SystemUI { @Override public void setLayoutDirection(int layoutDirection) throws RemoteException { - mPanel.setLayoutDirection(layoutDirection); + mPanel.postLayoutDirection(layoutDirection); } @Override public void dismiss() throws RemoteException { mPanel.postDismiss(); } + + @Override + public ZenModeController getZenController() { + return mDialogPanel.getZenController(); + } + + @Override + public void setVolumePanel(VolumePanel panel) { + mPanel = panel == null ? mDialogPanel : panel; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java new file mode 100644 index 0000000..77d267e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.service.notification.Condition; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.util.Arrays; +import java.util.HashSet; + +public class ZenModePanel extends LinearLayout { + private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; + public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + + private final LayoutInflater mInflater; + private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>(); + private final H mHandler = new H(); + private LinearLayout mConditions; + private int mMinutesIndex = Arrays.binarySearch(MINUTES, 60); // default to one hour + private Callback mCallback; + private ZenModeController mController; + private boolean mRequestingConditions; + + public ZenModePanel(Context context, AttributeSet attrs) { + super(context, attrs); + mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mConditions = (LinearLayout) findViewById(android.R.id.content); + findViewById(android.R.id.button2).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + fireMoreSettings(); + } + }); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + setRequestingConditions(visibility == VISIBLE); + } + + /** Start or stop requesting relevant zen mode exit conditions */ + private void setRequestingConditions(boolean requesting) { + if (mRequestingConditions == requesting) return; + mRequestingConditions = requesting; + if (mRequestingConditions) { + mController.addCallback(mZenCallback); + } else { + mController.removeCallback(mZenCallback); + } + mController.requestConditions(mRequestingConditions); + } + + public void init(ZenModeController controller) { + mController = controller; + mConditions.removeAllViews(); + bind(updateTimeCondition(), mConditions.getChildAt(0)); + handleUpdateConditions(new Condition[0]); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + private Condition updateTimeCondition() { + final int minutes = MINUTES[mMinutesIndex]; + final long millis = System.currentTimeMillis() + minutes * 60 * 1000; + final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android") + .appendPath("countdown").appendPath(Long.toString(millis)).build(); + final int num = minutes < 60 ? minutes : minutes / 60; + final int resId = minutes < 60 + ? R.plurals.zen_mode_duration_minutes + : R.plurals.zen_mode_duration_hours; + final String caption = mContext.getResources().getQuantityString(resId, num, num); + return new Condition(id, caption, "", "", 0, Condition.STATE_TRUE, + Condition.FLAG_RELEVANT_NOW); + } + + private void handleUpdateConditions(Condition[] conditions) { + final int newCount = conditions == null ? 0 : conditions.length; + for (int i = mConditions.getChildCount() - 1; i > newCount; i--) { + mConditions.removeViewAt(i); + } + for (int i = 0; i < newCount; i++) { + bind(conditions[i], mConditions.getChildAt(i + 1)); + } + bind(null, mConditions.getChildAt(newCount + 1)); + } + + private void editTimeCondition(int delta) { + final int i = mMinutesIndex + delta; + if (i < 0 || i >= MINUTES.length) return; + mMinutesIndex = i; + final Condition c = updateTimeCondition(); + bind(c, mConditions.getChildAt(0)); + } + + private void bind(final Condition condition, View convertView) { + final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE; + final View row; + if (convertView == null) { + row = mInflater.inflate(R.layout.zen_mode_condition, this, false); + mConditions.addView(row); + } else { + row = convertView; + } + final int position = mConditions.indexOfChild(row); + final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox); + mRadioButtons.add(rb); + rb.setEnabled(enabled); + rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + for (RadioButton otherButton : mRadioButtons) { + if (otherButton == rb) continue; + otherButton.setChecked(false); + } + mController.select(condition); + fireInteraction(); + } + } + }); + final TextView title = (TextView) row.findViewById(android.R.id.title); + if (condition == null) { + title.setText(R.string.zen_mode_forever); + } else { + title.setText(condition.summary); + } + title.setEnabled(enabled); + title.setAlpha(enabled ? 1 : .5f); + final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); + button1.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + editTimeCondition(-1); + fireInteraction(); + } + }); + + final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); + button2.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + editTimeCondition(1); + fireInteraction(); + } + }); + title.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + fireInteraction(); + } + }); + if (position == 0) { + button1.setEnabled(mMinutesIndex > 0); + button2.setEnabled(mMinutesIndex < MINUTES.length - 1); + button1.setImageAlpha(button1.isEnabled() ? 0xff : 0x7f); + button2.setImageAlpha(button2.isEnabled() ? 0xff : 0x7f); + } else { + button1.setVisibility(View.GONE); + button2.setVisibility(View.GONE); + } + if (position == 0 && mConditions.getChildCount() == 1) { + rb.setChecked(true); + } + } + + private void fireMoreSettings() { + if (mCallback != null) { + mCallback.onMoreSettings(); + } + } + + private void fireInteraction() { + if (mCallback != null) { + mCallback.onInteraction(); + } + } + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + @Override + public void onConditionsChanged(Condition[] conditions) { + mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); + } + }; + + private final class H extends Handler { + private static final int UPDATE_CONDITIONS = 1; + + private H() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == UPDATE_CONDITIONS) { + handleUpdateConditions((Condition[])msg.obj); + } + } + } + + public interface Callback { + void onMoreSettings(); + void onInteraction(); + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 2fea785..6b0095a 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -3179,7 +3179,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { com.android.internal.R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { - layoutResource = com.android.internal.R.layout.screen_action_bar; + layoutResource = a.getResourceId( + com.android.internal.R.styleable.Window_windowActionBarFullscreenDecorLayout, + com.android.internal.R.layout.screen_action_bar); } else { layoutResource = com.android.internal.R.layout.screen_title; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index fb02ebe..0273016 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -66,6 +66,7 @@ import android.os.Vibrator; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.telephony.TelephonyManager; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -354,6 +355,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // the same as mCur*, but may be larger if the screen decor has supplied // content insets. int mContentLeft, mContentTop, mContentRight, mContentBottom; + // During layout, the frame in which voice content should be displayed + // to the user, accounting for all screen decoration except for any + // space they deem as available for other content. + int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom; // During layout, the current screen borders along which input method // windows are placed. int mDockLeft, mDockTop, mDockRight, mDockBottom; @@ -1265,6 +1270,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case TYPE_INPUT_METHOD: case TYPE_WALLPAPER: case TYPE_PRIVATE_PRESENTATION: + case TYPE_VOICE_INTERACTION: // The window manager will check these. break; case TYPE_PHONE: @@ -1431,74 +1437,77 @@ public class PhoneWindowManager implements WindowManagerPolicy { return 3; case TYPE_SEARCH_BAR: return 4; + case TYPE_VOICE_INTERACTION: + // voice interaction layer is almost immediately above apps. + return 5; case TYPE_RECENTS_OVERLAY: case TYPE_SYSTEM_DIALOG: - return 5; + return 6; case TYPE_TOAST: // toasts and the plugged-in battery thing - return 6; + return 7; case TYPE_PRIORITY_PHONE: // SIM errors and unlock. Not sure if this really should be in a high layer. - return 7; + return 8; case TYPE_DREAM: // used for Dreams (screensavers with TYPE_DREAM windows) - return 8; + return 9; case TYPE_SYSTEM_ALERT: // like the ANR / app crashed dialogs - return 9; + return 10; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. - return 10; + return 11; case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. - return 11; + return 12; case TYPE_KEYGUARD_SCRIM: // the safety window that shows behind keyguard while keyguard is starting - return 12; - case TYPE_STATUS_BAR_SUB_PANEL: return 13; - case TYPE_STATUS_BAR: + case TYPE_STATUS_BAR_SUB_PANEL: return 14; - case TYPE_STATUS_BAR_PANEL: + case TYPE_STATUS_BAR: return 15; - case TYPE_KEYGUARD_DIALOG: + case TYPE_STATUS_BAR_PANEL: return 16; + case TYPE_KEYGUARD_DIALOG: + return 17; case TYPE_VOLUME_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume - return 17; + return 18; case TYPE_SYSTEM_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume - return 18; + return 19; case TYPE_NAVIGATION_BAR: // the navigation bar, if available, shows atop most things - return 19; + return 20; case TYPE_NAVIGATION_BAR_PANEL: // some panels (e.g. search) need to show on top of the navigation bar - return 20; + return 21; case TYPE_SYSTEM_ERROR: // system-level error dialogs - return 21; + return 22; case TYPE_MAGNIFICATION_OVERLAY: // used to highlight the magnified portion of a display - return 22; + return 23; case TYPE_DISPLAY_OVERLAY: // used to simulate secondary display devices - return 23; + return 24; case TYPE_DRAG: // the drag layer: input for drag-and-drop is associated with this window, // which sits above all other focusable windows - return 24; - case TYPE_SECURE_SYSTEM_OVERLAY: return 25; - case TYPE_BOOT_PROGRESS: + case TYPE_SECURE_SYSTEM_OVERLAY: return 26; + case TYPE_BOOT_PROGRESS: + return 27; case TYPE_POINTER: // the (mouse) pointer layer - return 27; - case TYPE_HIDDEN_NAV_CONSUMER: return 28; + case TYPE_HIDDEN_NAV_CONSUMER: + return 29; } Log.e(TAG, "Unknown window type: " + type); return 2; @@ -1930,9 +1939,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { ServiceManager.checkService(DreamService.DREAM_SERVICE)); } - static ITelephony getTelephonyService() { - return ITelephony.Stub.asInterface( - ServiceManager.checkService(Context.TELEPHONY_SERVICE)); + TelephonyManager getTelephonyService() { + return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); } static IAudioService getAudioService() { @@ -2015,14 +2023,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If an incoming call is ringing, HOME is totally disabled. // (The user is already on the InCallScreen at this point, // and his ONLY options are to answer or reject the call.) - try { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null && telephonyService.isRinging()) { - Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); - return -1; - } - } catch (RemoteException ex) { - Log.w(TAG, "RemoteException from getPhoneInterface()", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null && telephonyManager.isRinging()) { + Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + return -1; } // Delay handling home if a double-tap is possible. @@ -2720,13 +2724,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mRestrictedScreenTop = mUnrestrictedScreenTop; mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth; mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight; - mDockLeft = mContentLeft = mStableLeft = mStableFullscreenLeft + mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft = mCurLeft = mUnrestrictedScreenLeft; - mDockTop = mContentTop = mStableTop = mStableFullscreenTop + mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop = mCurTop = mUnrestrictedScreenTop; - mDockRight = mContentRight = mStableRight = mStableFullscreenRight + mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight = mCurRight = displayWidth - overscanRight; - mDockBottom = mContentBottom = mStableBottom = mStableFullscreenBottom + mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom = mCurBottom = displayHeight - overscanBottom; mDockLayer = 0x10000000; mStatusBarLayer = -1; @@ -2837,10 +2841,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Make sure the content and current rectangles are updated to // account for the restrictions from the navigation bar. - mContentTop = mCurTop = mDockTop; - mContentBottom = mCurBottom = mDockBottom; - mContentLeft = mCurLeft = mDockLeft; - mContentRight = mCurRight = mDockRight; + mContentTop = mVoiceContentTop = mCurTop = mDockTop; + mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom; + mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft; + mContentRight = mVoiceContentRight = mCurRight = mDockRight; mStatusBarLayer = mNavigationBar.getSurfaceLayer(); // And compute the final frame. mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, @@ -2887,10 +2891,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // status bar is visible. mDockTop = mUnrestrictedScreenTop + mStatusBarHeight; - mContentTop = mCurTop = mDockTop; - mContentBottom = mCurBottom = mDockBottom; - mContentLeft = mCurLeft = mDockLeft; - mContentRight = mCurRight = mDockRight; + mContentTop = mVoiceContentTop = mCurTop = mDockTop; + mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom; + mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft; + mContentRight = mVoiceContentRight = mCurRight = mDockRight; if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format( @@ -2961,7 +2965,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Ungh. So to deal with that, make sure the content frame // we end up using is not covering the IM dock. cf.set(attached.getContentFrameLw()); - if (attached.getSurfaceLayer() < mDockLayer) { + if (attached.isVoiceInteraction()) { + if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft; + if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop; + if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight; + if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom; + } else if (attached.getSurfaceLayer() < mDockLayer) { if (cf.left < mContentLeft) cf.left = mContentLeft; if (cf.top < mContentTop) cf.top = mContentTop; if (cf.right > mContentRight) cf.right = mContentRight; @@ -3169,16 +3178,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if ((fl & FLAG_FULLSCREEN) == 0) { - if (adjust != SOFT_INPUT_ADJUST_RESIZE) { - cf.left = mDockLeft; - cf.top = mDockTop; - cf.right = mDockRight; - cf.bottom = mDockBottom; + if (win.isVoiceInteraction()) { + cf.left = mVoiceContentLeft; + cf.top = mVoiceContentTop; + cf.right = mVoiceContentRight; + cf.bottom = mVoiceContentBottom; } else { - cf.left = mContentLeft; - cf.top = mContentTop; - cf.right = mContentRight; - cf.bottom = mContentBottom; + if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + cf.left = mDockLeft; + cf.top = mDockTop; + cf.right = mDockRight; + cf.bottom = mDockBottom; + } else { + cf.left = mContentLeft; + cf.top = mContentTop; + cf.right = mContentRight; + cf.bottom = mContentBottom; + } } } else { // Full screen windows are always given a layout that is as if the @@ -3390,6 +3406,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { setLastInputMethodWindowLw(null, null); offsetInputMethodWindowLw(win); } + if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw() + && !win.getGivenInsetsPendingLw()) { + offsetVoiceInputWindowLw(win); + } } private void offsetInputMethodWindowLw(WindowState win) { @@ -3398,6 +3418,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mContentBottom > top) { mContentBottom = top; } + if (mVoiceContentBottom > top) { + mVoiceContentBottom = top; + } top = win.getVisibleFrameLw().top; top += win.getGivenVisibleInsetsLw().top; if (mCurBottom > top) { @@ -3408,6 +3431,40 @@ public class PhoneWindowManager implements WindowManagerPolicy { + mContentBottom + " mCurBottom=" + mCurBottom); } + private void offsetVoiceInputWindowLw(WindowState win) { + final int gravity = win.getAttrs().gravity; + switch (gravity&((Gravity.AXIS_PULL_BEFORE|Gravity.AXIS_PULL_AFTER) + << Gravity.AXIS_X_SHIFT)) { + case Gravity.AXIS_PULL_BEFORE<<Gravity.AXIS_X_SHIFT: { + int right = win.getContentFrameLw().right - win.getGivenContentInsetsLw().right; + if (mVoiceContentLeft < right) { + mVoiceContentLeft = right; + } + } break; + case Gravity.AXIS_PULL_AFTER<<Gravity.AXIS_X_SHIFT: { + int left = win.getContentFrameLw().left - win.getGivenContentInsetsLw().left; + if (mVoiceContentRight < left) { + mVoiceContentRight = left; + } + } break; + } + switch (gravity&((Gravity.AXIS_PULL_BEFORE|Gravity.AXIS_PULL_AFTER) + << Gravity.AXIS_Y_SHIFT)) { + case Gravity.AXIS_PULL_BEFORE<<Gravity.AXIS_Y_SHIFT: { + int bottom = win.getContentFrameLw().bottom - win.getGivenContentInsetsLw().bottom; + if (mVoiceContentTop < bottom) { + mVoiceContentTop = bottom; + } + } break; + case Gravity.AXIS_PULL_AFTER<<Gravity.AXIS_Y_SHIFT: { + int top = win.getContentFrameLw().top - win.getGivenContentInsetsLw().top; + if (mVoiceContentBottom < top) { + mVoiceContentBottom = top; + } + } break; + } + } + /** {@inheritDoc} */ @Override public void finishLayoutLw() { @@ -3972,37 +4029,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - // If an incoming call is ringing, either VOLUME key means - // "silence ringer". We handle these keys here, rather than - // in the InCallScreen, to make sure we'll respond to them - // even if the InCallScreen hasn't come to the foreground yet. - // Look for the DOWN event here, to agree with the "fallback" - // behavior in the InCallScreen. - Log.i(TAG, "interceptKeyBeforeQueueing:" - + " VOLUME key-down while ringing: Silence ringer!"); - - // Silence the ringer. (It's safe to call this - // even if the ringer has already been silenced.) - telephonyService.silenceRinger(); - - // And *don't* pass this key thru to the current activity - // (which is probably the InCallScreen.) - result &= ~ACTION_PASS_TO_USER; - break; - } - if (telephonyService.isOffhook() - && (result & ACTION_PASS_TO_USER) == 0) { - // If we are in call but we decided not to pass the key to - // the application, handle the volume change here. - handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); - break; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + // If an incoming call is ringing, either VOLUME key means + // "silence ringer". We handle these keys here, rather than + // in the InCallScreen, to make sure we'll respond to them + // even if the InCallScreen hasn't come to the foreground yet. + // Look for the DOWN event here, to agree with the "fallback" + // behavior in the InCallScreen. + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " VOLUME key-down while ringing: Silence ringer!"); + + // Silence the ringer. (It's safe to call this + // even if the ringer has already been silenced.) + telephonyManager.silenceRinger(); + + // And *don't* pass this key thru to the current activity + // (which is probably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + break; + } + if (telephonyManager.isOffhook() + && (result & ACTION_PASS_TO_USER) == 0) { + // If we are in call but we decided not to pass the key to + // the application, handle the volume change here. + handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); + break; } } @@ -4019,14 +4072,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_ENDCALL: { result &= ~ACTION_PASS_TO_USER; if (down) { - ITelephony telephonyService = getTelephonyService(); + TelephonyManager telephonyManager = getTelephonyService(); boolean hungUp = false; - if (telephonyService != null) { - try { - hungUp = telephonyService.endCall(); - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); - } + if (telephonyManager != null) { + hungUp = telephonyManager.endCall(); } interceptPowerKeyDown(!interactive || hungUp); } else { @@ -4062,23 +4111,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { interceptScreenshotChord(); } - ITelephony telephonyService = getTelephonyService(); + TelephonyManager telephonyManager = getTelephonyService(); boolean hungUp = false; - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - // Pressing Power while there's a ringing incoming - // call should silence the ringer. - telephonyService.silenceRinger(); - } else if ((mIncallPowerBehavior - & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 - && telephonyService.isOffhook() && interactive) { - // Otherwise, if "Power button ends call" is enabled, - // the Power button will hang up any current active call. - hungUp = telephonyService.endCall(); - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + // Pressing Power while there's a ringing incoming + // call should silence the ringer. + telephonyManager.silenceRinger(); + } else if ((mIncallPowerBehavior + & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 + && telephonyManager.isOffhook() && interactive) { + // Otherwise, if "Power button ends call" is enabled, + // the Power button will hang up any current active call. + hungUp = telephonyManager.endCall(); } } interceptPowerKeyDown(!interactive || hungUp @@ -4111,16 +4156,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (!telephonyService.isIdle()) { - // Suppress PLAY/PAUSE toggle when phone is ringing or in-call - // to avoid music playback. - break; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (!telephonyManager.isIdle()) { + // Suppress PLAY/PAUSE toggle when phone is ringing or in-call + // to avoid music playback. + break; } } } @@ -4150,20 +4191,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_CALL: { if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - Log.i(TAG, "interceptKeyBeforeQueueing:" - + " CALL key-down while ringing: Answer the call!"); - telephonyService.answerRingingCall(); - - // And *don't* pass this key thru to the current activity - // (which is presumably the InCallScreen.) - result &= ~ACTION_PASS_TO_USER; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " CALL key-down while ringing: Answer the call!"); + telephonyManager.answerRingingCall(); + + // And *don't* pass this key thru to the current activity + // (which is presumably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; } } } @@ -5523,6 +5560,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(","); pw.print(mContentTop); pw.print(")-("); pw.print(mContentRight); pw.print(","); pw.print(mContentBottom); pw.println(")"); + pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft); + pw.print(","); pw.print(mVoiceContentTop); + pw.print(")-("); pw.print(mVoiceContentRight); + pw.print(","); pw.print(mVoiceContentBottom); pw.println(")"); pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft); pw.print(","); pw.print(mDockTop); pw.print(")-("); pw.print(mDockRight); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7ecf248..5527528 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -343,12 +343,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_INET_CONDITION_HOLD_END = 5; /** - * used internally to set enable/disable cellular data - * arg1 = ENBALED or DISABLED - */ - private static final int EVENT_SET_MOBILE_DATA = 7; - - /** * used internally to clear a wakelock when transitioning * from one net to another */ @@ -1822,20 +1816,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return true; } - /** - * @see ConnectivityManager#getMobileDataEnabled() - */ - public boolean getMobileDataEnabled() { - // TODO: This detail should probably be in DataConnectionTracker's - // which is where we store the value and maybe make this - // asynchronous. - enforceAccessPermission(); - boolean retVal = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MOBILE_DATA, 1) == 1; - if (VDBG) log("getMobileDataEnabled returning " + retVal); - return retVal; - } - public void setDataDependency(int networkType, boolean met) { enforceConnectivityInternalPermission(); @@ -1908,22 +1888,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } }; - /** - * @see ConnectivityManager#setMobileDataEnabled(boolean) - */ - public void setMobileDataEnabled(boolean enabled) { - enforceChangePermission(); - if (DBG) log("setMobileDataEnabled(" + enabled + ")"); - - mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, - (enabled ? ENABLED : DISABLED), 0)); - } - - private void handleSetMobileData(boolean enabled) { - // TODO - handle this - probably generalize passing in a transport type and send to the - // factories? - } - @Override public void setPolicyDataEnable(int networkType, boolean enabled) { // only someone like NPMS should only be calling us @@ -3315,11 +3279,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleInetConditionHoldEnd(netType, sequence); break; } - case EVENT_SET_MOBILE_DATA: { - boolean enabled = (msg.arg1 == ENABLED); - handleSetMobileData(enabled); - break; - } case EVENT_APPLY_GLOBAL_HTTP_PROXY: { handleDeprecatedGlobalHttpProxy(); break; @@ -5647,16 +5606,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { boolean isNewDefault = false; if (DBG) log("handleConnectionValidated for "+newNetwork.name()); // check if any NetworkRequest wants this NetworkAgent - // first check if it satisfies the NetworkCapabilities ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>(); if (VDBG) log(" new Network has: " + newNetwork.networkCapabilities); for (NetworkRequestInfo nri : mNetworkRequests.values()) { + NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId); + if (newNetwork == currentNetwork) { + if (VDBG) log("Network " + newNetwork.name() + " was already satisfying" + + " request " + nri.request.requestId + ". No change."); + keep = true; + continue; + } + + // check if it satisfies the NetworkCapabilities if (VDBG) log(" checking if request is satisfied: " + nri.request); if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities( newNetwork.networkCapabilities)) { // next check if it's better than any current network we're using for // this request - NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId); if (VDBG) { log("currentScore = " + (currentNetwork != null ? currentNetwork.currentScore : 0) + @@ -5785,12 +5751,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (state == NetworkInfo.State.CONNECTED) { - // TODO - check if we want it (optimization) try { + // This is likely caused by the fact that this network already + // exists. An example is when a network goes from CONNECTED to + // CONNECTING and back (like wifi on DHCP renew). + // TODO: keep track of which networks we've created, or ask netd + // to tell us whether we've already created this network or not. mNetd.createNetwork(networkAgent.network.netId); } catch (Exception e) { - loge("Error creating Network " + networkAgent.network.netId); + loge("Error creating network " + networkAgent.network.netId + ": " + + e.getMessage()); + return; } + updateLinkProperties(networkAgent, null); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 88bebcb..fc808ec 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7085,6 +7085,10 @@ public final class ActivityManagerService extends ActivityManagerNative * Creates a new RecentTaskInfo from a TaskRecord. */ private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) { + // Update the task description to reflect any changes in the task stack + tr.updateTaskDescription(); + + // Compose the recent task info ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); rti.id = tr.mActivities.isEmpty() ? -1 : tr.taskId; @@ -9574,11 +9578,13 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - mRecentTasks = mTaskPersister.restoreTasksLocked(); - if (!mRecentTasks.isEmpty()) { - mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks); + if (mRecentTasks == null) { + mRecentTasks = mTaskPersister.restoreTasksLocked(); + if (!mRecentTasks.isEmpty()) { + mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks); + } + mTaskPersister.startPersisting(); } - mTaskPersister.startPersisting(); // Check to see if there are any update receivers to run. if (!mDidUpdate) { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b948c41..b429b93 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1154,13 +1154,15 @@ final class ActivityRecord { } if (intent == null) { - Slog.e(TAG, "restoreActivity error intent=" + intent); - return null; + throw new XmlPullParserException("restoreActivity error intent=" + intent); } final ActivityManagerService service = stackSupervisor.mService; final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null, null, userId); + if (aInfo == null) { + throw new XmlPullParserException("restoreActivity resolver error."); + } final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid, launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(), null, null, 0, componentSpecified, stackSupervisor, null, null); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d0ba118..8f60b03 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -37,6 +37,7 @@ import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; @@ -346,6 +347,10 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; + // Get the activity screenshot thumbnail dimensions + Resources res = mService.mContext.getResources(); + mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); } /** @@ -729,42 +734,54 @@ final class ActivityStack { } } + /** + * This resets the saved state from the last screenshot, forcing a new screenshot to be taken + * again when requested. + */ + private void invalidateLastScreenshot() { + mLastScreenshotActivity = null; + if (mLastScreenshotBitmap != null) { + mLastScreenshotBitmap.recycle(); + } + mLastScreenshotBitmap = null; + } + public final Bitmap screenshotActivities(ActivityRecord who) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "screenshotActivities: " + who); if (who.noDisplay) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tNo display"); return null; } TaskRecord tr = who.task; - if (mService.getMostRecentTask() != tr && tr.intent != null && - (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { - // If this task is being excluded from recents, we don't want to take - // the expense of capturing a thumbnail, since we will never show it. + if (mService.getMostRecentTask() != tr || isHomeStack()) { + // This is an optimization -- since we never show Home or Recents within Recents itself, + // we can just go ahead and skip taking the screenshot if this is the home stack. In + // the case where the most recent task is not the task that was supplied, then the stack + // has changed, so invalidate the last screenshot(). + invalidateLastScreenshot(); + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tIs Home stack? " + isHomeStack()); return null; } - Resources res = mService.mContext.getResources(); int w = mThumbnailWidth; int h = mThumbnailHeight; - if (w < 0) { - mThumbnailWidth = w = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width); - mThumbnailHeight = h = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_height); - } - if (w > 0) { if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null || mLastScreenshotActivity.state == ActivityState.RESUMED || mLastScreenshotBitmap.getWidth() != w || mLastScreenshotBitmap.getHeight() != h) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tUpdating screenshot"); mLastScreenshotActivity = who; mLastScreenshotBitmap = mWindowManager.screenshotApplications( who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565); } if (mLastScreenshotBitmap != null) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tReusing last screenshot"); return mLastScreenshotBitmap.copy(mLastScreenshotBitmap.getConfig(), true); } } + Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h); return null; } @@ -1042,6 +1059,12 @@ final class ActivityStack { } else { next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process } + + // If we are resuming the activity that we had last screenshotted, then we know it will be + // updated, so invalidate the last screenshot to ensure we take a fresh one when requested + if (next == mLastScreenshotActivity) { + invalidateLastScreenshot(); + } } private void setVisibile(ActivityRecord r, boolean visible) { @@ -1837,7 +1860,7 @@ final class ActivityStack { mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, - r.userId, r.info.configChanges); + r.userId, r.info.configChanges, task.voiceSession != null); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } @@ -1898,7 +1921,7 @@ final class ActivityStack { mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, - r.info.configChanges); + r.info.configChanges, task.voiceSession != null); boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need @@ -1943,7 +1966,7 @@ final class ActivityStack { mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, - r.info.configChanges); + r.info.configChanges, task.voiceSession != null); ActivityOptions.abort(options); options = null; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index c1a4643..6d20a32 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -109,6 +109,7 @@ public final class ActivityStackSupervisor implements DisplayListener { static final boolean DEBUG_SAVED_STATE = DEBUG || false; static final boolean DEBUG_STATES = DEBUG || false; static final boolean DEBUG_IDLE = DEBUG || false; + static final boolean DEBUG_SCREENSHOTS = DEBUG || false; public static final int HOME_STACK_ID = 0; @@ -1212,8 +1213,7 @@ public final class ActivityStackSupervisor implements DisplayListener { requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; if (resultRecord != null) { - resultRecord.removeResultsLocked( - sourceRecord, resultWho, requestCode); + resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); } if (sourceRecord.launchedFromUid == callingUid) { // The new activity is being launched from the same uid as the previous @@ -1385,7 +1385,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return err; } - ActivityStack adjustStackFocus(ActivityRecord r) { + ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) { final TaskRecord task = r.task; if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { if (task != null) { @@ -1410,7 +1410,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return container.mStack; } - if (mFocusedStack != mHomeStack) { + if (mFocusedStack != mHomeStack && (!newTask || + mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Have a focused stack=" + mFocusedStack); return mFocusedStack; @@ -1463,7 +1464,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // We'll invoke onUserLeaving before onPause only if the launching // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving); // If the caller has asked not to resume at this point, we make note @@ -1473,7 +1474,8 @@ public final class ActivityStackSupervisor implements DisplayListener { r.delayedResume = true; } - ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + ActivityRecord notTop = + (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; // If the onlyIfNeeded flag is set, then we can do this if the activity // being launched is the same as the one making the call... or, as @@ -1496,9 +1498,11 @@ public final class ActivityStackSupervisor implements DisplayListener { case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: intent.addFlags( Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + launchFlags = intent.getFlags(); break; case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + launchFlags = intent.getFlags(); break; } final boolean newDocument = intent.isDocument(); @@ -1804,7 +1808,8 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } - targetStack = adjustStackFocus(r); + newTask = true; + targetStack = adjustStackFocus(r, newTask); targetStack.moveToFront(); if (reuseTask == null) { r.setTask(targetStack.createTaskRecord(getNextTaskId(), @@ -1821,7 +1826,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } else { r.setTask(reuseTask, reuseTask, true); } - newTask = true; if (!movedHome) { if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) @@ -1889,7 +1893,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); + targetStack = adjustStackFocus(r, newTask); targetStack.moveToFront(); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task @@ -2286,7 +2290,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mWindowManager.addAppToken(0, r.appToken, taskId, stackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, - r.userId, r.info.configChanges); + r.userId, r.info.configChanges, task.voiceSession != null); } mWindowManager.addTask(taskId, stackId, false); } @@ -2316,7 +2320,12 @@ public final class ActivityStackSupervisor implements DisplayListener { for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (!r.isApplicationActivity() && !stack.isHomeStack()) { - if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack); + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (home activity) " + stack); + continue; + } + if (!stack.mActivityContainer.isEligibleForNewTasks()) { + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (new task not allowed) " + + stack); continue; } final ActivityRecord ar = stack.findTaskLocked(r); @@ -3247,6 +3256,11 @@ public final class ActivityStackSupervisor implements DisplayListener { void setDrawn() { } + // You can always start a new task on a regular ActivityStack. + boolean isEligibleForNewTasks() { + return true; + } + @Override public String toString() { return mIdString + (mActivityDisplay == null ? "N" : "A"); @@ -3327,6 +3341,12 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + // Never start a new task on an ActivityView if it isn't explicitly specified. + @Override + boolean isEligibleForNewTasks() { + return false; + } + private void setSurfaceIfReady() { if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReady: mDrawn=" + mDrawn + " mContainerState=" + mContainerState + " mSurface=" + mSurface); diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index ba3f2fe..3bfaca9 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -155,6 +155,7 @@ public class TaskPersister { File taskFile = recentFiles[taskNdx]; if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName()); BufferedReader reader = null; + boolean deleteFile = false; try { reader = new BufferedReader(new FileReader(taskFile)); final XmlPullParser in = Xml.newPullParser(); @@ -183,10 +184,9 @@ public class TaskPersister { } XmlUtils.skipCurrentTag(in); } - } catch (IOException e) { - Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); - } catch (XmlPullParserException e) { - Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); + } catch (Exception e) { + Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e); + deleteFile = true; } finally { if (reader != null) { try { @@ -194,6 +194,9 @@ public class TaskPersister { } catch (IOException e) { } } + if (!DEBUG && deleteFile) { + taskFile.delete(); + } } } @@ -220,7 +223,7 @@ public class TaskPersister { return new ArrayList<TaskRecord>(Arrays.asList(tasksArray)); } - private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { + private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { File file = files[fileNdx]; String filename = file.getName(); @@ -285,8 +288,7 @@ public class TaskPersister { synchronized(mService) { final ArrayList<TaskRecord> tasks = mService.mRecentTasks; persistentTaskIds.clear(); - int taskNdx; - for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { task = tasks.get(taskNdx); if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + task.isPersistable + " needsPersisting=" + task.needsPersisting); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index b94ea62..1b1fc8b 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -259,13 +259,17 @@ abstract public class ManagedServices { userIds[i])); } - ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()]; + ArrayList<ManagedServiceInfo> toRemove = new ArrayList<ManagedServiceInfo>(); final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<ArrayList<ComponentName>>(); synchronized (mMutex) { - // unbind and remove all existing services - toRemove = mServices.toArray(toRemove); + // Unbind automatically bound services, retain system services. + for (ManagedServiceInfo service : mServices) { + if (!service.isSystem) { + toRemove.add(service); + } + } final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>(); final ArraySet<String> newPackages = new ArraySet<String>(); diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index d81a25e..66cc532 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -46,12 +46,14 @@ import java.util.Map; * {@hide} */ public class NotificationUsageStats { + private static final boolean ENABLE_SQLITE_LOG = false; + // Guarded by synchronized(this). private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>(); private final SQLiteLog mSQLiteLog; public NotificationUsageStats(Context context) { - mSQLiteLog = new SQLiteLog(context); + mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null; } /** @@ -63,7 +65,9 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numPostedByApp++; } - mSQLiteLog.logPosted(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logPosted(notification); + } } /** @@ -85,7 +89,9 @@ public class NotificationUsageStats { stats.numRemovedByApp++; stats.collect(notification.stats); } - mSQLiteLog.logRemoved(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logRemoved(notification); + } } /** @@ -97,7 +103,9 @@ public class NotificationUsageStats { stats.numDismissedByUser++; stats.collect(notification.stats); } - mSQLiteLog.logDismissed(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logDismissed(notification); + } } /** @@ -108,7 +116,9 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numClickedByUser++; } - mSQLiteLog.logClicked(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logClicked(notification); + } } /** @@ -164,7 +174,9 @@ public class NotificationUsageStats { for (AggregatedStats as : mStats.values()) { as.dump(pw, indent); } - mSQLiteLog.dump(pw, indent); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.dump(pw, indent); + } } /** diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 157d749..a629a5f 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -139,56 +139,64 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } private String[] getExtraPeople(Bundle extras) { - String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE); - if (people != null) { - return people; + Object people = extras.get(Notification.EXTRA_PEOPLE); + if (people instanceof String[]) { + return (String[]) people; } - ArrayList<String> stringArray = extras.getStringArrayList(Notification.EXTRA_PEOPLE); - if (stringArray != null) { - return (String[]) stringArray.toArray(); + if (people instanceof ArrayList) { + ArrayList arrayList = (ArrayList) people; + + if (arrayList.isEmpty()) { + return null; + } + + if (arrayList.get(0) instanceof String) { + ArrayList<String> stringArray = (ArrayList<String>) arrayList; + return stringArray.toArray(new String[stringArray.size()]); + } + + if (arrayList.get(0) instanceof CharSequence) { + ArrayList<CharSequence> charSeqList = (ArrayList<CharSequence>) arrayList; + final int N = charSeqList.size(); + String[] array = new String[N]; + for (int i = 0; i < N; i++) { + array[i] = charSeqList.get(i).toString(); + } + return array; + } + + return null; } - String string = extras.getString(Notification.EXTRA_PEOPLE); - if (string != null) { - people = new String[1]; - people[0] = string; - return people; + if (people instanceof String) { + String[] array = new String[1]; + array[0] = (String) people; + return array; } - char[] charArray = extras.getCharArray(Notification.EXTRA_PEOPLE); - if (charArray != null) { - people = new String[1]; - people[0] = new String(charArray); - return people; + + if (people instanceof char[]) { + String[] array = new String[1]; + array[0] = new String((char[]) people); + return array; } - CharSequence charSeq = extras.getCharSequence(Notification.EXTRA_PEOPLE); - if (charSeq != null) { - people = new String[1]; - people[0] = charSeq.toString(); - return people; + if (people instanceof CharSequence) { + String[] array = new String[1]; + array[0] = ((CharSequence) people).toString(); + return array; } - CharSequence[] charSeqArray = extras.getCharSequenceArray(Notification.EXTRA_PEOPLE); - if (charSeqArray != null) { + if (people instanceof CharSequence[]) { + CharSequence[] charSeqArray = (CharSequence[]) people; final int N = charSeqArray.length; - people = new String[N]; + String[] array = new String[N]; for (int i = 0; i < N; i++) { - people[i] = charSeqArray[i].toString(); + array[i] = charSeqArray[i].toString(); } - return people; + return array; } - ArrayList<CharSequence> charSeqList = - extras.getCharSequenceArrayList(Notification.EXTRA_PEOPLE); - if (charSeqList != null) { - final int N = charSeqList.size(); - people = new String[N]; - for (int i = 0; i < N; i++) { - people[i] = charSeqList.get(i).toString(); - } - return people; - } return null; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b06b090..d505e81 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11775,8 +11775,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } if (removed.size() > 0) { - for (int j=0; j<removed.size(); j++) { - PreferredActivity pa = removed.get(i); + for (int r=0; r<removed.size(); r++) { + PreferredActivity pa = removed.get(r); Slog.w(TAG, "Removing dangling preferred activity: " + pa.mPref.mComponent); pir.removeFilter(pa); diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java index 6d208ff..80030b4 100644 --- a/services/core/java/com/android/server/task/TaskManagerService.java +++ b/services/core/java/com/android/server/task/TaskManagerService.java @@ -16,12 +16,22 @@ package com.android.server.task; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import android.app.task.ITaskManager; +import android.app.task.Task; import android.content.Context; -import android.content.Task; +import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.util.Log; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; import android.util.SparseArray; import com.android.server.task.controllers.TaskStatus; @@ -39,13 +49,6 @@ public class TaskManagerService extends com.android.server.SystemService /** Master list of tasks. */ private final TaskStore mTasks; - /** Check the pending queue and start any tasks. */ - static final int MSG_RUN_PENDING = 0; - /** Initiate the stop task flow. */ - static final int MSG_STOP_TASK = 1; - /** */ - static final int MSG_CHECK_TASKS = 2; - /** * Track Services that have currently active or pending tasks. The index is provided by * {@link TaskStatus#getServiceToken()} @@ -54,6 +57,14 @@ public class TaskManagerService extends com.android.server.SystemService new SparseArray<TaskServiceContext>(); private final TaskHandler mHandler; + private final TaskManagerStub mTaskManagerStub; + + /** Check the pending queue and start any tasks. */ + static final int MSG_RUN_PENDING = 0; + /** Initiate the stop task flow. */ + static final int MSG_STOP_TASK = 1; + /** */ + static final int MSG_CHECK_TASKS = 2; private class TaskHandler extends Handler { @@ -94,21 +105,6 @@ public class TaskManagerService extends com.android.server.SystemService } /** - * Entry point from client to schedule the provided task. - * This will add the task to the - * @param task Task object containing execution parameters - * @param userId The id of the user this task is for. - * @param uId The package identifier of the application this task is for. - * @param canPersistTask Whether or not the client has the appropriate permissions for persisting - * of this task. - * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes. - */ - public int schedule(Task task, int userId, int uId, boolean canPersistTask) { - TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask); - return 0; - } - - /** * Initializes the system service. * <p> * Subclasses must define a single argument constructor that accepts the context @@ -121,11 +117,42 @@ public class TaskManagerService extends com.android.server.SystemService super(context); mTasks = new TaskStore(context); mHandler = new TaskHandler(context.getMainLooper()); + mTaskManagerStub = new TaskManagerStub(); } @Override public void onStart() { + publishBinderService(Context.TASK_SERVICE, mTaskManagerStub); + } + + /** + * Entry point from client to schedule the provided task. + * This will add the task to the + * @param task Task object containing execution parameters + * @param userId The id of the user this task is for. + * @param uId The package identifier of the application this task is for. + * @param canPersistTask Whether or not the client has the appropriate permissions for + * persisting of this task. + * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes. + */ + public int schedule(Task task, int userId, int uId, boolean canPersistTask) { + TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask); + return 0; + } + public List<Task> getPendingTasks(int uid) { + ArrayList<Task> outList = new ArrayList<Task>(3); + synchronized (mTasks) { + final SparseArray<TaskStatus> tasks = mTasks.getTasks(); + final int N = tasks.size(); + for (int i = 0; i < N; i++) { + TaskStatus ts = tasks.get(i); + if (ts.getUid() == uid) { + outList.add(ts.getTask()); + } + } + } + return outList; } // StateChangedListener implementations. @@ -162,7 +189,7 @@ public class TaskManagerService extends com.android.server.SystemService public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) { final TaskServiceContext serviceContext = mActiveServices.get(serviceToken); if (serviceContext == null) { - Log.e(TAG, "Task completed for invalid service context; " + serviceToken); + Slog.e(TAG, "Task completed for invalid service context; " + serviceToken); return; } @@ -203,4 +230,98 @@ public class TaskManagerService extends com.android.server.SystemService private void postCheckTasksMessage() { mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget(); } + + /** + * Binder stub trampoline implementation + */ + final class TaskManagerStub extends ITaskManager.Stub { + /** Cache determination of whether a given app can persist tasks + * key is uid of the calling app; value is undetermined/true/false + */ + private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); + + // Determine whether the caller is allowed to persist tasks, with a small cache + // because the lookup is expensive enough that we'd like to avoid repeating it. + // This must be called from within the calling app's binder identity! + private boolean canCallerPersistTasks() { + final boolean canPersist; + final int callingUid = Binder.getCallingUid(); + synchronized (mPersistCache) { + Boolean cached = mPersistCache.get(callingUid); + if (cached) { + canPersist = cached.booleanValue(); + } else { + // Persisting tasks is tantamount to running at boot, so we permit + // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED + // permission + int result = getContext().checkCallingPermission( + android.Manifest.permission.RECEIVE_BOOT_COMPLETED); + canPersist = (result == PackageManager.PERMISSION_GRANTED); + mPersistCache.put(callingUid, canPersist); + } + } + return canPersist; + } + + // ITaskManager implementation + @Override + public int schedule(Task task) throws RemoteException { + final boolean canPersist = canCallerPersistTasks(); + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getCallingUserId(); + + long ident = Binder.clearCallingIdentity(); + try { + return TaskManagerService.this.schedule(task, userId, uid, canPersist); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public List<Task> getAllPendingTasks() throws RemoteException { + return null; + } + + @Override + public void cancelAll() throws RemoteException { + } + + @Override + public void cancel(int taskId) throws RemoteException { + } + + /** + * "dumpsys" infrastructure + */ + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + + long identityToken = Binder.clearCallingIdentity(); + try { + TaskManagerService.this.dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + }; + + void dumpInternal(PrintWriter pw) { + synchronized (mTasks) { + pw.print("Registered tasks:"); + if (mTasks.size() > 0) { + SparseArray<TaskStatus> tasks = mTasks.getTasks(); + for (int i = 0; i < tasks.size(); i++) { + TaskStatus ts = tasks.get(i); + pw.println(); + ts.dump(pw, " "); + } + } else { + pw.println(); + pw.println("No tasks scheduled."); + } + } + pw.println(); + } } diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java index b51cbb3..165445a 100644 --- a/services/core/java/com/android/server/task/TaskServiceContext.java +++ b/services/core/java/com/android/server/task/TaskServiceContext.java @@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * is reused to start concurrent tasks on the TaskService. Information here is unique * to the service. * Functionality provided by this class: - * - Managages wakelock for the service. + * - Manages wakelock for the service. * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks. * - */ diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java index 3bfc8a5..81187c8 100644 --- a/services/core/java/com/android/server/task/TaskStore.java +++ b/services/core/java/com/android/server/task/TaskStore.java @@ -16,8 +16,8 @@ package com.android.server.task; +import android.app.task.Task; import android.content.Context; -import android.content.Task; import android.util.SparseArray; import com.android.server.task.controllers.TaskStatus; @@ -95,6 +95,13 @@ public class TaskStore { } /** + * @return the number of tasks in the store + */ + public int size() { + return mTasks.size(); + } + + /** * @return The live array of TaskStatus objects. */ public SparseArray<TaskStatus> getTasks() { diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java index 6a4e1f3..a0038c5 100644 --- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java @@ -70,13 +70,10 @@ public class ConnectivityController extends StateController { } /** - * @param userId Id of the user for whom we are updating the connectivity state. + * */ - private void updateTrackedTasks(int userId) { + private void updateTrackedTasks() { for (TaskStatus ts : mTrackedTasks) { - if (ts.userId != userId) { - continue; - } boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity); boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered); if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) { @@ -107,14 +104,13 @@ public class ConnectivityController extends StateController { final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); // This broadcast gets sent a lot, only update if the active network has changed. if (activeNetwork.getType() == networkType) { - final int userid = context.getUserId(); mMetered = false; mConnectivity = !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); if (mConnectivity) { // No point making the call if we know there's no conn. mMetered = connManager.isActiveNetworkMetered(); } - updateTrackedTasks(userid); + updateTrackedTasks(); } } else { Log.w(TAG, "Unrecognised action in intent: " + action); diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java index d96fedc..d270016 100644 --- a/services/core/java/com/android/server/task/controllers/TaskStatus.java +++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java @@ -16,17 +16,19 @@ package com.android.server.task.controllers; +import android.app.task.Task; import android.content.ComponentName; -import android.content.Task; import android.content.pm.PackageParser; import android.os.Bundle; import android.os.SystemClock; +import android.os.UserHandle; +import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; /** * Uniquely identifies a task internally. - * Created from the public {@link android.content.Task} object when it lands on the scheduler. + * Created from the public {@link android.app.task.Task} object when it lands on the scheduler. * Contains current state of the requirements of the task, as well as a function to evaluate * whether it's ready to run. * This object is shared among the various controllers - hence why the different fields are atomic. @@ -36,10 +38,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * @hide */ public class TaskStatus { + final Task task; final int taskId; - final int userId; final int uId; - final ComponentName component; final Bundle extras; final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean(); @@ -57,9 +58,9 @@ public class TaskStatus { private long earliestRunTimeElapsedMillis; private long latestRunTimeElapsedMillis; - /** Provide a unique handle to the service that this task will be run on. */ + /** Provide a handle to the service that this task will be run on. */ public int getServiceToken() { - return component.hashCode() + userId; + return uId; } /** Generate a TaskStatus object for a given task and uid. */ @@ -70,9 +71,8 @@ public class TaskStatus { /** Set up the state of a newly scheduled task. */ TaskStatus(Task task, int userId, int uId) { + this.task = task; this.taskId = task.getTaskId(); - this.userId = userId; - this.component = task.getService(); this.extras = task.getExtras(); this.uId = uId; @@ -100,16 +100,20 @@ public class TaskStatus { hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY; } + public Task getTask() { + return task; + } + public int getTaskId() { return taskId; } public ComponentName getServiceComponent() { - return component; + return task.getService(); } public int getUserId() { - return userId; + return UserHandle.getUserId(uId); } public int getUid() { @@ -161,9 +165,9 @@ public class TaskStatus { @Override public int hashCode() { - int result = component.hashCode(); + int result = getServiceComponent().hashCode(); result = 31 * result + taskId; - result = 31 * result + userId; + result = 31 * result + uId; return result; } @@ -174,7 +178,14 @@ public class TaskStatus { TaskStatus that = (TaskStatus) o; return ((taskId == that.taskId) - && (userId == that.userId) - && (component.equals(that.component))); + && (uId == that.uId) + && (getServiceComponent().equals(that.getServiceComponent()))); + } + + // Dumpsys infrastructure + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("Task "); pw.println(taskId); + pw.print(prefix); pw.print("uid="); pw.println(uId); + pw.print(prefix); pw.print("component="); pw.println(task.getService()); } } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 47ce3b6..f18939f 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -63,6 +63,11 @@ public class TrustAgentWrapper { public void handleMessage(Message msg) { switch (msg.what) { case MSG_GRANT_TRUST: + if (!isConnected()) { + Log.w(TAG, "Agent is not connected, cannot grant trust: " + + mName.flattenToShortString()); + return; + } mTrusted = true; mMessage = (CharSequence) msg.obj; boolean initiatedByUser = msg.arg1 != 0; @@ -119,6 +124,7 @@ public class TrustAgentWrapper { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Log.v(TAG, "TrustAgent started : " + name.flattenToString()); mTrustAgentService = ITrustAgentService.Stub.asInterface(service); + mTrustManagerService.mArchive.logAgentConnected(mUserId, name); setCallback(mCallback); } @@ -179,7 +185,10 @@ public class TrustAgentWrapper { public void unbind() { if (DEBUG) Log.v(TAG, "TrustAgent unbound : " + mName.flattenToShortString()); + mTrustManagerService.mArchive.logAgentStopped(mUserId, mName); mContext.unbindService(mConnection); + mTrustAgentService = null; + mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); } public boolean isConnected() { diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java index aad156c..56950d2 100644 --- a/services/core/java/com/android/server/trust/TrustArchive.java +++ b/services/core/java/com/android/server/trust/TrustArchive.java @@ -33,6 +33,8 @@ public class TrustArchive { private static final int TYPE_REVOKE_TRUST = 1; private static final int TYPE_TRUST_TIMEOUT = 2; private static final int TYPE_AGENT_DIED = 3; + private static final int TYPE_AGENT_CONNECTED = 4; + private static final int TYPE_AGENT_STOPPED = 5; private static final int HISTORY_LIMIT = 200; @@ -79,6 +81,14 @@ public class TrustArchive { addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false)); } + public void logAgentConnected(int userId, ComponentName agent) { + addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, false)); + } + + public void logAgentStopped(int userId, ComponentName agent) { + addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, false)); + } + private void addEvent(Event e) { if (mEvents.size() >= HISTORY_LIMIT) { mEvents.removeFirst(); @@ -152,6 +162,10 @@ public class TrustArchive { return "TrustTimeout"; case TYPE_AGENT_DIED: return "AgentDied"; + case TYPE_AGENT_CONNECTED: + return "AgentConnected"; + case TYPE_AGENT_STOPPED: + return "AgentStopped"; default: return "Unknown(" + type + ")"; } diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java new file mode 100644 index 0000000..4bdd2be --- /dev/null +++ b/services/core/java/com/android/server/tv/TvInputHal.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tv; + +import android.os.Handler; +import android.os.HandlerThread; +import android.tv.TvInputHardwareInfo; +import android.tv.TvStreamConfig; +import android.view.Surface; + +/** + * Provides access to the low-level TV input hardware abstraction layer. + */ +final class TvInputHal { + public final static int SUCCESS = 0; + public final static int ERROR_NO_INIT = -1; + public final static int ERROR_STALE_CONFIG = -2; + public final static int ERROR_UNKNOWN = -3; + + public static final int TYPE_HDMI = 1; + public static final int TYPE_BUILT_IN_TUNER = 2; + public static final int TYPE_PASSTHROUGH = 3; + + public interface Callback { + public void onDeviceAvailable( + TvInputHardwareInfo info, TvStreamConfig[] configs); + public void onDeviceUnavailable(int deviceId); + public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs); + } + + private native long nativeOpen(); + + private static native int nativeSetSurface(long ptr, int deviceId, int streamId, + Surface surface); + private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId, + int generation); + private static native void nativeClose(long ptr); + + private long mPtr = 0l; + private final Callback mCallback; + private final HandlerThread mThread = new HandlerThread("TV input HAL event thread"); + private final Handler mHandler; + private int mStreamConfigGeneration = 0; + private TvStreamConfig[] mStreamConfigs; + + public TvInputHal(Callback callback) { + mCallback = callback; + mThread.start(); + mHandler = new Handler(mThread.getLooper()); + } + + public void init() { + mPtr = nativeOpen(); + } + + public int setSurface(int deviceId, Surface surface, TvStreamConfig streamConfig) { + if (mPtr == 0) { + return ERROR_NO_INIT; + } + if (mStreamConfigGeneration != streamConfig.getGeneration()) { + return ERROR_STALE_CONFIG; + } + if (nativeSetSurface(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) { + return SUCCESS; + } else { + return ERROR_UNKNOWN; + } + } + + public void close() { + if (mPtr != 0l) { + nativeClose(mPtr); + mThread.quitSafely(); + } + } + + private synchronized void retrieveStreamConfigs(int deviceId) { + ++mStreamConfigGeneration; + mStreamConfigs = nativeGetStreamConfigs(mPtr, deviceId, mStreamConfigGeneration); + } + + // Called from native + private void deviceAvailableFromNative(int deviceId, int type) { + final TvInputHardwareInfo info = new TvInputHardwareInfo(deviceId, type); + mHandler.post(new Runnable() { + @Override + public void run() { + retrieveStreamConfigs(info.getDeviceId()); + mCallback.onDeviceAvailable(info, mStreamConfigs); + } + }); + } + + private void deviceUnavailableFromNative(int deviceId) { + final int id = deviceId; + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onDeviceUnavailable(id); + } + }); + } + + private void streamConfigsChangedFromNative(int deviceId) { + final int id = deviceId; + mHandler.post(new Runnable() { + @Override + public void run() { + retrieveStreamConfigs(id); + mCallback.onStreamConfigurationChanged(id, mStreamConfigs); + } + }); + } +} diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java new file mode 100644 index 0000000..b95b0f0 --- /dev/null +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tv; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.tv.ITvInputHardware; +import android.tv.ITvInputHardwareCallback; +import android.tv.TvInputHardwareInfo; +import android.tv.TvStreamConfig; +import android.util.Slog; +import android.util.SparseArray; +import android.view.KeyEvent; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A helper class for TvInputManagerService to handle TV input hardware. + * + * This class does a basic connection management and forwarding calls to TvInputHal which eventually + * calls to tv_input HAL module. + * + * @hide + */ +class TvInputHardwareManager implements TvInputHal.Callback { + private static final String TAG = TvInputHardwareManager.class.getSimpleName(); + private final TvInputHal mHal = new TvInputHal(this); + private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); + private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); + private final Context mContext; + private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); + + private final Object mLock = new Object(); + + public TvInputHardwareManager(Context context) { + mContext = context; + // TODO(hdmi): mHdmiManager = mContext.getSystemService(...); + // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient(); + mHal.init(); + } + + @Override + public void onDeviceAvailable( + TvInputHardwareInfo info, TvStreamConfig[] configs) { + synchronized (mLock) { + Connection connection = new Connection(info); + connection.updateConfigsLocked(configs); + mConnections.put(info.getDeviceId(), connection); + buildInfoListLocked(); + // TODO: notify if necessary + } + } + + private void buildInfoListLocked() { + mInfoList.clear(); + for (int i = 0; i < mConnections.size(); ++i) { + mInfoList.add(mConnections.valueAt(i).getInfoLocked()); + } + } + + @Override + public void onDeviceUnavailable(int deviceId) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); + return; + } + connection.resetLocked(null, null, null, null); + mConnections.remove(deviceId); + buildInfoListLocked(); + // TODO: notify if necessary + } + } + + @Override + public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " + + deviceId); + return; + } + connection.updateConfigsLocked(configs); + try { + connection.getCallbackLocked().onStreamConfigChanged(configs); + } catch (RemoteException e) { + Slog.e(TAG, "onStreamConfigurationChanged: " + e); + } + } + } + + public List<TvInputHardwareInfo> getHardwareList() { + synchronized (mLock) { + return mInfoList; + } + } + + /** + * Create a TvInputHardware object with a specific deviceId. One service at a time can access + * the object, and if more than one process attempts to create hardware with the same deviceId, + * the latest service will get the object and all the other hardware are released. The + * release is notified via ITvInputHardwareCallback.onReleased(). + */ + public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, + int callingUid, int resolvedUserId) { + if (callback == null) { + throw new NullPointerException(); + } + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "Invalid deviceId : " + deviceId); + return null; + } + if (connection.getCallingUidLocked() != callingUid + || connection.getResolvedUserIdLocked() != resolvedUserId) { + TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked()); + try { + callback.asBinder().linkToDeath(connection, 0); + } catch (RemoteException e) { + hardware.release(); + return null; + } + connection.resetLocked(hardware, callback, callingUid, resolvedUserId); + } + return connection.getHardwareLocked(); + } + } + + /** + * Release the specified hardware. + */ + public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, + int resolvedUserId) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "Invalid deviceId : " + deviceId); + return; + } + if (connection.getHardwareLocked() != hardware + || connection.getCallingUidLocked() != callingUid + || connection.getResolvedUserIdLocked() != resolvedUserId) { + return; + } + connection.resetLocked(null, null, null, null); + } + } + + private class Connection implements IBinder.DeathRecipient { + private final TvInputHardwareInfo mInfo; + private TvInputHardwareImpl mHardware = null; + private ITvInputHardwareCallback mCallback; + private TvStreamConfig[] mConfigs = null; + private Integer mCallingUid = null; + private Integer mResolvedUserId = null; + + public Connection(TvInputHardwareInfo info) { + mInfo = info; + } + + // *Locked methods assume TvInputHardwareManager.mLock is held. + + public void resetLocked(TvInputHardwareImpl hardware, + ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) { + if (mHardware != null) { + try { + mCallback.onReleased(); + } catch (RemoteException e) { + Slog.e(TAG, "Connection::resetHardware: " + e); + } + mHardware.release(); + } + mHardware = hardware; + mCallback = callback; + mCallingUid = callingUid; + mResolvedUserId = resolvedUserId; + + if (mHardware != null && mCallback != null) { + try { + mCallback.onStreamConfigChanged(getConfigsLocked()); + } catch (RemoteException e) { + Slog.e(TAG, "Connection::resetHardware: " + e); + } + } + } + + public void updateConfigsLocked(TvStreamConfig[] configs) { + mConfigs = configs; + } + + public TvInputHardwareInfo getInfoLocked() { + return mInfo; + } + + public ITvInputHardware getHardwareLocked() { + return mHardware; + } + + public ITvInputHardwareCallback getCallbackLocked() { + return mCallback; + } + + public TvStreamConfig[] getConfigsLocked() { + return mConfigs; + } + + public int getCallingUidLocked() { + return mCallingUid; + } + + public int getResolvedUserIdLocked() { + return mResolvedUserId; + } + + @Override + public void binderDied() { + synchronized (mLock) { + resetLocked(null, null, null, null); + } + } + } + + private class TvInputHardwareImpl extends ITvInputHardware.Stub { + private final TvInputHardwareInfo mInfo; + private boolean mReleased = false; + private final Object mImplLock = new Object(); + + public TvInputHardwareImpl(TvInputHardwareInfo info) { + mInfo = info; + } + + public void release() { + synchronized (mImplLock) { + mReleased = true; + } + } + + @Override + public boolean setSurface(Surface surface, TvStreamConfig config) + throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + if (mInfo.getType() == TvInputHal.TYPE_HDMI) { + if (surface != null) { + // Set "Active Source" for HDMI. + // TODO(hdmi): mHdmiClient.deviceSelect(...); + mActiveHdmiSources.add(mInfo.getDeviceId()); + } else { + mActiveHdmiSources.remove(mInfo.getDeviceId()); + if (mActiveHdmiSources.size() == 0) { + // Tell HDMI that no HDMI source is active + // TODO(hdmi): mHdmiClient.portSelect(null); + } + } + } + return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS; + } + } + + @Override + public void setVolume(float volume) throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + } + // TODO + } + + @Override + public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + } + if (mInfo.getType() != TvInputHal.TYPE_HDMI) { + return false; + } + // TODO(hdmi): mHdmiClient.sendKeyEvent(event); + return false; + } + } +} diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 8ad7fff..6c38a4c 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -42,11 +42,14 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.TvContract; import android.tv.ITvInputClient; +import android.tv.ITvInputHardware; +import android.tv.ITvInputHardwareCallback; import android.tv.ITvInputManager; import android.tv.ITvInputService; import android.tv.ITvInputServiceCallback; import android.tv.ITvInputSession; import android.tv.ITvInputSessionCallback; +import android.tv.TvInputHardwareInfo; import android.tv.TvInputInfo; import android.tv.TvInputService; import android.util.Slog; @@ -71,6 +74,7 @@ public final class TvInputManagerService extends SystemService { private static final String TAG = "TvInputManagerService"; private final Context mContext; + private final TvInputHardwareManager mTvInputHardwareManager; private final ContentResolver mContentResolver; @@ -92,6 +96,7 @@ public final class TvInputManagerService extends SystemService { mContentResolver = context.getContentResolver(); mLogHandler = new LogHandler(IoThread.get().getLooper()); + mTvInputHardwareManager = new TvInputHardwareManager(context); registerBroadcastReceivers(); synchronized (mLock) { @@ -730,6 +735,64 @@ public final class TvInputManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public List<TvInputHardwareInfo> getHardwareList() throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return null; + } + + final long identity = Binder.clearCallingIdentity(); + try { + return mTvInputHardwareManager.getHardwareList(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public ITvInputHardware acquireTvInputHardware(int deviceId, + ITvInputHardwareCallback callback, int userId) throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return null; + } + + final long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "acquireTvInputHardware"); + try { + return mTvInputHardwareManager.acquireHardware( + deviceId, callback, callingUid, resolvedUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId) + throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return; + } + + final long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "releaseTvInputHardware"); + try { + mTvInputHardwareManager.releaseHardware( + deviceId, hardware, callingUid, resolvedUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } private static final class UserState { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index e2d2ac6..4f8b9d7 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -24,9 +24,7 @@ import android.graphics.Rect; import android.os.Debug; import android.os.Handler; import android.os.IRemoteCallback; -import android.os.SystemProperties; import android.util.Slog; -import android.view.View; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -299,7 +297,7 @@ public class AppTransition implements Dump { return null; } - Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) { + Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) { int anim = 0; Context context = mContext; if (animAttr >= 0) { @@ -315,7 +313,19 @@ public class AppTransition implements Dump { return null; } - private Animation loadAnimation(String packageName, int resId) { + Animation loadAnimationRes(WindowManager.LayoutParams lp, int resId) { + Context context = mContext; + if (resId >= 0) { + AttributeCache.Entry ent = getCachedAnimations(lp); + if (ent != null) { + context = ent.context; + } + return AnimationUtils.loadAnimation(context, resId); + } + return null; + } + + private Animation loadAnimationRes(String packageName, int resId) { int anim = 0; Context context = mContext; if (resId >= 0) { @@ -695,11 +705,31 @@ public class AppTransition implements Dump { Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - int appWidth, int appHeight, int orientation, - Rect containingFrame, Rect contentInsets, boolean isFullScreen) { + int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets, + boolean isFullScreen, boolean isVoiceInteraction) { Animation a; - if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { - a = loadAnimation(mNextAppTransitionPackage, enter ? + if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN + || transit == TRANSIT_TASK_OPEN + || transit == TRANSIT_TASK_TO_FRONT)) { + a = loadAnimationRes(lp, enter + ? com.android.internal.R.anim.voice_activity_open_enter + : com.android.internal.R.anim.voice_activity_open_exit); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation voice:" + + " anim=" + a + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE + || transit == TRANSIT_TASK_CLOSE + || transit == TRANSIT_TASK_TO_BACK)) { + a = loadAnimationRes(lp, enter + ? com.android.internal.R.anim.voice_activity_close_enter + : com.android.internal.R.anim.voice_activity_close_exit); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation voice:" + + " anim=" + a + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { + a = loadAnimationRes(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" @@ -782,7 +812,7 @@ public class AppTransition implements Dump { : WindowAnimation_wallpaperIntraCloseExitAnimation; break; } - a = animAttr != 0 ? loadAnimation(lp, animAttr) : null; + a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null; if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index ca4ad8a..12c15e2 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -50,6 +50,8 @@ class AppWindowToken extends WindowToken { final WindowAnimator mAnimator; + final boolean voiceInteraction; + int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -107,11 +109,13 @@ class AppWindowToken extends WindowToken { boolean mDeferRemoval; - AppWindowToken(WindowManagerService _service, IApplicationToken _token) { + AppWindowToken(WindowManagerService _service, IApplicationToken _token, + boolean _voiceInteraction) { super(_service, _token.asBinder(), WindowManager.LayoutParams.TYPE_APPLICATION, true); appWindowToken = this; appToken = _token; + voiceInteraction = _voiceInteraction; mInputApplicationHandle = new InputApplicationHandle(this); mAnimator = service.mAnimator; mAppAnimator = new AppWindowAnimator(this); @@ -249,7 +253,7 @@ class AppWindowToken extends WindowToken { void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); if (appToken != null) { - pw.print(prefix); pw.println("app=true"); + pw.print(prefix); pw.print("app=true voiceInteraction="); pw.println(voiceInteraction); } if (allAppWindows.size() > 0) { pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 27ab5ac..9937321 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2215,6 +2215,11 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + if (type == TYPE_VOICE_INTERACTION) { + Slog.w(TAG, "Attempted to add voice interaction window with unknown token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } if (type == TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); @@ -2250,6 +2255,12 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + } else if (type == TYPE_VOICE_INTERACTION) { + if (token.windowType != TYPE_VOICE_INTERACTION) { + Slog.w(TAG, "Attempted to add voice interaction window with bad token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } } else if (type == TYPE_WALLPAPER) { if (token.windowType != TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with bad token " @@ -3173,7 +3184,7 @@ public class WindowManagerService extends IWindowManager.Stub } private boolean applyAnimationLocked(AppWindowToken atoken, - WindowManager.LayoutParams lp, int transit, boolean enter) { + WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { // Only apply an animation if the display isn't frozen. If it is // frozen, there is no reason to animate and it can cause strange // artifacts when we unfreeze the display if some different animation @@ -3189,6 +3200,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = atoken.findMainWindow(); Rect containingFrame = new Rect(0, 0, width, height); Rect contentInsets = new Rect(); + boolean isFullScreen = true; if (win != null) { if (win.mContainingFrame != null) { containingFrame.set(win.mContainingFrame); @@ -3196,13 +3208,14 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mContentInsets != null) { contentInsets.set(win.mContentInsets); } + isFullScreen = + ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) == + SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN); } - boolean isFullScreen = - ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) - == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN); Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height, - mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen); + mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen, + isVoiceInteraction); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; @@ -3422,7 +3435,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, - int configChanges) { + int configChanges, boolean voiceInteraction) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3448,7 +3461,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to add existing app token: " + token); return; } - atoken = new AppWindowToken(this, token); + atoken = new AppWindowToken(this, token, voiceInteraction); atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; atoken.groupId = taskId; atoken.appFullscreen = fullscreen; @@ -4200,7 +4213,7 @@ public class WindowManagerService extends IWindowManager.Stub } boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp, - boolean visible, int transit, boolean performLayout) { + boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) { boolean delayed = false; if (wtoken.clientHidden == visible) { @@ -4221,7 +4234,7 @@ public class WindowManagerService extends IWindowManager.Stub if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { wtoken.mAppAnimator.animation = null; } - if (applyAnimationLocked(wtoken, lp, transit, visible)) { + if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) { delayed = runningAppAnimation = true; } WindowState window = wtoken.findMainWindow(); @@ -4399,7 +4412,7 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET, - true); + true, wtoken.voiceInteraction); wtoken.updateReportedVisibilityLocked(); Binder.restoreCallingIdentity(origId); } @@ -4546,7 +4559,7 @@ public class WindowManagerService extends IWindowManager.Stub if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken); delayed = setTokenVisibilityLocked(wtoken, null, false, - AppTransition.TRANSIT_UNSET, true); + AppTransition.TRANSIT_UNSET, true, wtoken.voiceInteraction); wtoken.inPendingTransaction = false; mOpeningApps.remove(wtoken); wtoken.waitingToShow = false; @@ -8537,6 +8550,7 @@ public class WindowManagerService extends IWindowManager.Stub LayoutParams animLp = null; int bestAnimLayer = -1; boolean fullscreenAnim = false; + boolean voiceInteraction = false; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New wallpaper target=" + mWallpaperTarget @@ -8581,6 +8595,8 @@ public class WindowManagerService extends IWindowManager.Stub } } + voiceInteraction |= wtoken.voiceInteraction; + if (wtoken.appFullscreen) { WindowState ws = wtoken.findMainWindow(); if (ws != null) { @@ -8653,7 +8669,7 @@ public class WindowManagerService extends IWindowManager.Stub appAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; appAnimator.animation = null; - setTokenVisibilityLocked(wtoken, animLp, true, transit, false); + setTokenVisibilityLocked(wtoken, animLp, true, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToShow = false; @@ -8685,7 +8701,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.mAppAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; wtoken.mAppAnimator.animation = null; - setTokenVisibilityLocked(wtoken, animLp, false, transit, false); + setTokenVisibilityLocked(wtoken, animLp, false, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToHide = false; // Force the allDrawn flag, because we want to start diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c88382c..4a80e3e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -714,6 +714,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null ? mAppToken.appToken : null; } + @Override + public boolean isVoiceInteraction() { + return mAppToken != null ? mAppToken.voiceInteraction : false; + } + boolean setInsetsChanged() { mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets); mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 1e79dcb..e257ebc 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1658,7 +1658,7 @@ class WindowStateAnimator { break; } if (attr >= 0) { - a = mService.mAppTransition.loadAnimation(mWin.mAttrs, attr); + a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr); } } if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 51583a5..3cfb45b 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \ + $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \ diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp new file mode 100644 index 0000000..f0c4f3a --- /dev/null +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -0,0 +1,388 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "TvInputHal" + +//#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_view_Surface.h" +#include "JNIHelp.h" +#include "jni.h" + +#include <gui/Surface.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/Log.h> +#include <utils/NativeHandle.h> +#include <hardware/tv_input.h> + +namespace android { + +static struct { + jmethodID deviceAvailable; + jmethodID deviceUnavailable; + jmethodID streamConfigsChanged; +} gTvInputHalClassInfo; + +static struct { + jclass clazz; +} gTvStreamConfigClassInfo; + +static struct { + jclass clazz; + + jmethodID constructor; + jmethodID streamId; + jmethodID type; + jmethodID maxWidth; + jmethodID maxHeight; + jmethodID generation; + jmethodID build; +} gTvStreamConfigBuilderClassInfo; + +//////////////////////////////////////////////////////////////////////////////// + +class JTvInputHal { +public: + ~JTvInputHal(); + + static JTvInputHal* createInstance(JNIEnv* env, jobject thiz); + + int setSurface(int deviceId, int streamId, const sp<Surface>& surface); + void getStreamConfigs(int deviceId, jobjectArray* array); + const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs); + +private: + class Connection { + public: + Connection() : mStreamId(0) {} + + sp<Surface> mSurface; + sp<NativeHandle> mSourceHandle; + int mStreamId; + }; + + JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev); + + static void notify( + tv_input_device_t* dev,tv_input_event_t* event, void* data); + + void onDeviceAvailable(const tv_input_device_info_t& info); + void onDeviceUnavailable(int deviceId); + void onStreamConfigurationsChanged(int deviceId); + + jweak mThiz; + tv_input_device_t* mDevice; + tv_input_callback_ops_t mCallback; + + KeyedVector<int, Connection> mConnections; +}; + +JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device) { + mThiz = env->NewWeakGlobalRef(thiz); + mDevice = device; + mCallback.notify = &JTvInputHal::notify; + + mDevice->initialize(mDevice, &mCallback, this); +} + +JTvInputHal::~JTvInputHal() { + mDevice->common.close((hw_device_t*)mDevice); + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mThiz); + mThiz = NULL; +} + +JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz) { + tv_input_module_t* module = NULL; + status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID, + (hw_module_t const**)&module); + if (err) { + ALOGE("Couldn't load %s module (%s)", + TV_INPUT_HARDWARE_MODULE_ID, strerror(-err)); + return 0; + } + + tv_input_device_t* device = NULL; + err = module->common.methods->open( + (hw_module_t*)module, + TV_INPUT_DEFAULT_DEVICE, + (hw_device_t**)&device); + if (err) { + ALOGE("Couldn't open %s device (%s)", + TV_INPUT_DEFAULT_DEVICE, strerror(-err)); + return 0; + } + + return new JTvInputHal(env, thiz, device); +} + +int JTvInputHal::setSurface(int deviceId, int streamId, const sp<Surface>& surface) { + Connection& connection = mConnections.editValueFor(deviceId); + if (connection.mStreamId == streamId && connection.mSurface == surface) { + // Nothing to do + return NO_ERROR; + } + if (Surface::isValid(connection.mSurface)) { + connection.mSurface.clear(); + } + if (surface == NULL) { + if (connection.mSurface != NULL) { + connection.mSurface->setSidebandStream(NULL); + connection.mSurface.clear(); + } + if (connection.mSourceHandle != NULL) { + // Need to reset streams + if (mDevice->close_stream( + mDevice, deviceId, connection.mStreamId) != 0) { + ALOGE("Couldn't remove stream"); + return BAD_VALUE; + } + connection.mSourceHandle.clear(); + } + return NO_ERROR; + } + connection.mSurface = surface; + if (connection.mSourceHandle == NULL) { + // Need to configure stream + int numConfigs = 0; + const tv_stream_config_t* configs = NULL; + if (mDevice->get_stream_configurations( + mDevice, deviceId, &numConfigs, &configs) != 0) { + ALOGE("Couldn't get stream configs"); + return UNKNOWN_ERROR; + } + int configIndex = -1; + for (int i = 0; i < numConfigs; ++i) { + if (configs[i].stream_id == streamId) { + configIndex = i; + break; + } + } + if (configIndex == -1) { + ALOGE("Cannot find a config with given stream ID: %d", streamId); + return BAD_VALUE; + } + // TODO: handle buffer producer profile. + if (configs[configIndex].type != + TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { + ALOGE("Profiles other than independent video source is not yet " + "supported : type = %d", configs[configIndex].type); + return INVALID_OPERATION; + } + tv_stream_t stream; + stream.stream_id = configs[configIndex].stream_id; + if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) { + ALOGE("Couldn't add stream"); + return UNKNOWN_ERROR; + } + connection.mSourceHandle = NativeHandle::create( + stream.sideband_stream_source_handle, false); + connection.mStreamId = stream.stream_id; + connection.mSurface->setSidebandStream(connection.mSourceHandle); + } + return NO_ERROR; +} + +const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { + const tv_stream_config_t* configs = NULL; + if (mDevice->get_stream_configurations( + mDevice, deviceId, numConfigs, &configs) != 0) { + ALOGE("Couldn't get stream configs"); + return NULL; + } + return configs; +} + + +// static +void JTvInputHal::notify( + tv_input_device_t* dev, tv_input_event_t* event, void* data) { + JTvInputHal* thiz = (JTvInputHal*)data; + switch (event->type) { + case TV_INPUT_EVENT_DEVICE_AVAILABLE: { + thiz->onDeviceAvailable(event->device_info); + } break; + case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: { + thiz->onDeviceUnavailable(event->device_info.device_id); + } break; + case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: { + thiz->onStreamConfigurationsChanged(event->device_info.device_id); + } break; + default: + ALOGE("Unrecognizable event"); + } +} + +void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.add(info.device_id, Connection()); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.deviceAvailable, + info.device_id, + info.type); +} + +void JTvInputHal::onDeviceUnavailable(int deviceId) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.removeItem(deviceId); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.deviceUnavailable, + deviceId); +} + +void JTvInputHal::onStreamConfigurationsChanged(int deviceId) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.removeItem(deviceId); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.streamConfigsChanged, + deviceId); +} + +//////////////////////////////////////////////////////////////////////////////// + +static jlong nativeOpen(JNIEnv* env, jobject thiz) { + return (jlong)JTvInputHal::createInstance(env, thiz); +} + +static int nativeSetSurface(JNIEnv* env, jclass clazz, + jlong ptr, jint deviceId, jint streamId, jobject jsurface) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + sp<Surface> surface( + jsurface + ? android_view_Surface_getSurface(env, jsurface) + : NULL); + return tvInputHal->setSurface(deviceId, streamId, surface); +} + +static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, + jlong ptr, jint deviceId, jint generation) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + int numConfigs = 0; + const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); + + jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL); + for (int i = 0; i < numConfigs; ++i) { + jobject builder = env->NewObject( + gTvStreamConfigBuilderClassInfo.clazz, + gTvStreamConfigBuilderClassInfo.constructor); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.generation, generation); + + jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build); + + env->SetObjectArrayElement(result, i, config); + + env->DeleteLocalRef(config); + env->DeleteLocalRef(builder); + } + return result; +} + +static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + delete tvInputHal; +} + +static JNINativeMethod gTvInputHalMethods[] = { + /* name, signature, funcPtr */ + { "nativeOpen", "()J", + (void*) nativeOpen }, + { "nativeSetSurface", "(JIILandroid/view/Surface;)I", + (void*) nativeSetSurface }, + { "nativeGetStreamConfigs", "(JII)[Landroid/tv/TvStreamConfig;", + (void*) nativeGetStreamConfigs }, + { "nativeClose", "(J)V", + (void*) nativeClose }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className) + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName) + +int register_android_server_tv_TvInputHal(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal", + gTvInputHalMethods, NELEM(gTvInputHalMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz; + FIND_CLASS(clazz, "com/android/server/tv/TvInputHal"); + + GET_METHOD_ID( + gTvInputHalClassInfo.deviceAvailable, clazz, "deviceAvailableFromNative", "(II)V"); + GET_METHOD_ID( + gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V"); + GET_METHOD_ID( + gTvInputHalClassInfo.streamConfigsChanged, clazz, + "streamConfigsChangedFromNative", "(I)V"); + + FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/tv/TvStreamConfig"); + gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz)); + + FIND_CLASS(gTvStreamConfigBuilderClassInfo.clazz, "android/tv/TvStreamConfig$Builder"); + gTvStreamConfigBuilderClassInfo.clazz = + jclass(env->NewGlobalRef(gTvStreamConfigBuilderClassInfo.clazz)); + + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.constructor, + gTvStreamConfigBuilderClassInfo.clazz, + "<init>", "()V"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.streamId, + gTvStreamConfigBuilderClassInfo.clazz, + "streamId", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.type, + gTvStreamConfigBuilderClassInfo.clazz, + "type", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.maxWidth, + gTvStreamConfigBuilderClassInfo.clazz, + "maxWidth", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.maxHeight, + gTvStreamConfigBuilderClassInfo.clazz, + "maxHeight", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.generation, + gTvStreamConfigBuilderClassInfo.clazz, + "generation", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.build, + gTvStreamConfigBuilderClassInfo.clazz, + "build", "()Landroid/tv/TvStreamConfig;"); + + return 0; +} + +} /* namespace android */ diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 1feb325..bfa8286 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,6 +41,7 @@ int register_android_server_dreams_McuHal(JNIEnv* env); int register_android_server_hdmi_HdmiCecController(JNIEnv* env); int register_android_server_hdmi_HdmiCecService(JNIEnv* env); int register_android_server_hdmi_HdmiMhlController(JNIEnv* env); +int register_android_server_tv_TvInputHal(JNIEnv* env); }; using namespace android; @@ -78,6 +79,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) // TODO: remove this once replaces HdmiCecService with HdmiControlService. register_android_server_hdmi_HdmiCecService(env); register_android_server_hdmi_HdmiMhlController(env); + register_android_server_tv_TvInputHal(env); return JNI_VERSION_1_4; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index de46b16..0f24ff6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -83,6 +83,7 @@ import com.android.server.power.ShutdownThread; import com.android.server.search.SearchManagerService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; +import com.android.server.task.TaskManagerService; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.twilight.TwilightService; @@ -132,6 +133,8 @@ public final class SystemServer { "com.android.server.hdmi.HdmiCecService"; private static final String ETHERNET_SERVICE_CLASS = "com.android.server.ethernet.EthernetService"; + private static final String TASK_SERVICE_CLASS = + "com.android.server.task.TaskManagerService"; private final int mFactoryTestMode; private Timer mProfilerSnapshotTimer; @@ -183,7 +186,7 @@ public final class SystemServer { // had to fallback to a different runtime because it is // running as root and we need to be the system user to set // the property. http://b/11463182 - SystemProperties.set("persist.sys.dalvik.vm.lib.1", VMRuntime.getRuntime().vmLibrary()); + SystemProperties.set("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary()); // Enable the sampling profiler. if (SamplingProfilerIntegration.isEnabled()) { @@ -831,6 +834,8 @@ public final class SystemServer { mSystemServiceManager.startService(UiModeManagerService.class); + mSystemServiceManager.startService(TaskManagerService.class); + if (!disableNonCoreServices) { try { if (pm.hasSystemFeature(PackageManager.FEATURE_BACKUP)) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 9b6daad..62ff121 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -113,7 +113,7 @@ class VoiceInteractionManagerServiceImpl { if (mBound) { try { mIWindowManager.addWindowToken(mToken, - WindowManager.LayoutParams.TYPE_INPUT_METHOD); + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION); } catch (RemoteException e) { Slog.w(TAG, "Failed adding window token", e); } diff --git a/telecomm/java/android/telecomm/package.html b/telecomm/java/android/telecomm/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/telecomm/java/android/telecomm/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl new file mode 100644 index 0000000..c439211 --- /dev/null +++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telecomm; + +/** + * Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing + * commands that were previously handled by ITelephony. + * {@hide} + */ +oneway interface ITelecommService { + + /** + * Silence the ringer if an incoming call is currently ringing. + * (If vibrating, stop the vibrator also.) + * + * It's safe to call this if the ringer has already been silenced, or + * even if there's no incoming call. (If so, this method will do nothing.) + */ + void silenceRinger(); +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 5d485c5..4aed1fe 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -30,6 +30,7 @@ import android.os.SystemProperties; import android.telephony.Rlog; import android.util.Log; +import com.android.internal.telecomm.ITelecommService; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; @@ -65,6 +66,8 @@ import java.util.regex.Pattern; public class TelephonyManager { private static final String TAG = "TelephonyManager"; + private static final String TELECOMM_SERVICE_NAME = "telecomm"; + private static ITelephonyRegistry sRegistry; /** @@ -1536,6 +1539,10 @@ public class TelephonyManager { return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); } + private ITelecommService getTelecommService() { + return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME)); + } + // // // PhoneStateListener @@ -2016,9 +2023,9 @@ public class TelephonyManager { @PrivateApi public void silenceRinger() { try { - getITelephony().silenceRinger(); + getTelecommService().silenceRinger(); } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#silenceRinger", e); + Log.e(TAG, "Error calling ITelecommService#silenceRinger", e); } } @@ -2249,4 +2256,25 @@ public class TelephonyManager { } return false; } + + /** @hide */ + @PrivateApi + public void setDataEnabled(boolean enable) { + try { + getITelephony().setDataEnabled(enable); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setDataEnabled", e); + } + } + + /** @hide */ + @PrivateApi + public boolean getDataEnabled() { + try { + return getITelephony().getDataEnabled(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getDataEnabled", e); + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index baacb74..6d7f158 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -436,4 +436,18 @@ interface ITelephony { * @return true on success; false on any failure. */ boolean setPreferredNetworkType(int networkType); + + /** + * User enable/disable Mobile Data. + * + * @param enable true to turn on, else false + */ + void setDataEnabled(boolean enable); + + /** + * Get the user enabled state of Mobile Data. + * + * @return true on enabled + */ + boolean getDataEnabled(); } diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java index fa0f995..6876f5a 100644 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java @@ -26,6 +26,7 @@ import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; import android.os.Handler; import android.renderscript.Allocation; import android.renderscript.Element; @@ -86,7 +87,7 @@ public class Camera2Source extends Filter implements Allocation.OnBufferAvailabl @Override public void onCaptureCompleted(CameraDevice camera, CaptureRequest request, - CaptureResult result) { + TotalCaptureResult result) { // TODO Auto-generated method stub Log.v(TAG, "in onCaptureComplete"); diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index db802c5..6b70631 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -902,5 +902,14 @@ <category android:name="com.android.test.hwui.TEST" /> </intent-filter> </activity> + + <activity + android:name=".ZOrderingActivity" + android:label="Reordering/Z Ordering"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.hwui.TEST" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/HwAccelerationTest/res/layout/z_ordering.xml b/tests/HwAccelerationTest/res/layout/z_ordering.xml new file mode 100644 index 0000000..970c5fd --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/z_ordering.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/parent"> + <RelativeLayout + android:layout_width="400dp" + android:layout_height="200dp" + android:layout_weight="1" + android:orientation="vertical"> + <TextView style="@style/TopLeftReorderTextView"/> + <TextView style="@style/BottomLeftReorderTextView"/> + <TextView style="@style/TopRightReorderTextView"/> + <TextView style="@style/BottomRightReorderTextView"/> + </RelativeLayout> +</LinearLayout>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/res/values/styles.xml b/tests/HwAccelerationTest/res/values/styles.xml index cde5d20..108709b 100644 --- a/tests/HwAccelerationTest/res/values/styles.xml +++ b/tests/HwAccelerationTest/res/values/styles.xml @@ -1,34 +1,37 @@ <resources> <style name="ReorderTextView" parent="@android:style/TextAppearance.Medium"> - <item name="android:layout_width">match_parent</item> + <item name="android:background">@drawable/appwidget_background</item> + <item name="android:layout_width">300dp</item> <item name="android:layout_height">100dp</item> <item name="android:gravity">center</item> </style> <style name="LeftReorderTextView" parent="@style/ReorderTextView"> - <item name="android:translationX">20dp</item> + <item name="android:translationX">50dp</item> + <item name="android:layout_alignParentLeft">true</item> </style> <style name="RightReorderTextView" parent="@style/ReorderTextView"> - <item name="android:translationX">-20dp</item> + <item name="android:translationX">-50dp</item> + <item name="android:layout_alignParentRight">true</item> </style> <style name="TopLeftReorderTextView" parent="@style/LeftReorderTextView"> - <item name="android:background">#666</item> - <item name="android:text">100</item> - <item name="android:translationZ">100dp</item> + <item name="android:text">200</item> + <item name="android:translationZ">200dp</item> + <item name="android:layout_alignParentTop">true</item> </style> <style name="BottomLeftReorderTextView" parent="@style/LeftReorderTextView"> - <item name="android:background">#bbb</item> <item name="android:text">300</item> <item name="android:translationZ">300dp</item> + <item name="android:layout_alignParentBottom">true</item> </style> <style name="TopRightReorderTextView" parent="@style/RightReorderTextView"> - <item name="android:background">#888</item> - <item name="android:text">200</item> - <item name="android:translationZ">200dp</item> + <item name="android:text">100</item> + <item name="android:translationZ">100dp</item> + <item name="android:layout_alignParentTop">true</item> </style> <style name="BottomRightReorderTextView" parent="@style/RightReorderTextView"> - <item name="android:background">#ccc</item> <item name="android:text">400</item> <item name="android:translationZ">400dp</item> + <item name="android:layout_alignParentBottom">true</item> </style> </resources> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ZOrderingActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ZOrderingActivity.java new file mode 100644 index 0000000..45e77ed --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ZOrderingActivity.java @@ -0,0 +1,28 @@ +package com.android.test.hwui; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +public class ZOrderingActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.z_ordering); + + ViewGroup grandParent = (ViewGroup) findViewById(R.id.parent); + if (grandParent == null) throw new IllegalStateException(); + View.OnClickListener l = new View.OnClickListener() { + @Override + public void onClick(View v) {} + }; + for (int i = 0; i < grandParent.getChildCount(); i++) { + ViewGroup parent = (ViewGroup) grandParent.getChildAt(i); + for (int j = 0; j < parent.getChildCount(); j++) { + parent.getChildAt(j).setOnClickListener(l); + } + } + } +} diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml index ac0f701..2d08163 100644 --- a/tests/VoiceInteraction/AndroidManifest.xml +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -2,7 +2,8 @@ package="com.android.test.voiceinteraction"> <application> - <activity android:name="VoiceInteractionMain" android:label="Voice Interaction"> + <activity android:name="VoiceInteractionMain" android:label="Voice Interaction" + android:theme="@android:style/Theme.Quantum"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml index 9fcbf3e..563fa44 100644 --- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml +++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml @@ -19,6 +19,7 @@ android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffffff" + android:fitsSystemWindows="true" > <TextView android:id="@+id/text" diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 6f5788a..a6c09f3 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -93,7 +93,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0); + mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false); fail("IWindowManager.addAppToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index cc621c4..105803e 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -826,6 +826,11 @@ public final class BridgeTypedArray extends TypedArray { return null; } + @Override + public int[] extractThemeAttrs() { + return null; + } + /** * Retrieve the raw TypedValue for the attribute at <var>index</var>. * @@ -912,4 +917,9 @@ public final class BridgeTypedArray extends TypedArray { public String toString() { return Arrays.toString(mResourceData); } - } + + static TypedArray obtain(Resources res, int len) { + return res instanceof BridgeResources ? + new BridgeTypedArray(((BridgeResources) res), null, len, true) : null; + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java index 31d1594..f4a9f52 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -97,6 +97,13 @@ public class Resources_Theme_Delegate { return found; } + @LayoutlibDelegate + /*package*/ static TypedArray resolveAttributes(Resources thisResources, Theme thisTheme, + int[] values, int[] attrs) { + // FIXME + return null; + } + // ---- private helper methods ---- private static boolean setupResources(Theme thisTheme) { diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java index 0a7899a..faa8852 100644 --- a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java @@ -27,4 +27,9 @@ public class TypedArray_Delegate { // pass return false; } + + @LayoutlibDelegate + /*package*/ static TypedArray obtain(Resources res, int len) { + return BridgeTypedArray.obtain(res, len); + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 802cf1c..33813d1 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -18,9 +18,11 @@ package android.graphics; import java.awt.Font; import java.awt.Graphics2D; +import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.Rectangle2D; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -36,12 +38,12 @@ import android.graphics.Paint_Delegate.FontInfo; @SuppressWarnings("deprecation") public class BidiRenderer { - /* package */ static class ScriptRun { + /*package*/ static class ScriptRun { int start; int limit; boolean isRtl; int scriptCode; - FontInfo font; + Font font; public ScriptRun(int start, int limit, boolean isRtl) { this.start = start; @@ -51,9 +53,10 @@ public class BidiRenderer { } } - private Graphics2D mGraphics; - private Paint_Delegate mPaint; + private final Graphics2D mGraphics; + private final Paint_Delegate mPaint; private char[] mText; + private List<Font> mFonts; // Bounds of the text drawn so far. private RectF mBounds; private float mBaseline; @@ -63,11 +66,15 @@ public class BidiRenderer { * @param paint The Paint to use to get the fonts. Should not be null. * @param text Unidirectional text. Should not be null. */ - /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { + /*package*/ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { assert (paint != null); mGraphics = graphics; mPaint = paint; mText = text; + mFonts = new ArrayList<Font>(paint.getFonts().size()); + for (FontInfo fontInfo : paint.getFonts()) { + mFonts.add(fontInfo.mFont); + } } /** @@ -94,7 +101,7 @@ public class BidiRenderer { // the script runs. mBounds = new RectF(x, y, x, y); mBaseline = y; - for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) { + for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mFonts)) { int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT; flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT; renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); @@ -108,16 +115,15 @@ public class BidiRenderer { * much as possible. This also implements a fallback mechanism to render characters that cannot * be drawn using the preferred font. */ - private void renderScript(int start, int limit, FontInfo preferredFont, int flag, + private void renderScript(int start, int limit, Font preferredFont, int flag, float[] advances, int advancesIndex, boolean draw) { - List<FontInfo> fonts = mPaint.getFonts(); - if (fonts == null || preferredFont == null) { + if (mFonts.size() == 0 || preferredFont == null) { return; } while (start < limit) { boolean foundFont = false; - int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit); + int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit); if (canDisplayUpTo == -1) { // We can draw all characters in the text. render(start, limit, preferredFont, flag, advances, advancesIndex, draw); @@ -133,8 +139,8 @@ public class BidiRenderer { // The current character cannot be drawn with the preferred font. Cycle through all the // fonts to check which one can draw it. int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1; - for (FontInfo font : fonts) { - canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount); + for (Font font : mFonts) { + canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount); if (canDisplayUpTo == -1) { render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; @@ -160,15 +166,19 @@ public class BidiRenderer { * Renders the text to the right of the bounds with the given font. * @param font The font to render the text with. */ - private void render(int start, int limit, FontInfo font, int flag, float[] advances, + private void render(int start, int limit, Font font, int flag, float[] advances, int advancesIndex, boolean draw) { - // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with - // the anti-aliasing set. - FontRenderContext f = font.mMetrics.getFontRenderContext(); - FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(), - f.usesFractionalMetrics()); - GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag); + FontRenderContext frc; + if (mGraphics != null) { + frc = mGraphics.getFontRenderContext(); + } else { + frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext(); + // Metrics obtained this way don't have anti-aliasing set. So, + // we create a new FontRenderContext with anti-aliasing set. + frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), frc.usesFractionalMetrics()); + } + GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag); int ng = gv.getNumGlyphs(); int[] ci = gv.getGlyphCharIndices(0, ng, null); if (advances != null) { @@ -206,7 +216,7 @@ public class BidiRenderer { } /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, - boolean isRtl, List<FontInfo> fonts) { + boolean isRtl, List<Font> fonts) { LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>(); int count = limit - start; @@ -225,10 +235,10 @@ public class BidiRenderer { // TODO: Replace this method with one which returns the font based on the scriptCode. private static void setScriptFont(char[] text, ScriptRun run, - List<FontInfo> fonts) { - for (FontInfo fontInfo : fonts) { - if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) { - run.font = fontInfo; + List<Font> fonts) { + for (Font font : fonts) { + if (font.canDisplayUpTo(text, run.start, run.limit) == -1) { + run.font = font; return; } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java index cdbbe46..610c867 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -79,13 +79,6 @@ public class BitmapShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate(long native_shader, long native_bitmap, - int shaderTileModeX, int shaderTileModeY) { - // pass, not needed. - return 0; - } - // ---- Private delegate/helper methods ---- private BitmapShader_Delegate(java.awt.image.BufferedImage image, diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 56c0de9..e9daffd 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -979,7 +979,6 @@ public final class Canvas_Delegate { final float startX, final float startY, final int flags, long paint, long typeface) { - // TODO: use typeface. draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -1097,7 +1096,7 @@ public final class Canvas_Delegate { /** * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, GcSnapshot.Drawable drawable) { @@ -1117,7 +1116,7 @@ public final class Canvas_Delegate { * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { // get the delegate from the native int. @@ -1190,12 +1189,6 @@ public final class Canvas_Delegate { return mSnapshot.clipRect(left, top, right, bottom, regionOp); } - private void setBitmap(Bitmap_Delegate bitmap) { - mBitmap = bitmap; - assert mSnapshot.size() == 1; - mSnapshot.setBitmap(mBitmap); - } - private static void drawBitmap( long nativeCanvas, Bitmap_Delegate bitmap, diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index fae8aef..59ddcc6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -78,19 +78,6 @@ public class ComposeShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, long native_mode) { - // pass, not needed. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, int porterDuffMode) { - // pass, not needed. - return 0; - } // ---- Private delegate/helper methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java new file mode 100644 index 0000000..9ea4538 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Font; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static android.graphics.Typeface_Delegate.SYSTEM_FONTS; + +/** + * Delegate implementing the native methods of android.graphics.FontFamily + * + * Through the layoutlib_create tool, the original native methods of FontFamily have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original FontFamily class. + * + * @see DelegateManager + */ +public class FontFamily_Delegate { + + // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked + // separately. + private static final String FONT_SUFFIX_BOLDITALIC = "BoldItalic.ttf"; + private static final String FONT_SUFFIX_BOLD = "Bold.ttf"; + private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; + private static final String FONT_SUBSTRING_COMPACT = "UI"; + + /** + * A class associating {@link Font} with its metadata. + */ + private static final class FontInfo { + Font mFont; + /** Regular, Bold, Italic, or BoldItalic. */ + int mStyle; + /** + * The variant of the Font - compact or elegant. + * @see Paint#setElegantTextHeight(boolean) + */ + boolean mIsCompact; + } + + // ---- delegate manager ---- + private static final DelegateManager<FontFamily_Delegate> sManager = + new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class); + + // ---- delegate helper data ---- + private static String sFontLocation; + private static final List<FontFamily_Delegate> sPostInitDelegate = new + ArrayList<FontFamily_Delegate>(); + + + // ---- delegate data ---- + private List<FontInfo> mFonts = new ArrayList<FontInfo>(); + // Path of fonts that haven't been created since sFontLoader hasn't been initialized. + private List<String> mPath = new ArrayList<String>(); + + + // ---- Public Helper methods ---- + + public static FontFamily_Delegate getDelegate(long nativeFontFamily) { + return sManager.getDelegate(nativeFontFamily); + } + + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + for (FontFamily_Delegate fontFamily : sPostInitDelegate) { + fontFamily.init(); + } + sPostInitDelegate.clear(); + } + + public Font getFont(int style, boolean isCompact) { + FontInfo plainFont = null; + FontInfo styledFont = null; // Font matching the style but not isCompact + for (FontInfo font : mFonts) { + if (font.mStyle == style) { + if (font.mIsCompact == isCompact) { + return font.mFont; + } + styledFont = font; + } + if (font.mStyle == Font.PLAIN) { + if (plainFont == null) { + plainFont = font; + continue; + } + if (font.mIsCompact == isCompact) { + // Override the previous selection of plain font since we've found a better one. + plainFont = font; + } + } + } + if (styledFont != null) { + return styledFont.mFont; + } + + // No font with the mentioned style is found. Try to derive one. + if (plainFont != null && style > 0 && style < 4) { + styledFont = new FontInfo(); + styledFont.mFont = plainFont.mFont.deriveFont(style); + styledFont.mStyle = style; + styledFont.mIsCompact = plainFont.mIsCompact; + // Add the font to the list of fonts so that we don't have to derive it the next time. + mFonts.add(styledFont); + return styledFont.mFont; + } + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static long nCreateFamily() { + FontFamily_Delegate delegate = new FontFamily_Delegate(); + if (sFontLocation != null) { + delegate.init(); + } else { + sPostInitDelegate.add(delegate); + } + return sManager.addNewDelegate(delegate); + } + + @LayoutlibDelegate + /*package*/ static void nUnrefFamily(long nativePtr) { + // Removing the java reference for the object doesn't mean that it's freed for garbage + // collection. Typeface_Delegate may still hold a reference for it. + sManager.removeJavaReferenceFor(nativePtr); + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFont(long nativeFamily, String path) { + FontFamily_Delegate delegate = getDelegate(nativeFamily); + if (delegate != null) { + if (sFontLocation == null) { + delegate.mPath.add(path); + return true; + } + return delegate.addFont(path); + } + return false; + } + + private void init() { + for (String path : mPath) { + addFont(path); + } + mPath = null; + } + + private boolean addFont(String path) { + Font font = loadFont(path); + if (font == null) { + return false; + } + FontInfo fontInfo = new FontInfo(); + fontInfo.mFont = font; + addFontMetadata(fontInfo, path); + // TODO ensure that mFonts doesn't have the font with this style already. + mFonts.add(fontInfo); + return true; + } + + private static void addFontMetadata(FontInfo fontInfo, String path) { + int style = Font.PLAIN; + String fontName = path.substring(path.lastIndexOf('/'), path.length()); + if (fontName.endsWith(FONT_SUFFIX_BOLDITALIC)) { + style = Font.BOLD | Font.ITALIC; + } else if (fontName.endsWith(FONT_SUFFIX_BOLD)) { + style = Font.BOLD; + } else if (fontName.endsWith(FONT_SUFFIX_ITALIC)) { + style = Font.ITALIC; + } + fontInfo.mStyle = style; + + // Names of compact fonts end with UI-<style>.ttf. For example, NotoNakshUI-Regular.ttf. + // This should go away when this info is passed on by nAddFont(). + int hyphenIndex = fontName.lastIndexOf('-'); + fontInfo.mIsCompact = hyphenIndex > 0 && + fontName.substring(0, hyphenIndex).endsWith(FONT_SUBSTRING_COMPACT); + + } + + private static Font loadFont(String path) { + if (path.startsWith(SYSTEM_FONTS) ) { + String relativePath = path.substring(SYSTEM_FONTS.length()); + File f = new File(sFontLocation, relativePath); + + try { + return Font.createFont(Font.TRUETYPE_FONT, f); + } catch (Exception e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unable to load font %1$s", relativePath), + null /*throwable*/, null /*data*/); + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.", + null /*throwable*/, null /*data*/); + } + + return null; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java index ac77377..55c4b98 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -71,22 +71,6 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index 8862f5b..f42f48f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -203,6 +203,16 @@ public final class Matrix_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_isAffine(long native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kPerspective_Mask) == 0; + } + + @LayoutlibDelegate /*package*/ static boolean native_rectStaysRect(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 83df745..911f4e7 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -53,7 +53,7 @@ import java.util.Locale; public class Paint_Delegate { /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + * Class associating a {@link Font} and its {@link java.awt.FontMetrics}. */ /*package*/ static final class FontInfo { Font mFont; @@ -66,8 +66,6 @@ public class Paint_Delegate { // ---- delegate helper data ---- private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); // ---- delegate data ---- private int mFlags; @@ -83,6 +81,7 @@ public class Paint_Delegate { private float mTextScaleX; private float mTextSkewX; private int mHintingMode = Paint.HINTING_ON; + private boolean mIsCompact = true; private Xfermode_Delegate mXfermode; private ColorFilter_Delegate mColorFilter; @@ -101,8 +100,7 @@ public class Paint_Delegate { } /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. + * Returns the list of {@link Font} objects. */ public List<FontInfo> getFonts() { return mFonts; @@ -437,12 +435,20 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { - return false; + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + return delegate != null && !delegate.mIsCompact; } @LayoutlibDelegate /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) { - // TODO + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mIsCompact = !elegant; } @LayoutlibDelegate @@ -621,7 +627,6 @@ public class Paint_Delegate { int inc = count > 0 ? 1 : -1; int measureIndex = 0; - float measureAcc = 0; for (int i = index; i != index + count; i += inc, measureIndex++) { int start, end; if (i < index) { @@ -640,7 +645,6 @@ public class Paint_Delegate { measuredWidth[measureIndex] = res; } - measureAcc += res; if (res > maxWidth) { // we should not return this char index, but since it's 0-based // and we need to return a count, we simply return measureIndex; @@ -818,7 +822,7 @@ public class Paint_Delegate { return filter; } - delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter); // since none of those are supported, display a fidelity warning right away if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { @@ -940,52 +944,17 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_getTextWidths(long native_object, char[] text, int index, - int count, int bidiFlags, float[] widths) { - // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null) { - return 0; - } - - if (delegate.mFonts.size() > 0) { - // FIXME: handle multi-char characters (see measureText) - float totalAdvance = 0; - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : delegate.mFonts) { - if (info.mFont.canDisplay(c)) { - float adv = info.mMetrics.charWidth(c); - totalAdvance += adv; - if (widths != null) { - widths[i] = adv; - } - - found = true; - break; - } - } - - if (found == false) { - // no advance for this char. - if (widths != null) { - widths[i] = 0.f; - } - } - } - - return (int) totalAdvance; - } - - return 0; + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + char[] text, int index, int count, int bidiFlags, float[] widths) { + return (int) native_getTextRunAdvances(native_object, native_typeface, text, index, count, + index, count, bidiFlags, widths, 0); } @LayoutlibDelegate - /*package*/ static int native_getTextWidths(long native_object, String text, int start, - int end, int bidiFlags, float[] widths) { - return native_getTextWidths(native_object, text.toCharArray(), start, end - start, - bidiFlags, widths); + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + String text, int start, int end, int bidiFlags, float[] widths) { + return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, widths); } @LayoutlibDelegate @@ -997,15 +966,20 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float native_getTextRunAdvances(long native_object, + long native_typeface /*ignored*/, char[] text, int index, int count, int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex) { + // native_typeface is passed here since Framework's old implementation did not have the + // typeface object associated with the Paint. Since, we follow the new framework way, + // we store the typeface with the paint and use it directly. + if (advances != null) for (int i = advancesIndex; i< advancesIndex+count; i++) advances[i]=0; // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return 0.f; } boolean isRtl = isRtl(flags); @@ -1017,7 +991,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex) { // FIXME: support contextStart and contextEnd @@ -1025,8 +999,8 @@ public class Paint_Delegate { char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - return native_getTextRunAdvances(native_object, buffer, 0, count, contextStart, - contextEnd - contextStart, flags, advances, advancesIndex); + return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count, + contextStart, contextEnd - contextStart, flags, advances, advancesIndex); } @LayoutlibDelegate @@ -1076,7 +1050,7 @@ public class Paint_Delegate { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return; } delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); @@ -1150,7 +1124,7 @@ public class Paint_Delegate { private void updateFontObject() { if (mTypeface != null) { // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); + List<Font> fonts = mTypeface.getFonts(mIsCompact); // create new font objects as well as FontMetrics, based on the current text size // and skew info. diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java index 4f16dcf..80179ee 100644 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -68,20 +68,6 @@ public class RadialGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float x, float y, float radius, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float x, float y, float radius, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java index 70a0a43..14e9960 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -76,13 +76,12 @@ public abstract class Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void nativeDestructor(long native_shader, long native_skiaShader) { + /*package*/ static void nativeDestructor(long native_shader) { sManager.removeJavaReferenceFor(native_shader); } @LayoutlibDelegate - /*package*/ static void nativeSetLocalMatrix(long native_shader, long native_skiaShader, - long matrix_instance) { + /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) { // get the delegate from the native int. Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); if (shaderDelegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index f2b3e8d..95a57a9 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -62,20 +62,6 @@ public class SweepGradient_Delegate extends Gradient_Delegate { return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float cx, float cy, - int[] colors, float[] positions) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float cx, float cy, - int color0, int color1) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 60cd157..9746b48 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -19,7 +19,6 @@ package android.graphics; import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.AssetManager; @@ -44,100 +43,66 @@ import java.util.List; */ public final class Typeface_Delegate { - private static final String SYSTEM_FONTS = "/system/fonts/"; + public static final String SYSTEM_FONTS = "/system/fonts/"; // ---- delegate manager ---- private static final DelegateManager<Typeface_Delegate> sManager = new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); // ---- delegate helper data ---- - private static final String DEFAULT_FAMILY = "sans-serif"; - - private static FontLoader sFontLoader; - private static final List<Typeface_Delegate> sPostInitDelegate = - new ArrayList<Typeface_Delegate>(); + private static String sFontLocation; // ---- delegate data ---- - private final String mFamily; + private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate. private int mStyle; - private List<Font> mFonts; + private static long sDefaultTypeface; // ---- Public Helper methods ---- - - public static synchronized void init(FontLoader fontLoader) { - sFontLoader = fontLoader; - - for (Typeface_Delegate delegate : sPostInitDelegate) { - delegate.init(); - } - sPostInitDelegate.clear(); + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + FontFamily_Delegate.setFontLocation(fontLocation); } public static Typeface_Delegate getDelegate(long nativeTypeface) { return sManager.getDelegate(nativeTypeface); } - public static List<Font> getFonts(Typeface typeface) { - return getFonts(typeface.native_instance); - } - - public static List<Font> getFonts(long native_int) { - Typeface_Delegate delegate = sManager.getDelegate(native_int); - if (delegate == null) { - return null; + public List<Font> getFonts(boolean compact) { + List<Font> fonts = new ArrayList<Font>(mFontFamilies.length); + for (FontFamily_Delegate ffd : mFontFamilies) { + if (ffd != null) { + Font font = ffd.getFont(mStyle, compact); + if (font != null) { + fonts.add(font); + } + } } - - return delegate.getFonts(); - } - - public List<Font> getFonts() { - return mFonts; + return fonts; } // ---- native methods ---- @LayoutlibDelegate /*package*/ static synchronized long nativeCreate(String familyName, int style) { - if (familyName == null) { - familyName = DEFAULT_FAMILY; - } - if (style < 0) { - style = Typeface.NORMAL; - } - - Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); - } - - return sManager.addNewDelegate(newDelegate); + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Could not find font with family \"" + familyName + "\".", + null /*throwable*/, null /*data*/); + return 0; } @LayoutlibDelegate /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { Typeface_Delegate delegate = sManager.getDelegate(native_instance); if (delegate == null) { - return 0; + delegate = sManager.getDelegate(sDefaultTypeface); } - - Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); + if (delegate == null) { + return 0; } - return sManager.addNewDelegate(newDelegate); + return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style)); } @LayoutlibDelegate @@ -149,31 +114,19 @@ public final class Typeface_Delegate { @LayoutlibDelegate /*package*/ static synchronized long nativeCreateFromFile(String path) { - if (path.startsWith(SYSTEM_FONTS) ) { - String relativePath = path.substring(SYSTEM_FONTS.length()); - File f = new File(sFontLoader.getOsFontsLocation(), relativePath); + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromFile() is not supported.,", null, null); + return 0; + } - try { - Font font = Font.createFont(Font.TRUETYPE_FONT, f); - if (font != null) { - Typeface_Delegate newDelegate = new Typeface_Delegate(font); - return sManager.addNewDelegate(newDelegate); - } - } catch (Exception e) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, - String.format("Unable to load font %1$s", relativePath), - null /*throwable*/, null /*data*/); - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromFile() can only work with platform fonts located in " + - SYSTEM_FONTS, - null /*throwable*/, null /*data*/); + @LayoutlibDelegate + /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) { + FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length]; + for (int i = 0; i < familyArray.length; i++) { + fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]); } - - - // return a copy of the base font - return nativeCreate(null, 0); + Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL); + return sManager.addNewDelegate(delegate); } @LayoutlibDelegate @@ -191,24 +144,20 @@ public final class Typeface_Delegate { return delegate.mStyle; } - // ---- Private delegate/helper methods ---- - - private Typeface_Delegate(String family, int style) { - mFamily = family; - mStyle = style; + @LayoutlibDelegate + /*package*/ static void nativeSetDefault(long native_instance) { + sDefaultTypeface = native_instance; } - private Typeface_Delegate(Font font) { - mFamily = font.getFamily(); - mStyle = Typeface.NORMAL; - - mFonts = sFontLoader.getFallbackFonts(mStyle); - - // insert the font glyph first. - mFonts.add(0, font); + @LayoutlibDelegate + /*package*/ static File getSystemFontConfigLocation() { + return new File(sFontLocation); } - private void init() { - mFonts = sFontLoader.getFont(mFamily, mStyle); + // ---- Private delegate/helper methods ---- + + private Typeface_Delegate(FontFamily_Delegate[] fontFamilies, int style) { + mFontFamilies = fontFamilies; + mStyle = style; } } diff --git a/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java new file mode 100644 index 0000000..a193330 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * Delegate overriding some methods of android.util.Xml + * + * Through the layoutlib_create tool, the original methods of Xml have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + */ +public class Xml_Delegate { + + @LayoutlibDelegate + /*package*/ static XmlPullParser newPullParser() { + try { + KXmlParser parser = new KXmlParser(); + // The prebuilt kxml2 library with the IDE doesn't support DOCECL. +// parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + return parser; + } catch (XmlPullParserException e) { + throw new AssertionError(); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 757cdd2..e1a9719 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -72,7 +72,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7, int arg8) + boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9) throws RemoteException { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index fa8050f..ffab4de 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -26,7 +26,6 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.RenderDrawable; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.layoutlib.bridge.util.DynamicIdMap; @@ -61,7 +60,7 @@ import java.util.concurrent.locks.ReentrantLock; /** * Main entry point of the LayoutLib Bridge. * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #createScene(SceneParams)} + * {@link #createSession(SessionParams)} */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { @@ -147,8 +146,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { if (getClass() != obj.getClass()) return false; IntArray other = (IntArray) obj; - if (!Arrays.equals(mArray, other.mArray)) return false; - return true; + return Arrays.equals(mArray, other.mArray); } } @@ -251,14 +249,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } // load the fonts. - FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); - if (fontLoader != null) { - Typeface_Delegate.init(fontLoader); - } else { - log.error(LayoutLog.TAG_BROKEN, - "Failed create FontLoader in layout lib.", null); - return false; - } + Typeface_Delegate.setFontLocation(fontLocation.getAbsolutePath()); // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java index 936ab4f..e59ccd7 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java @@ -171,7 +171,7 @@ public class ActionBarLayout extends LinearLayout { // Set action bar to be split, if needed. ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar); mActionBarView.setSplitView(splitView); - mActionBarView.setSplitActionBar(mSplit); + mActionBarView.setSplitToolbar(mSplit); inflateMenus(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java deleted file mode 100644 index cc7338a..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * 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.layoutlib.bridge.impl; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import android.graphics.Typeface; - -import java.awt.Font; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Provides {@link Font} object to the layout lib. - * <p/> - * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the - * fonts.xml file located alongside the ttf files. - */ -public final class FontLoader { - private static final String FONTS_SYSTEM = "system_fonts.xml"; - private static final String FONTS_VENDOR = "vendor_fonts.xml"; - private static final String FONTS_FALLBACK = "fallback_fonts.xml"; - - private static final String NODE_FAMILYSET = "familyset"; - private static final String NODE_FAMILY = "family"; - private static final String NODE_NAME = "name"; - private static final String NODE_FILE = "file"; - - private static final String ATTRIBUTE_VARIANT = "variant"; - private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant"; - private static final String FONT_SUFFIX_NONE = ".ttf"; - private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; - private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; - // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked - // separately. - private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; - private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; - - // This must match the values of Typeface styles so that we can use them for indices in this - // array. - private static final int[] AWT_STYLES = new int[] { - Font.PLAIN, - Font.BOLD, - Font.ITALIC, - Font.BOLD | Font.ITALIC - }; - private static int[] DERIVE_BOLD_ITALIC = new int[] { - Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL - }; - private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL }; - private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL }; - - private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>(); - private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>(); - - private final String mOsFontsLocation; - - public static FontLoader create(String fontOsLocation) { - try { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - - // parse the system fonts - FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM); - List<FontInfo> systemFonts = handler.getFontList(); - - - // parse the fallback fonts - handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK); - List<FontInfo> fallbackFonts = handler.getFontList(); - - return new FontLoader(fontOsLocation, systemFonts, fallbackFonts); - } catch (ParserConfigurationException e) { - // return null below - } catch (SAXException e) { - // return null below - } catch (FileNotFoundException e) { - // return null below - } catch (IOException e) { - // return null below - } - - return null; - } - - private static FontHandler parseFontFile(SAXParserFactory parserFactory, - String fontOsLocation, String fontFileName) - throws ParserConfigurationException, SAXException, IOException, FileNotFoundException { - - SAXParser parser = parserFactory.newSAXParser(); - File f = new File(fontOsLocation, fontFileName); - - FontHandler definitionParser = new FontHandler( - fontOsLocation + File.separator); - parser.parse(new FileInputStream(f), definitionParser); - return definitionParser; - } - - private FontLoader(String fontOsLocation, - List<FontInfo> fontList, List<FontInfo> fallBackList) { - mOsFontsLocation = fontOsLocation; - mMainFonts.addAll(fontList); - mFallbackFonts.addAll(fallBackList); - } - - - public String getOsFontsLocation() { - return mOsFontsLocation; - } - - /** - * Returns a {@link Font} object given a family name and a style value (constant in - * {@link Typeface}). - * @param family the family name - * @param style a 1-item array containing the requested style. Based on the font being read - * the actual style may be different. The array contains the actual style after - * the method returns. - * @return the font object or null if no match could be found. - */ - public synchronized List<Font> getFont(String family, int style) { - List<Font> result = new ArrayList<Font>(); - - if (family == null) { - return result; - } - - - // get the font objects from the main list based on family. - for (FontInfo info : mMainFonts) { - if (info.families.contains(family)) { - result.add(info.font[style]); - break; - } - } - - // add all the fallback fonts for the given style - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - - return result; - } - - - public synchronized List<Font> getFallbackFonts(int style) { - List<Font> result = new ArrayList<Font>(); - // add all the fallback fonts - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - return result; - } - - - private final static class FontInfo { - final Font[] font = new Font[4]; // Matches the 4 type-face styles. - final Set<String> families; - - FontInfo() { - families = new HashSet<String>(); - } - } - - private final static class FontHandler extends DefaultHandler { - private final String mOsFontsLocation; - - private FontInfo mFontInfo = null; - private final StringBuilder mBuilder = new StringBuilder(); - private List<FontInfo> mFontList = new ArrayList<FontInfo>(); - private boolean isCompactFont = true; - - private FontHandler(String osFontsLocation) { - super(); - mOsFontsLocation = osFontsLocation; - } - - public List<FontInfo> getFontList() { - return mFontList; - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) - */ - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) - throws SAXException { - if (NODE_FAMILYSET.equals(localName)) { - mFontList = new ArrayList<FontInfo>(); - } else if (NODE_FAMILY.equals(localName)) { - if (mFontList != null) { - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - } else if (NODE_FILE.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) { - isCompactFont = false; - } else { - isCompactFont = true; - } - } - - mBuilder.setLength(0); - - super.startElement(uri, localName, name, attributes); - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) - */ - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - if (isCompactFont) { - mBuilder.append(ch, start, length); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void endElement(String uri, String localName, String name) throws SAXException { - if (NODE_FAMILY.equals(localName)) { - if (mFontInfo != null) { - // if has a normal font file, add to the list - if (mFontInfo.font[Typeface.NORMAL] != null) { - mFontList.add(mFontInfo); - - // create missing font styles, order is important. - if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) { - computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC); - } - if (mFontInfo.font[Typeface.ITALIC] == null) { - computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC); - } - if (mFontInfo.font[Typeface.BOLD] == null) { - computeDerivedFont(Typeface.BOLD, DERIVE_BOLD); - } - } - - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - // handle a new name for an existing Font Info - if (mFontInfo != null) { - String family = trimXmlWhitespaces(mBuilder.toString()); - mFontInfo.families.add(family); - } - } else if (NODE_FILE.equals(localName)) { - // handle a new file for an existing Font Info - if (isCompactFont && mFontInfo != null) { - String fileName = trimXmlWhitespaces(mBuilder.toString()); - Font font = getFont(fileName); - if (font != null) { - if (fileName.endsWith(FONT_SUFFIX_REGULAR)) { - mFontInfo.font[Typeface.NORMAL] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { - mFontInfo.font[Typeface.BOLD] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { - mFontInfo.font[Typeface.BOLD_ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { - mFontInfo.font[Typeface.ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { - mFontInfo.font[Typeface.NORMAL] = font; - } - } - } - } - } - - private Font getFont(String fileName) { - try { - File file = new File(mOsFontsLocation, fileName); - if (file.exists()) { - return Font.createFont(Font.TRUETYPE_FONT, file); - } - } catch (Exception e) { - - } - - return null; - } - - private void computeDerivedFont( int toCompute, int[] basedOnList) { - for (int basedOn : basedOnList) { - if (mFontInfo.font[basedOn] != null) { - mFontInfo.font[toCompute] = - mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]); - return; - } - } - - // we really shouldn't stop there. This means we don't have a NORMAL font... - assert false; - } - - private String trimXmlWhitespaces(String value) { - if (value == null) { - return null; - } - - // look for carriage return and replace all whitespace around it by just 1 space. - int index; - - while ((index = value.indexOf('\n')) != -1) { - // look for whitespace on each side - int left = index - 1; - while (left >= 0) { - if (Character.isWhitespace(value.charAt(left))) { - left--; - } else { - break; - } - } - - int right = index + 1; - int count = value.length(); - while (right < count) { - if (Character.isWhitespace(value.charAt(right))) { - right++; - } else { - break; - } - } - - // remove all between left and right (non inclusive) and replace by a single space. - String leftString = null; - if (left >= 0) { - leftString = value.substring(0, left + 1); - } - String rightString = null; - if (right < count) { - rightString = value.substring(right); - } - - if (leftString != null) { - value = leftString; - if (rightString != null) { - value += " " + rightString; - } - } else { - value = rightString != null ? rightString : ""; - } - } - - // now we un-escape the string - int length = value.length(); - char[] buffer = value.toCharArray(); - - for (int i = 0 ; i < length ; i++) { - if (buffer[i] == '\\') { - if (buffer[i+1] == 'n') { - // replace the char with \n - buffer[i+1] = '\n'; - } - - // offset the rest of the buffer since we go from 2 to 1 char - System.arraycopy(buffer, i+1, buffer, i, length - i - 1); - length--; - } - } - - return new String(buffer, 0, length); - } - - } -} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index bb72a1e..1f7a28e 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -125,15 +125,19 @@ public final class CreateInfo implements ICreateInfo { "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;", "android.content.res.Resources$Theme#obtainStyledAttributes", "android.content.res.Resources$Theme#resolveAttribute", + "android.content.res.Resources$Theme#resolveAttributes", "android.content.res.Resources#localeToLanguageTag", "android.content.res.AssetManager#newTheme", "android.content.res.AssetManager#deleteTheme", "android.content.res.AssetManager#applyThemeStyle", "android.content.res.TypedArray#getValueAt", + "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", + "android.graphics.Typeface#getSystemFontConfigLocation", "android.os.Handler#sendMessageAtTime", "android.os.HandlerThread#run", "android.text.format.DateFormat#is24HourFormat", + "android.util.Xml#newPullParser", "android.view.Choreographer#getRefreshRate", "android.view.Display#updateDisplayInfoLocked", "android.view.Display#getWindowManager", @@ -170,6 +174,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.DiscretePathEffect", "android.graphics.DrawFilter", "android.graphics.EmbossMaskFilter", + "android.graphics.FontFamily", "android.graphics.LayerRasterizer", "android.graphics.LightingColorFilter", "android.graphics.LinearGradient", diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py index ea3dccc..aea3241 100755 --- a/tools/layoutlib/rename_font/build_font.py +++ b/tools/layoutlib/rename_font/build_font.py @@ -15,10 +15,10 @@ # limitations under the License. """ -Rename the PS name of all fonts in the input directory and copy them to the +Rename the PS name of all fonts in the input directories and copy them to the output directory. -Usage: build_font.py /path/to/input_fonts/ /path/to/output_fonts/ +Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/ """ @@ -30,50 +30,86 @@ import os from lxml import etree import shutil import glob +from multiprocessing import Pool + +# global variable +dest_dir = '/tmp' def main(argv): - if len(argv) != 2: - print "Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/" - sys.exit(1) - if not os.path.isdir(argv[0]): - print argv[0] + "is not a valid directory" - sys.exit(1) - if not os.path.isdir(argv[1]): - print argv[1] + "is not a valid directory" - sys.exit(1) + if len(argv) < 2: + sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/') + for directory in argv: + if not os.path.isdir(directory): + sys.exit(directory + ' is not a valid directory') + global dest_dir + dest_dir = argv[-1] + src_dirs = argv[:-1] cwd = os.getcwd() - os.chdir(argv[1]) + os.chdir(dest_dir) files = glob.glob('*') for filename in files: os.remove(filename) os.chdir(cwd) - for filename in os.listdir(argv[0]): - if not os.path.splitext(filename)[1].lower() == ".ttf": - shutil.copy(os.path.join(argv[0], filename), argv[1]) - continue - print os.path.join(argv[0], filename) - old_ttf_path = os.path.join(argv[0], filename) + input_fonts = list() + for src_dir in src_dirs: + for dirname, dirnames, filenames in os.walk(src_dir): + for filename in filenames: + input_path = os.path.join(dirname, filename) + extension = os.path.splitext(filename)[1].lower() + if (extension == '.ttf'): + input_fonts.append(input_path) + elif (extension == '.xml'): + shutil.copy(input_path, dest_dir) + if '.git' in dirnames: + # don't go into any .git directories. + dirnames.remove('.git') + # Create as many threads as the number of CPUs + pool = Pool(processes=None) + pool.map(convert_font, input_fonts) + + +class InvalidFontException(Exception): + pass + +def convert_font(input_path): + filename = os.path.basename(input_path) + print 'Converting font: ' + filename + # the path to the output file. The file name is the fontfilename.ttx + ttx_path = os.path.join(dest_dir, filename) + ttx_path = ttx_path[:-1] + 'x' + try: # run ttx to generate an xml file in the output folder which represents all # its info - ttx_args = ["-d", argv[1], old_ttf_path] + ttx_args = ['-q', '-d', dest_dir, input_path] ttx.main(ttx_args) - # the path to the output file. The file name is the fontfilename.ttx - ttx_path = os.path.join(argv[1], filename) - ttx_path = ttx_path[:-1] + "x" # now parse the xml file to change its PS name. tree = etree.parse(ttx_path) encoding = tree.docinfo.encoding root = tree.getroot() for name in root.iter('name'): [old_ps_name, version] = get_font_info(name) - new_ps_name = old_ps_name + version - update_name(name, new_ps_name) + if old_ps_name is not None and version is not None: + new_ps_name = old_ps_name + version + update_name(name, new_ps_name) tree.write(ttx_path, xml_declaration=True, encoding=encoding ) # generate the udpated font now. - ttx_args = ["-d", argv[1], ttx_path] + ttx_args = ['-q', '-d', dest_dir, ttx_path] ttx.main(ttx_args) - # delete the temp ttx file. + except InvalidFontException: + # In case of invalid fonts, we exit. + print filename + ' is not a valid font' + raise + except Exception as e: + print 'Error converting font: ' + filename + print e + # Some fonts are too big to be handled by the ttx library. + # Just copy paste them. + shutil.copy(input_path, dest_dir) + try: + # delete the temp ttx file is it exists. os.remove(ttx_path) + except OSError: + pass def get_font_info(tag): ps_name = None @@ -85,19 +121,17 @@ def get_font_info(tag): if namerecord.attrib['nameID'] == '6': if ps_name is not None: if not sanitize(namerecord.text) == ps_name: - sys.exit('found multiple possibilities of the font name') + raise InvalidFontException('found multiple possibilities of the font name') else: ps_name = sanitize(namerecord.text) # nameID=5 means the font version if namerecord.attrib['nameID'] == '5': if ps_version is not None: if not ps_version == get_version(namerecord.text): - sys.exit('found multiple possibilities of the font version') + raise InvalidFontException('found multiple possibilities of the font version') else: ps_version = get_version(namerecord.text) - if ps_name is not None and ps_version is not None: - return [ps_name, ps_version] - sys.exit('didn\'t find the font name or version') + return [ps_name, ps_version] def update_name(tag, name): @@ -110,11 +144,11 @@ def sanitize(string): return re.sub(r'[^\w-]+', '', string) def get_version(string): - # The string must begin with "Version n.nn " + # The string must begin with 'Version n.nn ' # to extract n.nn, we return the second entry in the split strings. string = string.strip() - if not string.startswith("Version "): - sys.exit('mal-formed font version') + if not string.startswith('Version '): + raise InvalidFontException('mal-formed font version') return sanitize(string.split()[1]) if __name__ == '__main__': diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index bafc71e..1157de7 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -509,8 +509,6 @@ public class WifiConfiguration implements Parcelable { * @hide */ public boolean isValid() { - if (SSID == null) - return false; if (allowedKeyManagement == null) return false; |