diff options
152 files changed, 2754 insertions, 1518 deletions
diff --git a/api/current.txt b/api/current.txt index 3f3d026..bf6ea89 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5362,7 +5362,7 @@ package android.app.task { public class Task implements android.os.Parcelable { method public int describeContents(); method public int getBackoffPolicy(); - method public android.os.Bundle getExtras(); + method public android.os.PersistableBundle getExtras(); method public int getId(); method public long getInitialBackoffMillis(); method public long getIntervalMillis(); @@ -5386,7 +5386,7 @@ package android.app.task { 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 setExtras(android.os.PersistableBundle); 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); @@ -5413,7 +5413,7 @@ package android.app.task { public class TaskParams implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getExtras(); + method public android.os.PersistableBundle getExtras(); method public int getTaskId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -6751,7 +6751,6 @@ package android.content { method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle); method public deprecated void cancelSync(android.net.Uri); method public static void cancelSync(android.accounts.Account, java.lang.String); - method public static void cancelSync(android.content.ComponentName); method public static void cancelSync(android.content.SyncRequest); method public final android.net.Uri canonicalize(android.net.Uri); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); @@ -6761,18 +6760,14 @@ package android.content { method public static boolean getMasterSyncAutomatically(); method public java.util.List<android.content.UriPermission> getOutgoingPersistedUriPermissions(); method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.accounts.Account, java.lang.String); - method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.content.ComponentName); method public java.util.List<android.content.UriPermission> getPersistedUriPermissions(); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String); method public static android.content.SyncAdapterType[] getSyncAdapterTypes(); method public static boolean getSyncAutomatically(android.accounts.Account, java.lang.String); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public static boolean isServiceActive(android.content.ComponentName); method public static boolean isSyncActive(android.accounts.Account, java.lang.String); - method public static boolean isSyncActive(android.content.ComponentName); method public static boolean isSyncPending(android.accounts.Account, java.lang.String); - method public static boolean isSyncPending(android.content.ComponentName); method public void notifyChange(android.net.Uri, android.database.ContentObserver); method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean); method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; @@ -6794,7 +6789,6 @@ package android.content { method public static void requestSync(android.content.SyncRequest); method public static void setIsSyncable(android.accounts.Account, java.lang.String, int); method public static void setMasterSyncAutomatically(boolean); - method public static void setServiceActive(android.content.ComponentName, boolean); method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean); method public deprecated void startSync(android.net.Uri, android.os.Bundle); method public void takePersistableUriPermission(android.net.Uri, int); @@ -7762,9 +7756,7 @@ package android.content { field public final android.accounts.Account account; field public final java.lang.String authority; field public final android.os.Bundle extras; - field public final boolean isService; field public final long period; - field public final android.content.ComponentName service; } public class ReceiverCallNotAllowedException extends android.util.AndroidRuntimeException { @@ -7880,7 +7872,6 @@ package android.content { method public void writeToParcel(android.os.Parcel, int); field public final android.accounts.Account account; field public final java.lang.String authority; - field public final android.content.ComponentName service; field public final long startTime; } @@ -7903,8 +7894,6 @@ package android.content { method public android.content.SyncRequest.Builder setNoRetry(boolean); method public android.content.SyncRequest.Builder setPriority(int); method public android.content.SyncRequest.Builder setSyncAdapter(android.accounts.Account, java.lang.String); - method public android.content.SyncRequest.Builder setSyncAdapter(android.content.ComponentName); - method public android.content.SyncRequest.Builder setTransferSize(long, long); method public android.content.SyncRequest.Builder syncOnce(); method public android.content.SyncRequest.Builder syncPeriodic(long, long); } @@ -7932,13 +7921,6 @@ package android.content { field public boolean tooManyRetries; } - public abstract class SyncService extends android.app.Service { - ctor public SyncService(); - method public android.os.IBinder onBind(android.content.Intent); - method public abstract void onPerformSync(android.os.Bundle, android.content.SyncResult); - method protected boolean parallelSyncsEnabled(); - } - public class SyncStats implements android.os.Parcelable { ctor public SyncStats(); ctor public SyncStats(android.os.Parcel); @@ -12115,12 +12097,12 @@ 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.TotalCaptureResult); - method public void onCaptureFailed(android.hardware.camera2.CameraDevice, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureFailure); - 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); + method public void onCaptureCompleted(android.hardware.camera2.CameraCaptureSession, android.hardware.camera2.CaptureRequest, android.hardware.camera2.TotalCaptureResult); + method public void onCaptureFailed(android.hardware.camera2.CameraCaptureSession, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureFailure); + method public void onCaptureProgressed(android.hardware.camera2.CameraCaptureSession, android.hardware.camera2.CaptureRequest, android.hardware.camera2.CaptureResult); + method public void onCaptureSequenceAborted(android.hardware.camera2.CameraCaptureSession, int); + method public void onCaptureSequenceCompleted(android.hardware.camera2.CameraCaptureSession, int, long); + method public void onCaptureStarted(android.hardware.camera2.CameraCaptureSession, android.hardware.camera2.CaptureRequest, long); } public static abstract class CameraCaptureSession.StateListener { @@ -12207,7 +12189,7 @@ package android.hardware.camera2 { method public final int hashCode(); } - public abstract interface CameraDevice implements java.lang.AutoCloseable { + public abstract class CameraDevice implements java.lang.AutoCloseable { method public abstract deprecated int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract deprecated int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); @@ -12600,14 +12582,7 @@ package android.hardware.camera2 { public final class DngCreator implements java.lang.AutoCloseable { ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult); method public void close(); - method public android.hardware.camera2.DngCreator setDescription(java.lang.String); - method public android.hardware.camera2.DngCreator setLocation(android.location.Location); - method public android.hardware.camera2.DngCreator setOrientation(int); - method public android.hardware.camera2.DngCreator setThumbnail(android.graphics.Bitmap); - method public android.hardware.camera2.DngCreator setThumbnail(android.media.Image); - method public void writeByteBuffer(java.io.OutputStream, android.util.Size, java.nio.ByteBuffer, long) throws java.io.IOException; method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException; - 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 { @@ -15849,6 +15824,8 @@ package android.media.session { package android.media.tv { public final class TvContract { + method public static final android.net.Uri buildChannelLogoUri(long); + method public static final android.net.Uri buildChannelLogoUri(android.net.Uri); method public static final android.net.Uri buildChannelUri(long); method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName); method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName, boolean); @@ -15906,6 +15883,10 @@ package android.media.tv { field public static final int TYPE_T_DMB = 393216; // 0x60000 } + public static final class TvContract.Channels.Logo { + field public static final java.lang.String CONTENT_DIRECTORY = "logo"; + } + public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns { field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language"; field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; @@ -15914,8 +15895,10 @@ package android.media.tv { field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; + field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description"; field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; + field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final java.lang.String COLUMN_TITLE = "title"; field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program"; @@ -22887,16 +22870,6 @@ package android.provider { field public static final deprecated android.net.Uri DELETED_CONTENT_URI; field public static final deprecated java.lang.String GROUP_ANDROID_STARRED = "Starred in Android"; field public static final deprecated java.lang.String GROUP_MY_CONTACTS = "Contacts"; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable"; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local"; - field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account"; - field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type"; - field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty"; - field public static final java.lang.String _SYNC_ID = "_sync_id"; - field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id"; - field public static final java.lang.String _SYNC_MARK = "_sync_mark"; - field public static final java.lang.String _SYNC_TIME = "_sync_time"; - field public static final java.lang.String _SYNC_VERSION = "_sync_version"; } public static abstract deprecated interface Contacts.GroupsColumns { @@ -22998,19 +22971,9 @@ package android.provider { field public static final deprecated android.net.Uri CONTENT_URI; field public static final deprecated java.lang.String DEFAULT_SORT_ORDER = "name ASC"; field public static final deprecated android.net.Uri DELETED_CONTENT_URI; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable"; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local"; field public static final deprecated java.lang.String PRIMARY_EMAIL_ID = "primary_email"; field public static final deprecated java.lang.String PRIMARY_ORGANIZATION_ID = "primary_organization"; field public static final deprecated java.lang.String PRIMARY_PHONE_ID = "primary_phone"; - field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account"; - field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type"; - field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty"; - field public static final java.lang.String _SYNC_ID = "_sync_id"; - field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id"; - field public static final java.lang.String _SYNC_MARK = "_sync_mark"; - field public static final java.lang.String _SYNC_TIME = "_sync_time"; - field public static final java.lang.String _SYNC_VERSION = "_sync_version"; } public static final deprecated class Contacts.People.ContactMethods implements android.provider.BaseColumns android.provider.Contacts.ContactMethodsColumns android.provider.Contacts.PeopleColumns { @@ -23073,16 +23036,6 @@ package android.provider { field public static final deprecated java.lang.String CONTENT_DIRECTORY = "photo"; field public static final deprecated android.net.Uri CONTENT_URI; field public static final deprecated java.lang.String DEFAULT_SORT_ORDER = "person ASC"; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable"; - field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local"; - field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account"; - field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type"; - field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty"; - field public static final java.lang.String _SYNC_ID = "_sync_id"; - field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id"; - field public static final java.lang.String _SYNC_MARK = "_sync_mark"; - field public static final java.lang.String _SYNC_TIME = "_sync_time"; - field public static final java.lang.String _SYNC_VERSION = "_sync_version"; } public static abstract deprecated interface Contacts.PhotosColumns { @@ -23403,7 +23356,6 @@ package android.provider { protected static abstract interface ContactsContract.ContactOptionsColumns { field public static final java.lang.String CUSTOM_RINGTONE = "custom_ringtone"; field public static final java.lang.String LAST_TIME_CONTACTED = "last_time_contacted"; - field public static final java.lang.String PINNED = "pinned"; field public static final java.lang.String SEND_TO_VOICEMAIL = "send_to_voicemail"; field public static final java.lang.String STARRED = "starred"; field public static final java.lang.String TIMES_CONTACTED = "times_contacted"; @@ -23681,15 +23633,6 @@ package android.provider { field public static final int UNDEFINED = 0; // 0x0 } - public static final class ContactsContract.PinnedPositions { - ctor public ContactsContract.PinnedPositions(); - field public static final int DEMOTED = -1; // 0xffffffff - field public static final java.lang.String STAR_WHEN_PINNING = "star_when_pinning"; - field public static final java.lang.String UNDEMOTE = "undemote"; - field public static final int UNPINNED = 2147483647; // 0x7fffffff - field public static final android.net.Uri UPDATE_URI; - } - public static final class ContactsContract.Preferences { ctor public ContactsContract.Preferences(); field public static final java.lang.String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER"; @@ -27459,50 +27402,6 @@ package android.telephony { field public static final android.os.Parcelable.Creator CREATOR; } - public class DisconnectCause { - method public static java.lang.String toString(int); - field public static final int BUSY = 4; // 0x4 - field public static final int CALL_BARRED = 20; // 0x14 - field public static final int CDMA_ACCESS_BLOCKED = 35; // 0x23 - field public static final int CDMA_ACCESS_FAILURE = 32; // 0x20 - field public static final int CDMA_DROP = 27; // 0x1b - field public static final int CDMA_INTERCEPT = 28; // 0x1c - field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 26; // 0x1a - field public static final int CDMA_NOT_EMERGENCY = 34; // 0x22 - field public static final int CDMA_PREEMPTED = 33; // 0x21 - field public static final int CDMA_REORDER = 29; // 0x1d - field public static final int CDMA_RETRY_ORDER = 31; // 0x1f - field public static final int CDMA_SO_REJECT = 30; // 0x1e - field public static final int CONGESTION = 5; // 0x5 - field public static final int CS_RESTRICTED = 22; // 0x16 - field public static final int CS_RESTRICTED_EMERGENCY = 24; // 0x18 - field public static final int CS_RESTRICTED_NORMAL = 23; // 0x17 - field public static final int ERROR_UNSPECIFIED = 36; // 0x24 - field public static final int FDN_BLOCKED = 21; // 0x15 - field public static final int ICC_ERROR = 19; // 0x13 - field public static final int INCOMING_MISSED = 1; // 0x1 - field public static final int INCOMING_REJECTED = 16; // 0x10 - field public static final int INVALID_CREDENTIALS = 10; // 0xa - field public static final int INVALID_NUMBER = 7; // 0x7 - field public static final int LIMIT_EXCEEDED = 15; // 0xf - field public static final int LOCAL = 3; // 0x3 - field public static final int LOST_SIGNAL = 14; // 0xe - field public static final int MAXIMUM_VALID_VALUE = 36; // 0x24 - field public static final int MINIMUM_VALID_VALUE = 0; // 0x0 - field public static final int MMI = 6; // 0x6 - field public static final int NORMAL = 2; // 0x2 - field public static final int NOT_DISCONNECTED = 0; // 0x0 - field public static final int NOT_VALID = -1; // 0xffffffff - field public static final int NUMBER_UNREACHABLE = 8; // 0x8 - field public static final int OUT_OF_NETWORK = 11; // 0xb - field public static final int OUT_OF_SERVICE = 18; // 0x12 - field public static final int POWER_OFF = 17; // 0x11 - field public static final int SERVER_ERROR = 12; // 0xc - field public static final int SERVER_UNREACHABLE = 9; // 0x9 - field public static final int TIMED_OUT = 13; // 0xd - field public static final int UNOBTAINABLE_NUMBER = 25; // 0x19 - } - public class NeighboringCellInfo implements android.os.Parcelable { ctor public deprecated NeighboringCellInfo(); ctor public deprecated NeighboringCellInfo(int, int); @@ -27554,7 +27453,7 @@ package android.telephony { method public static boolean isEmergencyNumber(java.lang.String); method public static boolean isGlobalPhoneNumber(java.lang.String); method public static boolean isISODigit(char); - method public static boolean isLocalEmergencyNumber(java.lang.String, android.content.Context); + method public static boolean isLocalEmergencyNumber(android.content.Context, java.lang.String); method public static final boolean isNonSeparator(char); method public static final boolean isReallyDialable(char); method public static final boolean isStartsPostDial(char); @@ -32050,7 +31949,6 @@ package android.view { method protected int computeVerticalScrollRange(); method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(); method public void createContextMenu(android.view.ContextMenu); - method public final android.animation.ValueAnimator createRevealAnimator(int, int, float, float); method public void destroyDrawingCache(); method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets); method public void dispatchConfigurationChanged(android.content.res.Configuration); @@ -32698,6 +32596,10 @@ package android.view { method public abstract boolean onTouch(android.view.View, android.view.MotionEvent); } + public class ViewAnimationUtils { + method public static final android.animation.ValueAnimator createCircularReveal(android.view.View, int, int, float, float); + } + public class ViewConfiguration { ctor public deprecated ViewConfiguration(); method public static android.view.ViewConfiguration get(android.content.Context); @@ -33857,11 +33759,17 @@ package android.view.accessibility { public static final class CaptioningManager.CaptionStyle { method public android.graphics.Typeface getTypeface(); + method public boolean hasBackgroundColor(); + method public boolean hasEdgeColor(); + method public boolean hasEdgeType(); + method public boolean hasForegroundColor(); + method public boolean hasWindowColor(); field public static final int EDGE_TYPE_DEPRESSED = 4; // 0x4 field public static final int EDGE_TYPE_DROP_SHADOW = 2; // 0x2 field public static final int EDGE_TYPE_NONE = 0; // 0x0 field public static final int EDGE_TYPE_OUTLINE = 1; // 0x1 field public static final int EDGE_TYPE_RAISED = 3; // 0x3 + field public static final int EDGE_TYPE_UNSPECIFIED = -1; // 0xffffffff field public final int backgroundColor; field public final int edgeColor; field public final int edgeType; diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java index ca4aeb2..87d57fb 100644 --- a/core/java/android/app/task/Task.java +++ b/core/java/android/app/task/Task.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; /** * Container of data passed to the {@link android.app.task.TaskManager} fully encapsulating the @@ -37,6 +38,18 @@ public class Task implements Parcelable { } /** + * Amount of backoff a task has initially by default, in milliseconds. + * @hide. + */ + public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 5000L; + + /** + * Default type of backoff. + * @hide + */ + public static final int DEFAULT_BACKOFF_POLICY = BackoffPolicy.EXPONENTIAL; + + /** * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1 * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1 */ @@ -47,7 +60,7 @@ public class Task implements Parcelable { private final int taskId; // TODO: Change this to use PersistableBundle when that lands in master. - private final Bundle extras; + private final PersistableBundle extras; private final ComponentName service; private final boolean requireCharging; private final boolean requireDeviceIdle; @@ -71,7 +84,7 @@ public class Task implements Parcelable { /** * Bundle of extras which are returned to your application at execution time. */ - public Bundle getExtras() { + public PersistableBundle getExtras() { return extras; } @@ -171,7 +184,7 @@ public class Task implements Parcelable { private Task(Parcel in) { taskId = in.readInt(); - extras = in.readBundle(); + extras = in.readPersistableBundle(); service = ComponentName.readFromParcel(in); requireCharging = in.readInt() == 1; requireDeviceIdle = in.readInt() == 1; @@ -188,7 +201,7 @@ public class Task implements Parcelable { private Task(Task.Builder b) { taskId = b.mTaskId; - extras = new Bundle(b.mExtras); + extras = new PersistableBundle(b.mExtras); service = b.mTaskService; requireCharging = b.mRequiresCharging; requireDeviceIdle = b.mRequiresDeviceIdle; @@ -211,7 +224,7 @@ public class Task implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(taskId); - out.writeBundle(extras); + out.writePersistableBundle(extras); ComponentName.writeToParcel(service, out); out.writeInt(requireCharging ? 1 : 0); out.writeInt(requireDeviceIdle ? 1 : 0); @@ -238,12 +251,10 @@ public class Task implements Parcelable { } }; - /** - * Builder class for constructing {@link Task} objects. - */ + /** Builder class for constructing {@link Task} objects. */ public static final class Builder { private int mTaskId; - private Bundle mExtras; + private PersistableBundle mExtras = PersistableBundle.EMPTY; private ComponentName mTaskService; // Requirements. private boolean mRequiresCharging; @@ -258,8 +269,8 @@ public class Task implements Parcelable { private boolean mHasLateConstraint; private long mIntervalMillis; // Back-off parameters. - private long mInitialBackoffMillis = 5000L; - private int mBackoffPolicy = BackoffPolicy.EXPONENTIAL; + private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS; + private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY; /** Easy way to track whether the client has tried to set a back-off policy. */ private boolean mBackoffPolicySet = false; @@ -279,7 +290,7 @@ public class Task implements Parcelable { * Set optional extras. This is persisted, so we only allow primitive types. * @param extras Bundle containing extras you want the scheduler to hold on to for you. */ - public Builder setExtras(Bundle extras) { + public Builder setExtras(PersistableBundle extras) { mExtras = extras; return this; } @@ -394,18 +405,13 @@ public class Task implements Parcelable { * @return The task object to hand to the TaskManager. This object is immutable. */ public Task build() { - if (mExtras == null) { - mExtras = Bundle.EMPTY; - } - if (mTaskId < 0) { - throw new IllegalArgumentException("Task id must be greater than 0."); - } + mExtras = new PersistableBundle(mExtras); // Make our own copy. // Check that a deadline was not set on a periodic task. - if (mIsPeriodic && mHasLateConstraint) { + if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) { throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " + "periodic task."); } - if (mIsPeriodic && mHasEarlyConstraint) { + if (mIsPeriodic && (mMinLatencyMillis != 0L)) { throw new IllegalArgumentException("Can't call setMinimumLatency() on a " + "periodic task"); } diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java index dacb348..f4908c6 100644 --- a/core/java/android/app/task/TaskParams.java +++ b/core/java/android/app/task/TaskParams.java @@ -16,10 +16,10 @@ package android.app.task; -import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; /** * Contains the parameters used to configure/identify your task. You do not create this object @@ -28,11 +28,11 @@ import android.os.Parcelable; public class TaskParams implements Parcelable { private final int taskId; - private final Bundle extras; + private final PersistableBundle extras; private final IBinder callback; /** @hide */ - public TaskParams(int taskId, Bundle extras, IBinder callback) { + public TaskParams(int taskId, PersistableBundle extras, IBinder callback) { this.taskId = taskId; this.extras = extras; this.callback = callback; @@ -47,10 +47,10 @@ public class TaskParams implements Parcelable { /** * @return The extras you passed in when constructing this task with - * {@link android.app.task.Task.Builder#setExtras(android.os.Bundle)}. This will + * {@link android.app.task.Task.Builder#setExtras(android.os.PersistableBundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. */ - public Bundle getExtras() { + public PersistableBundle getExtras() { return extras; } @@ -61,7 +61,7 @@ public class TaskParams implements Parcelable { private TaskParams(Parcel in) { taskId = in.readInt(); - extras = in.readBundle(); + extras = in.readPersistableBundle(); callback = in.readStrongBinder(); } @@ -73,7 +73,7 @@ public class TaskParams implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(taskId); - dest.writeBundle(extras); + dest.writePersistableBundle(extras); dest.writeStrongBinder(callback); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 7642e13..392bfbc 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1839,19 +1839,6 @@ public abstract class ContentResolver { } /** - * Cancel any active or pending syncs that are running on this service. - * - * @param cname the service for which to cancel all active/pending operations. - */ - public static void cancelSync(ComponentName cname) { - try { - getContentService().cancelSync(null, null, cname); - } catch (RemoteException e) { - - } - } - - /** * Get information about the SyncAdapters that are known to the system. * @return an array of SyncAdapters that have registered with the system */ @@ -1991,13 +1978,13 @@ public abstract class ContentResolver { /** * Remove the specified sync. This will cancel any pending or active syncs. If the request is * for a periodic sync, this call will remove any future occurrences. - * <p>If a periodic sync is specified, the caller must hold the permission - * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. If this SyncRequest targets a - * SyncService adapter,the calling application must be signed with the same certificate as the - * adapter. - *</p>It is possible to cancel a sync using a SyncRequest object that is not the same object + * <p> + * If a periodic sync is specified, the caller must hold the permission + * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. + *</p> + * It is possible to cancel a sync using a SyncRequest object that is not the same object * with which you requested the sync. Do so by building a SyncRequest with the same - * service/adapter, frequency, <b>and</b> extras bundle. + * adapter, frequency, <b>and</b> extras bundle. * * @param request SyncRequest object containing information about sync to cancel. */ @@ -2031,22 +2018,6 @@ public abstract class ContentResolver { } /** - * Return periodic syncs associated with the provided component. - * <p>The calling application must be signed with the same certificate as the target component, - * otherwise this call will fail. - */ - public static List<PeriodicSync> getPeriodicSyncs(ComponentName cname) { - if (cname == null) { - throw new IllegalArgumentException("Component must not be null"); - } - try { - return getContentService().getPeriodicSyncs(null, null, cname); - } catch (RemoteException e) { - throw new RuntimeException("the ContentService should always be reachable", e); - } - } - - /** * Check if this account/provider is syncable. * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. @@ -2076,38 +2047,6 @@ public abstract class ContentResolver { } /** - * Set whether the provided {@link SyncService} is available to process work. - * <p>This method requires the caller to hold the permission - * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. - * <p>The calling application must be signed with the same certificate as the target component, - * otherwise this call will fail. - */ - public static void setServiceActive(ComponentName cname, boolean active) { - try { - getContentService().setServiceActive(cname, active); - } catch (RemoteException e) { - // exception ignored; if this is thrown then it means the runtime is in the midst of - // being restarted - } - } - - /** - * Query the state of this sync service. - * <p>Set with {@link #setServiceActive(ComponentName cname, boolean active)}. - * <p>The calling application must be signed with the same certificate as the target component, - * otherwise this call will fail. - * @param cname ComponentName referring to a {@link SyncService} - * @return true if jobs will be run on this service, false otherwise. - */ - public static boolean isServiceActive(ComponentName cname) { - try { - return getContentService().isServiceActive(cname); - } catch (RemoteException e) { - throw new RuntimeException("the ContentService should always be reachable", e); - } - } - - /** * Gets the master auto-sync setting that applies to all the providers and accounts. * If this is false then the per-provider auto-sync setting is ignored. * <p>This method requires the caller to hold the permission @@ -2164,17 +2103,6 @@ public abstract class ContentResolver { } } - public static boolean isSyncActive(ComponentName cname) { - if (cname == null) { - throw new IllegalArgumentException("component name must not be null"); - } - try { - return getContentService().isSyncActive(null, null, cname); - } catch (RemoteException e) { - throw new RuntimeException("the ContentService should always be reachable", e); - } - } - /** * If a sync is active returns the information about it, otherwise returns null. * <p> @@ -2249,14 +2177,6 @@ public abstract class ContentResolver { } } - public static boolean isSyncPending(ComponentName cname) { - try { - return getContentService().isSyncPending(null, null, cname); - } catch (RemoteException e) { - throw new RuntimeException("the ContentService should always be reachable", e); - } - } - /** * Request notifications when the different aspects of the SyncManager change. The * different items that can be requested are: diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index 73a76e8..373f2fb 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -121,19 +121,6 @@ interface IContentService { */ void setIsSyncable(in Account account, String providerName, int syncable); - /** - * Corresponds roughly to setIsSyncable(String account, String provider) for syncs that bind - * to a SyncService. - */ - void setServiceActive(in ComponentName cname, boolean active); - - /** - * Corresponds roughly to getIsSyncable(String account, String provider) for syncs that bind - * to a SyncService. - * @return 0 if this SyncService is not enabled, 1 if enabled, <0 if unknown. - */ - boolean isServiceActive(in ComponentName cname); - void setMasterSyncAutomatically(boolean flag); boolean getMasterSyncAutomatically(); diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java index 836c6f8..3efd89a 100644 --- a/core/java/android/content/PeriodicSync.java +++ b/core/java/android/content/PeriodicSync.java @@ -29,14 +29,10 @@ public class PeriodicSync implements Parcelable { public final Account account; /** The authority of the sync. Can be null. */ public final String authority; - /** The service for syncing, if this is an anonymous sync. Can be null.*/ - public final ComponentName service; /** Any extras that parameters that are to be passed to the sync adapter. */ public final Bundle extras; /** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */ public final long period; - /** Whether this periodic sync runs on a {@link SyncService}. */ - public final boolean isService; /** * How much flexibility can be taken in scheduling the sync, in seconds. * {@hide} @@ -44,16 +40,11 @@ public class PeriodicSync implements Parcelable { public final long flexTime; /** - * Creates a new PeriodicSync, copying the Bundle. SM no longer uses this ctor - kept around - * becuse it is part of the API. - * Note - even calls to the old API will not use this ctor, as - * they are given a default flex time. + * Creates a new PeriodicSync, copying the Bundle. This constructor is no longer used. */ public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) { this.account = account; this.authority = authority; - this.service = null; - this.isService = false; if (extras == null) { this.extras = new Bundle(); } else { @@ -71,8 +62,6 @@ public class PeriodicSync implements Parcelable { public PeriodicSync(PeriodicSync other) { this.account = other.account; this.authority = other.authority; - this.service = other.service; - this.isService = other.isService; this.extras = new Bundle(other.extras); this.period = other.period; this.flexTime = other.flexTime; @@ -86,40 +75,14 @@ public class PeriodicSync implements Parcelable { long period, long flexTime) { this.account = account; this.authority = authority; - this.service = null; - this.isService = false; - this.extras = new Bundle(extras); - this.period = period; - this.flexTime = flexTime; - } - - /** - * A PeriodicSync for a sync with a specified SyncService. - * {@hide} - */ - public PeriodicSync(ComponentName service, Bundle extras, - long period, - long flexTime) { - this.account = null; - this.authority = null; - this.service = service; - this.isService = true; this.extras = new Bundle(extras); this.period = period; this.flexTime = flexTime; } private PeriodicSync(Parcel in) { - this.isService = (in.readInt() != 0); - if (this.isService) { - this.service = in.readParcelable(null); - this.account = null; - this.authority = null; - } else { - this.account = in.readParcelable(null); - this.authority = in.readString(); - this.service = null; - } + this.account = in.readParcelable(null); + this.authority = in.readString(); this.extras = in.readBundle(); this.period = in.readLong(); this.flexTime = in.readLong(); @@ -132,13 +95,8 @@ public class PeriodicSync implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(isService ? 1 : 0); - if (account == null && authority == null) { - dest.writeParcelable(service, flags); - } else { - dest.writeParcelable(account, flags); - dest.writeString(authority); - } + dest.writeParcelable(account, flags); + dest.writeString(authority); dest.writeBundle(extras); dest.writeLong(period); dest.writeLong(flexTime); @@ -165,24 +123,14 @@ public class PeriodicSync implements Parcelable { return false; } final PeriodicSync other = (PeriodicSync) o; - if (this.isService != other.isService) { - return false; - } - boolean equal = false; - if (this.isService) { - equal = service.equals(other.service); - } else { - equal = account.equals(other.account) - && authority.equals(other.authority); - } - return equal - && period == other.period - && syncExtrasEquals(extras, other.extras); + return account.equals(other.account) + && authority.equals(other.authority) + && period == other.period + && syncExtrasEquals(extras, other.extras); } /** - * Periodic sync extra comparison function. Duplicated from - * {@link com.android.server.content.SyncManager#syncExtrasEquals(Bundle b1, Bundle b2)} + * Periodic sync extra comparison function. * {@hide} */ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) { @@ -207,7 +155,6 @@ public class PeriodicSync implements Parcelable { public String toString() { return "account: " + account + ", authority: " + authority + - ", service: " + service + ". period: " + period + "s " + ", flex: " + flexTime; } diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java index 146dd99..a586d6f 100644 --- a/core/java/android/content/SyncInfo.java +++ b/core/java/android/content/SyncInfo.java @@ -28,24 +28,16 @@ public class SyncInfo implements Parcelable { public final int authorityId; /** - * The {@link Account} that is currently being synced. Will be null if this sync is running via - * a {@link SyncService}. + * The {@link Account} that is currently being synced. */ public final Account account; /** - * The authority of the provider that is currently being synced. Will be null if this sync - * is running via a {@link SyncService}. + * The authority of the provider that is currently being synced. */ public final String authority; /** - * The {@link SyncService} that is targeted by this operation. Null if this sync is running via - * a {@link AbstractThreadedSyncAdapter}. - */ - public final ComponentName service; - - /** * The start time of the current sync operation in milliseconds since boot. * This is represented in elapsed real time. * See {@link android.os.SystemClock#elapsedRealtime()}. @@ -53,13 +45,11 @@ public class SyncInfo implements Parcelable { public final long startTime; /** @hide */ - public SyncInfo(int authorityId, Account account, String authority, ComponentName service, - long startTime) { + public SyncInfo(int authorityId, Account account, String authority, long startTime) { this.authorityId = authorityId; this.account = account; this.authority = authority; this.startTime = startTime; - this.service = service; } /** @hide */ @@ -68,7 +58,6 @@ public class SyncInfo implements Parcelable { this.account = new Account(other.account.name, other.account.type); this.authority = other.authority; this.startTime = other.startTime; - this.service = other.service; } /** @hide */ @@ -82,8 +71,6 @@ public class SyncInfo implements Parcelable { parcel.writeParcelable(account, flags); parcel.writeString(authority); parcel.writeLong(startTime); - parcel.writeParcelable(service, flags); - } /** @hide */ @@ -92,7 +79,6 @@ public class SyncInfo implements Parcelable { account = parcel.readParcelable(Account.class.getClassLoader()); authority = parcel.readString(); startTime = parcel.readLong(); - service = parcel.readParcelable(ComponentName.class.getClassLoader()); } /** @hide */ diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java index 9ba45ca..869f85c 100644 --- a/core/java/android/content/SyncRequest.java +++ b/core/java/android/content/SyncRequest.java @@ -27,23 +27,11 @@ public class SyncRequest implements Parcelable { private final Account mAccountToSync; /** Authority string that corresponds to a ContentProvider. */ private final String mAuthority; - /** {@link SyncService} identifier. */ - private final ComponentName mComponentInfo; /** Bundle containing user info as well as sync settings. */ private final Bundle mExtras; /** Don't allow this sync request on metered networks. */ private final boolean mDisallowMetered; /** - * Anticipated upload size in bytes. - * TODO: Not yet used - we put this information into the bundle for simplicity. - */ - private final long mTxBytes; - /** - * Anticipated download size in bytes. - * TODO: Not yet used - we put this information into the bundle. - */ - private final long mRxBytes; - /** * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be * started. */ @@ -75,25 +63,12 @@ public class SyncRequest implements Parcelable { /** * {@hide} - * @return true if this sync uses an account/authority pair, or false if - * this is an anonymous sync bound to an @link AnonymousSyncService. - */ - public boolean hasAuthority() { - return mIsAuthority; - } - - /** - * {@hide} * * @return account object for this sync. * @throws IllegalArgumentException if this function is called for a request that targets a * sync service. */ public Account getAccount() { - if (!hasAuthority()) { - throw new IllegalArgumentException("Cannot getAccount() for a sync that targets a sync" - + "service."); - } return mAccountToSync; } @@ -105,30 +80,11 @@ public class SyncRequest implements Parcelable { * sync service. */ public String getProvider() { - if (!hasAuthority()) { - throw new IllegalArgumentException("Cannot getProvider() for a sync that targets a" - + "sync service."); - } return mAuthority; } /** * {@hide} - * Throws a runtime IllegalArgumentException if this function is called for a - * SyncRequest that is bound to an account/provider. - * - * @return ComponentName for the service that this sync will bind to. - */ - public ComponentName getService() { - if (hasAuthority()) { - throw new IllegalArgumentException( - "Cannot getAnonymousService() for a sync that has specified a provider."); - } - return mComponentInfo; - } - - /** - * {@hide} * Retrieve bundle for this SyncRequest. Will not be null. */ public Bundle getBundle() { @@ -175,16 +131,10 @@ public class SyncRequest implements Parcelable { parcel.writeLong(mSyncRunTimeSecs); parcel.writeInt((mIsPeriodic ? 1 : 0)); parcel.writeInt((mDisallowMetered ? 1 : 0)); - parcel.writeLong(mTxBytes); - parcel.writeLong(mRxBytes); parcel.writeInt((mIsAuthority ? 1 : 0)); parcel.writeInt((mIsExpedited? 1 : 0)); - if (mIsAuthority) { - parcel.writeParcelable(mAccountToSync, flags); - parcel.writeString(mAuthority); - } else { - parcel.writeParcelable(mComponentInfo, flags); - } + parcel.writeParcelable(mAccountToSync, flags); + parcel.writeString(mAuthority); } private SyncRequest(Parcel in) { @@ -193,19 +143,10 @@ public class SyncRequest implements Parcelable { mSyncRunTimeSecs = in.readLong(); mIsPeriodic = (in.readInt() != 0); mDisallowMetered = (in.readInt() != 0); - mTxBytes = in.readLong(); - mRxBytes = in.readLong(); mIsAuthority = (in.readInt() != 0); mIsExpedited = (in.readInt() != 0); - if (mIsAuthority) { - mComponentInfo = null; - mAccountToSync = in.readParcelable(null); - mAuthority = in.readString(); - } else { - mComponentInfo = in.readParcelable(null); - mAccountToSync = null; - mAuthority = null; - } + mAccountToSync = in.readParcelable(null); + mAuthority = in.readString(); } /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */ @@ -214,7 +155,6 @@ public class SyncRequest implements Parcelable { mSyncRunTimeSecs = b.mSyncRunTimeSecs; mAccountToSync = b.mAccount; mAuthority = b.mAuthority; - mComponentInfo = b.mComponentName; mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC); mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER); mIsExpedited = b.mExpedited; @@ -223,8 +163,6 @@ public class SyncRequest implements Parcelable { // TODO: pass the configuration extras through separately. mExtras.putAll(b.mSyncConfigExtras); mDisallowMetered = b.mDisallowMetered; - mTxBytes = b.mTxBytes; - mRxBytes = b.mRxBytes; } /** @@ -240,8 +178,6 @@ public class SyncRequest implements Parcelable { private static final int SYNC_TYPE_ONCE = 2; /** Unknown sync target. */ private static final int SYNC_TARGET_UNKNOWN = 0; - /** Specify that this is an anonymous sync. */ - private static final int SYNC_TARGET_SERVICE = 1; /** Specify that this is a sync with a provider. */ private static final int SYNC_TARGET_ADAPTER = 2; /** @@ -275,7 +211,7 @@ public class SyncRequest implements Parcelable { * Whether this builder is building a periodic sync, or a one-time sync. */ private int mSyncType = SYNC_TYPE_UNKNOWN; - /** Whether this will go to a sync adapter or to a sync service. */ + /** Whether this will go to a sync adapter. */ private int mSyncTarget = SYNC_TARGET_UNKNOWN; /** Whether this is a user-activated sync. */ private boolean mIsManual; @@ -298,12 +234,6 @@ public class SyncRequest implements Parcelable { private boolean mExpedited; /** - * The {@link SyncService} component that - * contains the sync logic if this is a provider-less sync, otherwise - * null. - */ - private ComponentName mComponentName; - /** * The Account object that together with an Authority name define the SyncAdapter (if * this sync is bound to a provider), otherwise null. */ @@ -336,7 +266,7 @@ public class SyncRequest implements Parcelable { /** * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder. - * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the + * Syncs are identified by target {@link android.provider} and by the * contents of the extras bundle. * You cannot reuse the same builder for one-time syncs after having specified a periodic * sync (by calling this function). If you do, an <code>IllegalArgumentException</code> @@ -395,23 +325,10 @@ public class SyncRequest implements Parcelable { } /** - * Developer can provide insight into their payload size; optional. -1 specifies unknown, - * so that you are not restricted to defining both fields. - * - * @param rxBytes Bytes expected to be downloaded. - * @param txBytes Bytes expected to be uploaded. - */ - public Builder setTransferSize(long rxBytes, long txBytes) { - mRxBytes = rxBytes; - mTxBytes = txBytes; - return this; - } - - /** * Will throw an <code>IllegalArgumentException</code> if called and * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called. * @param disallow true to allow this transfer on metered networks. Default false. - * + * */ public Builder setDisallowMetered(boolean disallow) { if (mIgnoreSettings && disallow) { @@ -423,10 +340,9 @@ public class SyncRequest implements Parcelable { } /** - * Specify an authority and account for this transfer. Cannot be used with - * {@link #setSyncAdapter(ComponentName cname)}. + * Specify an authority and account for this transfer. * - * @param authority + * @param authority A String identifying the content provider to be synced. * @param account Account to sync. Can be null unless this is a periodic * sync, for which verification by the ContentResolver will * fail. If a sync is performed without an account, the @@ -441,25 +357,6 @@ public class SyncRequest implements Parcelable { mSyncTarget = SYNC_TARGET_ADAPTER; mAccount = account; mAuthority = authority; - mComponentName = null; - return this; - } - - /** - * Specify the {@link SyncService} component for this sync. This is not validated until - * sync time so providing an incorrect component name here will not fail. Cannot be used - * with {@link #setSyncAdapter(Account account, String authority)}. - * - * @param cname ComponentName to identify your Anonymous service - */ - public Builder setSyncAdapter(ComponentName cname) { - if (mSyncTarget != SYNC_TARGET_UNKNOWN) { - throw new IllegalArgumentException("Sync target has already been defined."); - } - mSyncTarget = SYNC_TARGET_SERVICE; - mComponentName = cname; - mAccount = null; - mAuthority = null; return this; } @@ -630,25 +527,17 @@ public class SyncRequest implements Parcelable { mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority); if (mSyncType == SYNC_TYPE_PERIODIC) { // If this is a periodic sync ensure than invalid extras were not set. - if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || + if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) { throw new IllegalArgumentException("Illegal extras were set"); } - } else if (mSyncType == SYNC_TYPE_UNKNOWN) { - throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()"); - } - if (mSyncTarget == SYNC_TARGET_SERVICE) { - if (mSyncConfigExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { - throw new IllegalArgumentException("Cannot specify an initialisation sync" - + " that targets a service."); - } } // Ensure that a target for the sync has been set. if (mSyncTarget == SYNC_TARGET_UNKNOWN) { - throw new IllegalArgumentException("Must specify an adapter with one of" - + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String"); + throw new IllegalArgumentException("Must specify an adapter with" + + " setSyncAdapter(Account, String"); } return new SyncRequest(this); } - } + } } diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java deleted file mode 100644 index 4df998c..0000000 --- a/core/java/android/content/SyncService.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package android.content; - -import android.app.Service; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Process; -import android.os.Trace; -import android.util.SparseArray; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; - -/** - * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that - * behaviour into a service to which the system can bind when requesting an - * anonymous (providerless/accountless) sync. - * <p> - * In order to perform an anonymous sync operation you must extend this service, implementing the - * abstract methods. This service must be declared in the application's manifest as usual. You - * can use this service for other work, however you <b> must not </b> override the onBind() method - * unless you know what you're doing, which limits the usefulness of this service for other work. - * <p>A {@link SyncService} can either be active or inactive. Different to an - * {@link AbstractThreadedSyncAdapter}, there is no - * {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)}, - * as well as no concept of initialisation (you can handle your own if needed). - * - * <pre> - * <service android:name=".MySyncService"/> - * </pre> - * Like @link android.content.AbstractThreadedSyncAdapter this service supports - * multiple syncs at the same time. Each incoming startSync() with a unique tag - * will spawn a thread to do the work of that sync. If startSync() is called - * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned. - * Remember that your service will spawn multiple threads if you schedule multiple syncs - * at once, so if you mutate local objects you must ensure synchronization. - */ -public abstract class SyncService extends Service { - private static final String TAG = "SyncService"; - - private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl(); - - /** Keep track of on-going syncs, keyed by bundle. */ - @GuardedBy("mSyncThreadLock") - private final SparseArray<SyncThread> - mSyncThreads = new SparseArray<SyncThread>(); - /** Lock object for accessing the SyncThreads HashMap. */ - private final Object mSyncThreadLock = new Object(); - /** - * Default key for if this sync service does not support parallel operations. Currently not - * sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now. - */ - private static final int KEY_DEFAULT = 0; - /** Identifier for this sync service. */ - private ComponentName mServiceComponent; - - /** {@hide} */ - public IBinder onBind(Intent intent) { - mServiceComponent = new ComponentName(this, getClass()); - return mSyncAdapter.asBinder(); - } - - /** {@hide} */ - private class SyncAdapterImpl extends ISyncServiceAdapter.Stub { - @Override - public void startSync(ISyncContext syncContext, Bundle extras) { - // Wrap the provided Sync Context because it may go away by the time - // we call it. - final SyncContext syncContextClient = new SyncContext(syncContext); - boolean alreadyInProgress = false; - final int extrasAsKey = extrasToKey(extras); - synchronized (mSyncThreadLock) { - if (mSyncThreads.get(extrasAsKey) == null) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "starting sync for : " + mServiceComponent); - } - // Start sync. - SyncThread syncThread = new SyncThread(syncContextClient, extras); - mSyncThreads.put(extrasAsKey, syncThread); - syncThread.start(); - } else { - // Don't want to call back to SyncManager while still - // holding lock. - alreadyInProgress = true; - } - } - if (alreadyInProgress) { - syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); - } - } - - /** - * Used by the SM to cancel a specific sync using the - * com.android.server.content.SyncManager.ActiveSyncContext as a handle. - */ - @Override - public void cancelSync(ISyncContext syncContext) { - SyncThread runningSync = null; - synchronized (mSyncThreadLock) { - for (int i = 0; i < mSyncThreads.size(); i++) { - SyncThread thread = mSyncThreads.valueAt(i); - if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) { - runningSync = thread; - break; - } - } - } - if (runningSync != null) { - runningSync.interrupt(); - } - } - } - - /** - * - * @param extras Bundle for which to compute hash - * @return an integer hash that is equal to that of another bundle if they both contain the - * same key -> value mappings, however, not necessarily in order. - * Based on the toString() representation of the value mapped. - */ - private int extrasToKey(Bundle extras) { - int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled. - if (parallelSyncsEnabled()) { - for (String key : extras.keySet()) { - String mapping = key + " " + extras.get(key).toString(); - hash += mapping.hashCode(); - } - } - return hash; - } - - /** - * {@hide} - * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while - * the ATSA considers an already in-progress sync to be if the account provided is currently - * syncing, this anonymous sync has no notion of account and considers a sync unique if the - * provided bundle is different. - */ - private class SyncThread extends Thread { - private final SyncContext mSyncContext; - private final Bundle mExtras; - private final int mThreadsKey; - - public SyncThread(SyncContext syncContext, Bundle extras) { - mSyncContext = syncContext; - mExtras = extras; - mThreadsKey = extrasToKey(extras); - } - - @Override - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName()); - - SyncResult syncResult = new SyncResult(); - try { - if (isCancelled()) return; - // Run the sync. - SyncService.this.onPerformSync(mExtras, syncResult); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER); - if (!isCancelled()) { - mSyncContext.onFinished(syncResult); - } - // Synchronize so that the assignment will be seen by other - // threads that also synchronize accesses to mSyncThreads. - synchronized (mSyncThreadLock) { - mSyncThreads.remove(mThreadsKey); - } - } - } - - private boolean isCancelled() { - return Thread.currentThread().isInterrupted(); - } - } - - /** - * Initiate an anonymous sync using this service. SyncAdapter-specific - * parameters may be specified in extras, which is guaranteed to not be - * null. - */ - public abstract void onPerformSync(Bundle extras, SyncResult syncResult); - - /** - * Override this function to indicated whether you want to support parallel syncs. - * <p>If you override and return true multiple threads will be spawned within your Service to - * handle each concurrent sync request. - * - * @return false to indicate that this service does not support parallel operations by default. - */ - protected boolean parallelSyncsEnabled() { - return false; - } -} diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index d62958f..5fd0f9b 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -370,6 +370,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * <p>If the camera device configuration fails, then {@link #onConfigureFailed} will * be invoked instead of this callback.</p> * + * @param session the session returned by {@link CameraDevice#createCaptureSession} */ public abstract void onConfigured(CameraCaptureSession session); @@ -383,6 +384,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * callback is invoked will throw an IllegalStateException. Any capture requests submitted * to the session prior to this callback will be discarded and will not produce any * callbacks on their listeners.</p> + * + * @param session the session returned by {@link CameraDevice#createCaptureSession} */ public abstract void onConfigureFailed(CameraCaptureSession session); @@ -396,6 +399,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * <p>Otherwise, this callback will be invoked any time the session finishes processing * all of its active capture requests, and no repeating request or burst is set up.</p> * + * @param session the session returned by {@link CameraDevice#createCaptureSession} + * */ public void onReady(CameraCaptureSession session) { // default empty implementation @@ -410,6 +415,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>If the session runs out of capture requests to process and calls {@link #onReady}, * then this callback will be invoked again once new requests are submitted for capture.</p> + * + * @param session the session returned by {@link CameraDevice#createCaptureSession} */ public void onActive(CameraCaptureSession session) { // default empty implementation @@ -426,6 +433,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * any repeating requests or bursts are stopped (as if {@link #stopRepeating()} was called). * However, any in-progress capture requests submitted to the session will be completed * as normal.</p> + * + * @param session the session returned by {@link CameraDevice#createCaptureSession} */ public void onClosed(CameraCaptureSession session) { // default empty implementation @@ -478,13 +487,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation of this method does nothing.</p> * - * @param camera the CameraDevice sending the callback + * @param session the session returned by {@link CameraDevice#createCaptureSession} * @param request the request for the capture that just begun * @param timestamp the timestamp at start of capture, in nanoseconds. * * @see android.media.MediaActionSound */ - public void onCaptureStarted(CameraDevice camera, + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp) { // default empty implementation } @@ -502,7 +511,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation of this method does nothing.</p> * - * @param camera The CameraDevice sending the callback. + * @param session the session returned by {@link CameraDevice#createCaptureSession} * @param request The request that was given to the CameraDevice * @param result The partial output metadata from the capture, which * includes a subset of the CaptureResult fields. @@ -514,7 +523,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * @hide */ - public void onCapturePartial(CameraDevice camera, + public void onCapturePartial(CameraCaptureSession session, CaptureRequest request, CaptureResult result) { // default empty implementation } @@ -545,7 +554,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation of this method does nothing.</p> * - * @param camera The CameraDevice sending the callback. + * @param session the session returned by {@link CameraDevice#createCaptureSession} * @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. @@ -555,7 +564,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst */ - public void onCaptureProgressed(CameraDevice camera, + public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) { // default empty implementation } @@ -573,7 +582,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation of this method does nothing.</p> * - * @param camera The CameraDevice sending the callback. + * @param session the session returned by {@link CameraDevice#createCaptureSession} * @param request The request that was given to the CameraDevice * @param result The total output metadata from the capture, including the * final capture parameters and the state of the camera system during @@ -584,7 +593,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst */ - public void onCaptureCompleted(CameraDevice camera, + public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { // default empty implementation } @@ -600,8 +609,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation of this method does nothing.</p> * - * @param camera - * The CameraDevice sending the callback. + * @param session + * The session returned by {@link CameraDevice#createCaptureSession} * @param request * The request that was given to the CameraDevice * @param failure @@ -613,7 +622,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst */ - public void onCaptureFailed(CameraDevice camera, + public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { // default empty implementation } @@ -629,8 +638,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation does nothing.</p> * - * @param camera - * The CameraDevice sending the callback. + * @param session + * The session returned by {@link CameraDevice#createCaptureSession} * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. * @param frameNumber @@ -643,7 +652,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see CaptureFailure#getSequenceId() * @see #onCaptureSequenceAborted */ - public void onCaptureSequenceCompleted(CameraDevice camera, + public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber) { // default empty implementation } @@ -661,8 +670,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>The default implementation does nothing.</p> * - * @param camera - * The CameraDevice sending the callback. + * @param session + * The session returned by {@link CameraDevice#createCaptureSession} * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. * @@ -672,7 +681,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see CaptureFailure#getSequenceId() * @see #onCaptureSequenceCompleted */ - public void onCaptureSequenceAborted(CameraDevice camera, + public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) { // default empty implementation } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 2f5b4fe..222374a 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -810,6 +810,7 @@ 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>MANUAL_POST_PROCESSING</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> @@ -1033,8 +1034,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * (i.e. format, width, height, output/input stream).</p> * <p>The configurations are listed as <code>(format, width, height, input?)</code> * tuples.</p> - * <p>All camera devices will support sensor maximum resolution (defined by - * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}) for the JPEG format.</p> * <p>For a given use case, the actual maximum supported resolution * may be lower than what is listed here, depending on the destination * Surface for the image data. For example, for recording video, diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index f9f617a..e9213c5 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -24,7 +24,7 @@ import android.view.Surface; import java.util.List; /** - * <p>The CameraDevice class is an interface to a single camera connected to an + * <p>The CameraDevice class is a representation of a single camera connected to an * Android device, allowing for fine-grain control of image capture and * post-processing at high frame rates.</p> * @@ -46,7 +46,7 @@ import java.util.List; * @see CameraManager#openCamera * @see android.Manifest.permission#CAMERA */ -public interface CameraDevice extends AutoCloseable { +public abstract class CameraDevice implements AutoCloseable { /** * Create a request suitable for a camera preview window. Specifically, this @@ -127,7 +127,7 @@ public interface CameraDevice extends AutoCloseable { * @see CameraManager#getCameraCharacteristics * @see CameraManager#getCameraIdList */ - public String getId(); + public abstract String getId(); /** * <p>Set up a new output set of Surfaces for the camera device.</p> @@ -245,7 +245,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link #createCaptureSession} instead */ @Deprecated - public void configureOutputs(List<Surface> outputs) throws CameraAccessException; + public abstract void configureOutputs(List<Surface> outputs) throws CameraAccessException; /** * <p>Create a new camera capture session by providing the target output set of Surfaces to the @@ -358,7 +358,7 @@ public interface CameraDevice extends AutoCloseable { * @see StreamConfigurationMap#getOutputSizes(int) * @see StreamConfigurationMap#getOutputSizes(Class) */ - public void createCaptureSession(List<Surface> outputs, + public abstract void createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateListener listener, Handler handler) throws CameraAccessException; @@ -387,7 +387,7 @@ public interface CameraDevice extends AutoCloseable { * @see #TEMPLATE_VIDEO_SNAPSHOT * @see #TEMPLATE_MANUAL */ - public CaptureRequest.Builder createCaptureRequest(int templateType) + public abstract CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException; /** @@ -434,7 +434,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public int capture(CaptureRequest request, CaptureListener listener, Handler handler) + public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -481,7 +481,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public int captureBurst(List<CaptureRequest> requests, CaptureListener listener, + public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -541,7 +541,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, + public abstract int setRepeatingRequest(CaptureRequest request, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -602,7 +602,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener, + public abstract int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener, Handler handler) throws CameraAccessException; /** @@ -628,7 +628,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public void stopRepeating() throws CameraAccessException; + public abstract void stopRepeating() throws CameraAccessException; /** * Flush all captures currently pending and in-progress as fast as @@ -666,7 +666,7 @@ public interface CameraDevice extends AutoCloseable { * @deprecated Use {@link CameraCaptureSession} instead */ @Deprecated - public void flush() throws CameraAccessException; + public abstract void flush() throws CameraAccessException; /** * Close the connection to this camera device as quickly as possible. @@ -684,7 +684,7 @@ public interface CameraDevice extends AutoCloseable { * */ @Override - public void close(); + public abstract void close(); /** * <p>A listener for tracking the progress of a {@link CaptureRequest} @@ -1230,4 +1230,10 @@ public interface CameraDevice extends AutoCloseable { */ public abstract void onError(CameraDevice camera, int error); // Must implement } + + /** + * To be inherited by android.hardware.camera2.* code only. + * @hide + */ + public CameraDevice() {} } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 83db056..9046b13 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -226,10 +226,10 @@ public final class CameraManager { synchronized (mLock) { - ICameraDeviceUser cameraUser; + ICameraDeviceUser cameraUser = null; - android.hardware.camera2.impl.CameraDevice deviceImpl = - new android.hardware.camera2.impl.CameraDevice( + android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = + new android.hardware.camera2.impl.CameraDeviceImpl( cameraId, listener, handler, @@ -248,8 +248,23 @@ public final class CameraManager { // Use legacy camera implementation for HAL1 devices Log.i(TAG, "Using legacy camera HAL."); cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); + } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE || + e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE || + e.getReason() == CameraAccessException.CAMERA_DISABLED || + e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || + e.getReason() == CameraAccessException.CAMERA_ERROR) { + // Received one of the known connection errors + // The remote camera device cannot be connected to, so + // set the local camera to the startup error state + deviceImpl.setRemoteFailure(e); + + if (e.getReason() == CameraAccessException.CAMERA_DISABLED || + e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { + // Per API docs, these failures call onError and throw + throw e; + } } else { - // Rethrow otherwise + // Unexpected failure - rethrow throw e; } } @@ -299,7 +314,7 @@ public final class CameraManager { * * <p>If opening the camera device fails, then the device listener's * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent - * calls on the camera device will throw an {@link IllegalStateException}.</p> + * calls on the camera device will throw a {@link CameraAccessException}.</p> * * @param cameraId * The unique identifier of the camera device to open diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java index e64deeb..3e3303c 100644 --- a/core/java/android/hardware/camera2/DngCreator.java +++ b/core/java/android/hardware/camera2/DngCreator.java @@ -121,6 +121,7 @@ public final class DngCreator implements AutoCloseable { * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li> * </ul> * @return this {@link #DngCreator} object. + * @hide */ public DngCreator setOrientation(int orientation) { @@ -147,6 +148,7 @@ public final class DngCreator implements AutoCloseable { * * @param pixels a {@link android.graphics.Bitmap} of pixel data. * @return this {@link #DngCreator} object. + * @hide */ public DngCreator setThumbnail(Bitmap pixels) { if (pixels == null) { @@ -180,6 +182,7 @@ public final class DngCreator implements AutoCloseable { * @param pixels an {@link android.media.Image} object with the format * {@link android.graphics.ImageFormat#YUV_420_888}. * @return this {@link #DngCreator} object. + * @hide */ public DngCreator setThumbnail(Image pixels) { if (pixels == null) { @@ -216,6 +219,7 @@ public final class DngCreator implements AutoCloseable { * * @throws java.lang.IllegalArgumentException if the given location object doesn't * contain enough information to set location metadata. + * @hide */ public DngCreator setLocation(Location location) { /*TODO*/ @@ -231,6 +235,7 @@ public final class DngCreator implements AutoCloseable { * * @param description the user description string. * @return this {@link #DngCreator} object. + * @hide */ public DngCreator setDescription(String description) { /*TODO*/ @@ -263,6 +268,7 @@ public final class DngCreator implements AutoCloseable { * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. * @throws java.lang.IllegalArgumentException if the size passed in does not match the + * @hide */ public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset) throws IOException { @@ -297,6 +303,7 @@ public final class DngCreator implements AutoCloseable { * @throws IOException if an error was encountered in the input or output stream. * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. + * @hide */ public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset) throws IOException { diff --git a/core/java/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.java b/core/java/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.java new file mode 100644 index 0000000..866f370 --- /dev/null +++ b/core/java/android/hardware/camera2/dispatch/ArgumentReplacingDispatcher.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.hardware.camera2.dispatch; + +import java.lang.reflect.Method; + +import static com.android.internal.util.Preconditions.*; + +/** + * A dispatcher that replaces one argument with another; replaces any argument at an index + * with another argument. + * + * <p>For example, we can override an {@code void onSomething(int x)} calls to have {@code x} always + * equal to 1. Or, if using this with a duck typing dispatcher, we could even overwrite {@code x} to + * be something + * that's not an {@code int}.</p> + * + * @param <T> + * source dispatch type, whose methods with {@link #dispatch} will be called + * @param <TArg> + * argument replacement type, args in {@link #dispatch} matching {@code argumentIndex} + * will be overriden to objects of this type + */ +public class ArgumentReplacingDispatcher<T, TArg> implements Dispatchable<T> { + + private final Dispatchable<T> mTarget; + private final int mArgumentIndex; + private final TArg mReplaceWith; + + /** + * Create a new argument replacing dispatcher; dispatches are forwarded to {@code target} + * after the argument is replaced. + * + * <p>For example, if a method {@code onAction(T1 a, Integer b, T2 c)} is invoked, and we wanted + * to replace all occurrences of {@code b} with {@code 0xDEADBEEF}, we would set + * {@code argumentIndex = 1} and {@code replaceWith = 0xDEADBEEF}.</p> + * + * <p>If a method dispatched has less arguments than {@code argumentIndex}, it is + * passed through with the arguments unchanged.</p> + * + * @param target destination dispatch type, methods will be redirected to this dispatcher + * @param argumentIndex the numeric index of the argument {@code >= 0} + * @param replaceWith arguments matching {@code argumentIndex} will be replaced with this object + */ + public ArgumentReplacingDispatcher(Dispatchable<T> target, int argumentIndex, + TArg replaceWith) { + mTarget = checkNotNull(target, "target must not be null"); + mArgumentIndex = checkArgumentNonnegative(argumentIndex, + "argumentIndex must not be negative"); + mReplaceWith = checkNotNull(replaceWith, "replaceWith must not be null"); + } + + @Override + public Object dispatch(Method method, Object[] args) throws Throwable { + + if (args.length > mArgumentIndex) { + args = arrayCopy(args); // don't change in-place since it can affect upstream dispatches + args[mArgumentIndex] = mReplaceWith; + } + + return mTarget.dispatch(method, args); + } + + private static Object[] arrayCopy(Object[] array) { + int length = array.length; + Object[] newArray = new Object[length]; + for (int i = 0; i < length; ++i) { + newArray[i] = array[i]; + } + return newArray; + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index e129783..f74fbaa 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -19,6 +19,7 @@ import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher; import android.hardware.camera2.dispatch.BroadcastDispatcher; import android.hardware.camera2.dispatch.Dispatchable; import android.hardware.camera2.dispatch.DuckTypingDispatcher; @@ -34,7 +35,7 @@ import android.view.Surface; import java.util.Arrays; import java.util.List; -import static android.hardware.camera2.impl.CameraDevice.checkHandler; +import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler; import static com.android.internal.util.Preconditions.*; public class CameraCaptureSessionImpl extends CameraCaptureSession { @@ -52,7 +53,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private final Handler mStateHandler; /** Internal camera device; used to translate calls into existing deprecated API */ - private final android.hardware.camera2.impl.CameraDevice mDeviceImpl; + private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl; /** Internal handler; used for all incoming events to preserve total order */ private final Handler mDeviceHandler; @@ -82,7 +83,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { */ CameraCaptureSessionImpl(List<Surface> outputs, CameraCaptureSession.StateListener listener, Handler stateHandler, - android.hardware.camera2.impl.CameraDevice deviceImpl, + android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, Handler deviceStateHandler, boolean configureSuccess) { if (outputs == null || outputs.isEmpty()) { throw new IllegalArgumentException("outputs must be a non-null, non-empty list"); @@ -325,6 +326,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { /* * Split the calls from the device listener into local listener and the following chain: + * - replace the first CameraDevice arg with a CameraCaptureSession * - duck type from device listener to session listener * - then forward the call to a handler * - then finally invoke the destination method on the session listener object @@ -340,12 +342,15 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { new InvokeDispatcher<>(localListener); HandlerDispatcher<CaptureListener> handlerPassthrough = new HandlerDispatcher<>(userListenerSink, handler); - DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSessionCaptureListener + DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSession = new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class); + ArgumentReplacingDispatcher<CameraDevice.CaptureListener, CameraCaptureSessionImpl> + replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession, + /*argumentIndex*/0, this); BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster = new BroadcastDispatcher<CameraDevice.CaptureListener>( - duckToSessionCaptureListener, + replaceDeviceWithSession, localSink); return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster); diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index e9d4b0f..9795082 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -21,7 +21,6 @@ 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; @@ -48,7 +47,7 @@ import java.util.TreeSet; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate */ -public class CameraDevice implements android.hardware.camera2.CameraDevice { +public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { private final String TAG; private final boolean DEBUG; @@ -65,8 +64,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private volatile StateListener mSessionStateListener; private final Handler mDeviceHandler; + private boolean mInError = false; private boolean mIdle = true; + /** map request IDs to listener/request data */ private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = new SparseArray<CaptureListenerHolder>(); @@ -99,11 +100,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnOpened = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onOpened(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onOpened(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onOpened(CameraDevice.this); + sessionListener.onOpened(CameraDeviceImpl.this); } } } @@ -112,11 +113,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnUnconfigured = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onUnconfigured(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onUnconfigured(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onUnconfigured(CameraDevice.this); + sessionListener.onUnconfigured(CameraDeviceImpl.this); } } } @@ -125,11 +126,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnActive = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onActive(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onActive(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onActive(CameraDevice.this); + sessionListener.onActive(CameraDeviceImpl.this); } } } @@ -138,11 +139,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnBusy = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onBusy(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onBusy(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onBusy(CameraDevice.this); + sessionListener.onBusy(CameraDeviceImpl.this); } } } @@ -151,10 +152,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnClosed = new Runnable() { @Override public void run() { - mDeviceListener.onClosed(CameraDevice.this); + mDeviceListener.onClosed(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onClosed(CameraDevice.this); + sessionListener.onClosed(CameraDeviceImpl.this); } } }; @@ -162,11 +163,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnIdle = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onIdle(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onIdle(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onIdle(CameraDevice.this); + sessionListener.onIdle(CameraDeviceImpl.this); } } } @@ -175,17 +176,17 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Runnable mCallOnDisconnected = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onDisconnected(CameraDevice.this); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onDisconnected(CameraDeviceImpl.this); StateListener sessionListener = mSessionStateListener; if (sessionListener != null) { - sessionListener.onDisconnected(CameraDevice.this); + sessionListener.onDisconnected(CameraDeviceImpl.this); } } } }; - public CameraDevice(String cameraId, StateListener listener, Handler handler, + public CameraDeviceImpl(String cameraId, StateListener listener, Handler handler, CameraCharacteristics characteristics) { if (cameraId == null || listener == null || handler == null) { throw new IllegalArgumentException("Null argument given"); @@ -211,6 +212,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void setRemoteDevice(ICameraDeviceUser remoteDevice) { // TODO: Move from decorator to direct binder-mediated exceptions synchronized(mLock) { + // If setRemoteFailure already called, do nothing + if (mInError) return; + mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); mDeviceHandler.post(mCallOnOpened); @@ -218,6 +222,52 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } } + /** + * Call to indicate failed connection to a remote camera device. + * + * <p>This places the camera device in the error state and informs the listener. + * Use in place of setRemoteDevice() when startup fails.</p> + */ + public void setRemoteFailure(final CameraRuntimeException failure) { + int failureCode = StateListener.ERROR_CAMERA_DEVICE; + boolean failureIsError = true; + + switch (failure.getReason()) { + case CameraAccessException.CAMERA_IN_USE: + failureCode = StateListener.ERROR_CAMERA_IN_USE; + break; + case CameraAccessException.MAX_CAMERAS_IN_USE: + failureCode = StateListener.ERROR_MAX_CAMERAS_IN_USE; + break; + case CameraAccessException.CAMERA_DISABLED: + failureCode = StateListener.ERROR_CAMERA_DISABLED; + break; + case CameraAccessException.CAMERA_DISCONNECTED: + failureIsError = false; + break; + case CameraAccessException.CAMERA_ERROR: + failureCode = StateListener.ERROR_CAMERA_DEVICE; + break; + default: + Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason()); + break; + } + final int code = failureCode; + final boolean isError = failureIsError; + synchronized (mLock) { + mInError = true; + mDeviceHandler.post(new Runnable() { + public void run() { + if (isError) { + mDeviceListener.onError(CameraDeviceImpl.this, code); + } else { + mDeviceListener.onDisconnected(CameraDeviceImpl.this); + } + } + }); + } + } + @Override public String getId() { return mCameraId; @@ -230,7 +280,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { outputs = new ArrayList<Surface>(); } synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete @@ -298,7 +348,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { Log.d(TAG, "createCaptureSession"); } - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); // TODO: we must be in UNCONFIGURED mode to begin with, or using another session @@ -336,7 +386,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); CameraMetadataNative templatedRequest = new CameraMetadataNative(); @@ -416,7 +466,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { Runnable resultDispatch = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { + if (!CameraDeviceImpl.this.isClosed()) { if (DEBUG) { Log.d(TAG, String.format( "early trigger sequence complete for request %d", @@ -427,7 +477,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { throw new AssertionError(lastFrameNumber + " cannot be cast to int"); } holder.getListener().onCaptureSequenceCompleted( - CameraDevice.this, + CameraDeviceImpl.this, requestId, lastFrameNumber); } @@ -456,7 +506,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); int requestId; if (repeating) { @@ -528,7 +578,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void stopRepeating() throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { int requestId = mRepeatingRequestId; @@ -559,7 +609,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private void waitUntilIdle() throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { throw new IllegalStateException("Active repeating request ongoing"); } @@ -580,7 +630,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void flush() throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); mDeviceHandler.post(mCallOnBusy); try { @@ -614,11 +664,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible } - if (mRemoteDevice != null) { + // Only want to fire the onClosed callback once; + // either a normal close where the remote device is valid + // or a close after a startup error (no remote device but in error state) + if (mRemoteDevice != null || mInError) { mDeviceHandler.post(mCallOnClosed); } mRemoteDevice = null; + mInError = false; } } @@ -769,7 +823,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { Runnable resultDispatch = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()){ + if (!CameraDeviceImpl.this.isClosed()){ if (DEBUG) { Log.d(TAG, String.format( "fire sequence complete for request %d", @@ -783,7 +837,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { + " cannot be cast to int"); } holder.getListener().onCaptureSequenceCompleted( - CameraDevice.this, + CameraDeviceImpl.this, requestId, lastFrameNumber); } @@ -835,6 +889,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { if (isClosed()) return; synchronized(mLock) { + mInError = true; switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: r = mCallOnDisconnected; @@ -847,14 +902,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { r = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { - mDeviceListener.onError(CameraDevice.this, errorCode); + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceListener.onError(CameraDeviceImpl.this, errorCode); } } }; break; } - CameraDevice.this.mDeviceHandler.post(r); + CameraDeviceImpl.this.mDeviceHandler.post(r); } // Fire onCaptureSequenceCompleted @@ -874,10 +929,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { Log.d(TAG, "Camera now idle"); } synchronized (mLock) { - if (!CameraDevice.this.mIdle) { - CameraDevice.this.mDeviceHandler.post(mCallOnIdle); + if (!CameraDeviceImpl.this.mIdle) { + CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle); } - CameraDevice.this.mIdle = true; + CameraDeviceImpl.this.mIdle = true; } } @@ -891,7 +946,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // Get the listener for this frame ID, if there is one synchronized (mLock) { - holder = CameraDevice.this.mCaptureListenerMap.get(requestId); + holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); } if (holder == null) { @@ -905,9 +960,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()) { + if (!CameraDeviceImpl.this.isClosed()) { holder.getListener().onCaptureStarted( - CameraDevice.this, + CameraDeviceImpl.this, holder.getRequest(resultExtras.getSubsequenceId()), timestamp); } @@ -932,7 +987,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { final CaptureListenerHolder holder; synchronized (mLock) { - holder = CameraDevice.this.mCaptureListenerMap.get(requestId); + holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); } Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); @@ -976,9 +1031,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { resultDispatch = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()){ + if (!CameraDeviceImpl.this.isClosed()){ holder.getListener().onCapturePartial( - CameraDevice.this, + CameraDeviceImpl.this, request, resultAsCapture); } @@ -992,9 +1047,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { resultDispatch = new Runnable() { @Override public void run() { - if (!CameraDevice.this.isClosed()){ + if (!CameraDeviceImpl.this.isClosed()){ holder.getListener().onCaptureCompleted( - CameraDevice.this, + CameraDeviceImpl.this, request, resultAsCapture); } @@ -1032,7 +1087,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return handler; } - private void checkIfCameraClosed() { + private void checkIfCameraClosedOrInError() throws CameraAccessException { + if (mInError) { + throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, + "The camera device has encountered a serious error"); + } if (mRemoteDevice == null) { throw new IllegalStateException("CameraDevice was already closed"); } diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 9e2aacd..d4c5cfb 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -58,7 +58,7 @@ public class Contacts { @Deprecated public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); - /** + /** * Signifies an email address row that is stored in the ContactMethods table * @deprecated see {@link android.provider.ContactsContract} */ @@ -337,7 +337,7 @@ public class Contacts { * @deprecated see {@link android.provider.ContactsContract} */ @Deprecated - public static final class People implements BaseColumns, SyncConstValue, PeopleColumns, + public static final class People implements BaseColumns, PeopleColumns, PhonesColumns, PresenceColumns { /** * no public constructor since this is a utility class @@ -790,7 +790,7 @@ public class Contacts { */ @Deprecated public static final class Groups - implements BaseColumns, SyncConstValue, GroupsColumns { + implements BaseColumns, GroupsColumns { /** * no public constructor since this is a utility class */ @@ -1864,7 +1864,7 @@ public class Contacts { * @deprecated see {@link android.provider.ContactsContract} */ @Deprecated - public static final class Photos implements BaseColumns, PhotosColumns, SyncConstValue { + public static final class Photos implements BaseColumns, PhotosColumns { /** * no public constructor since this is a utility class */ @@ -2199,7 +2199,7 @@ public class Contacts { } /** The action code to use when adding a contact - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated see {@link android.provider.ContactsContract} */ @Deprecated public static final String ACTION = ContactsContract.Intents.Insert.ACTION; diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index b53ea81..8c7e879 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -184,9 +184,9 @@ public final class ContactsContract { public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query"; /** - * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI}, - * {@link CommonDataKinds.Email#CONTENT_URI}, and - * {@link CommonDataKinds.StructuredPostal#CONTENT_URI}. + * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}, + * {@link CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, and + * {@link CommonDataKinds.StructuredPostal#CONTENT_URI StructuredPostal.CONTENT_URI}. * This enables a content provider to remove duplicate entries in results. */ public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries"; @@ -244,6 +244,9 @@ public final class ContactsContract { public static final String KEY_AUTHORIZED_URI = "authorized_uri"; } + /* + * @hide + */ public static final class Preferences { /** @@ -808,6 +811,7 @@ public final class ContactsContract { * The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED}, * the contact is not pinned. Also see {@link PinnedPositions}. * <P>Type: INTEGER </P> + * @hide */ public static final String PINNED = "pinned"; @@ -7764,6 +7768,8 @@ public final class ContactsContract { * {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned * contacts to be automatically starred and unstarred. * </p> + * + * @hide */ public static final class PinnedPositions { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c7c007e..634ae60 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10609,24 +10609,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns a ValueAnimator which can animate a clipping circle. - * <p> - * The View will be clipped to the animating circle. - * <p> - * Any shadow cast by the View will respect the circular clip from this animator. - * - * @param centerX The x coordinate of the center of the animating circle. - * @param centerY The y coordinate of the center of the animating circle. - * @param startRadius The starting radius of the animating circle. - * @param endRadius The ending radius of the animating circle. - */ - public final ValueAnimator createRevealAnimator(int centerX, int centerY, - float startRadius, float endRadius) { - return RevealAnimator.ofRevealCircle(this, centerX, centerY, - startRadius, endRadius, false); - } - - /** * Returns a ValueAnimator which can animate a clearing circle. * <p> * The View is prevented from drawing within the circle, so the content diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java new file mode 100644 index 0000000..3854f34 --- /dev/null +++ b/core/java/android/view/ViewAnimationUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.animation.RevealAnimator; +import android.animation.ValueAnimator; + +/** + * Defines common utilities for working with View's animations. + * + */ +public class ViewAnimationUtils { + private ViewAnimationUtils() {} + /** + * Returns a ValueAnimator which can animate a clipping circle. + * + * Any shadow cast by the View will respect the circular clip from this animator. + * + * @param view The View will be clipped to the animating circle. + * @param centerX The x coordinate of the center of the animating circle. + * @param centerY The y coordinate of the center of the animating circle. + * @param startRadius The starting radius of the animating circle. + * @param endRadius The ending radius of the animating circle. + */ + public static final ValueAnimator createCircularReveal(View view, + int centerX, int centerY, float startRadius, float endRadius) { + return RevealAnimator.ofRevealCircle(view, centerX, centerY, + startRadius, endRadius, false); + } +} diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index a0134d6..334ff43 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -78,6 +80,7 @@ public class CaptioningManager { * language * @hide */ + @Nullable public final String getRawLocale() { return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE); } @@ -86,6 +89,7 @@ public class CaptioningManager { * @return the locale for the user's preferred captioning language, or null * if not specified */ + @Nullable public final Locale getLocale() { final String rawLocale = getRawLocale(); if (!TextUtils.isEmpty(rawLocale)) { @@ -125,6 +129,7 @@ public class CaptioningManager { * @return the user's preferred visual properties for captions as a * {@link CaptionStyle}, or the default style if not specified */ + @NonNull public CaptionStyle getUserStyle() { final int preset = getRawUserStyle(); if (preset == CaptionStyle.PRESET_CUSTOM) { @@ -140,17 +145,19 @@ public class CaptioningManager { * * @param listener the listener to add */ - public void addCaptioningChangeListener(CaptioningChangeListener listener) { + public void addCaptioningChangeListener(@NonNull CaptioningChangeListener listener) { synchronized (mListeners) { if (mListeners.isEmpty()) { registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR); + registerObserver(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE); + registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET); } mListeners.add(listener); @@ -167,7 +174,7 @@ public class CaptioningManager { * * @param listener the listener to remove */ - public void removeCaptioningChangeListener(CaptioningChangeListener listener) { + public void removeCaptioningChangeListener(@NonNull CaptioningChangeListener listener) { synchronized (mListeners) { mListeners.remove(listener); @@ -253,11 +260,18 @@ public class CaptioningManager { /** Packed value for a color of 'none' and a cached opacity of 100%. */ private static final int COLOR_NONE_OPAQUE = 0x000000FF; + /** Packed value for an unspecified color and opacity. */ + private static final int COLOR_UNSPECIFIED = 0x000001FF; + private static final CaptionStyle WHITE_ON_BLACK; private static final CaptionStyle BLACK_ON_WHITE; private static final CaptionStyle YELLOW_ON_BLACK; private static final CaptionStyle YELLOW_ON_BLUE; private static final CaptionStyle DEFAULT_CUSTOM; + private static final CaptionStyle UNSPECIFIED; + + /** The default caption style used to fill in unspecified values. @hide */ + public static final CaptionStyle DEFAULT; /** @hide */ public static final CaptionStyle[] PRESETS; @@ -265,6 +279,9 @@ public class CaptioningManager { /** @hide */ public static final int PRESET_CUSTOM = -1; + /** Unspecified edge type value. */ + public static final int EDGE_TYPE_UNSPECIFIED = -1; + /** Edge type value specifying no character edges. */ public static final int EDGE_TYPE_NONE = 0; @@ -289,6 +306,7 @@ public class CaptioningManager { /** * The preferred edge type for video captions, one of: * <ul> + * <li>{@link #EDGE_TYPE_UNSPECIFIED} * <li>{@link #EDGE_TYPE_NONE} * <li>{@link #EDGE_TYPE_OUTLINE} * <li>{@link #EDGE_TYPE_DROP_SHADOW} @@ -326,9 +344,81 @@ public class CaptioningManager { } /** + * Applies a caption style, overriding any properties that are specified + * in the overlay caption. + * + * @param overlay The style to apply + * @return A caption style with the overlay style applied + * @hide + */ + @NonNull + public CaptionStyle applyStyle(@NonNull CaptionStyle overlay) { + final int newForegroundColor = overlay.hasForegroundColor() ? + overlay.foregroundColor : foregroundColor; + final int newBackgroundColor = overlay.hasBackgroundColor() ? + overlay.backgroundColor : backgroundColor; + final int newEdgeType = overlay.hasEdgeType() ? + overlay.edgeType : edgeType; + final int newEdgeColor = overlay.hasEdgeColor() ? + overlay.edgeColor : edgeColor; + final int newWindowColor = overlay.hasWindowColor() ? + overlay.windowColor : windowColor; + final String newRawTypeface = overlay.mRawTypeface != null ? + overlay.mRawTypeface : mRawTypeface; + return new CaptionStyle(newForegroundColor, newBackgroundColor, newEdgeType, + newEdgeColor, newWindowColor, newRawTypeface); + } + + /** + * @return {@code true} if the user has specified a background color + * that should override the application default, {@code false} + * otherwise + */ + public boolean hasBackgroundColor() { + return backgroundColor != COLOR_UNSPECIFIED; + } + + /** + * @return {@code true} if the user has specified a foreground color + * that should override the application default, {@code false} + * otherwise + */ + public boolean hasForegroundColor() { + return foregroundColor != COLOR_UNSPECIFIED; + } + + /** + * @return {@code true} if the user has specified an edge type that + * should override the application default, {@code false} + * otherwise + */ + public boolean hasEdgeType() { + return edgeType != EDGE_TYPE_UNSPECIFIED; + } + + /** + * @return {@code true} if the user has specified an edge color that + * should override the application default, {@code false} + * otherwise + */ + public boolean hasEdgeColor() { + return edgeColor != COLOR_UNSPECIFIED; + } + + /** + * @return {@code true} if the user has specified a window color that + * should override the application default, {@code false} + * otherwise + */ + public boolean hasWindowColor() { + return windowColor != COLOR_UNSPECIFIED; + } + + /** * @return the preferred {@link Typeface} for video captions, or null if * not specified */ + @Nullable public Typeface getTypeface() { if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) { mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL); @@ -339,6 +429,7 @@ public class CaptioningManager { /** * @hide */ + @NonNull public static CaptionStyle getCustomStyle(ContentResolver cr) { final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM; final int foregroundColor = Secure.getInt( @@ -370,12 +461,17 @@ public class CaptioningManager { Color.BLACK, COLOR_NONE_OPAQUE, null); YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, COLOR_NONE_OPAQUE, null); + UNSPECIFIED = new CaptionStyle(COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, + EDGE_TYPE_UNSPECIFIED, COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, null); + // The ordering of these cannot change since we store the index + // directly in preferences. PRESETS = new CaptionStyle[] { - WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE + WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE, UNSPECIFIED }; DEFAULT_CUSTOM = WHITE_ON_BLACK; + DEFAULT = WHITE_ON_BLACK; } } @@ -389,8 +485,7 @@ public class CaptioningManager { * * @param enabled the user's new preferred captioning enabled state */ - public void onEnabledChanged(boolean enabled) { - } + public void onEnabledChanged(boolean enabled) {} /** * Called when the captioning user style changes. @@ -398,17 +493,15 @@ public class CaptioningManager { * @param userStyle the user's new preferred style * @see CaptioningManager#getUserStyle() */ - public void onUserStyleChanged(CaptionStyle userStyle) { - } + public void onUserStyleChanged(@NonNull CaptionStyle userStyle) {} /** * Called when the captioning locale changes. * - * @param locale the preferred captioning locale + * @param locale the preferred captioning locale, or {@code null} if not specified * @see CaptioningManager#getLocale() */ - public void onLocaleChanged(Locale locale) { - } + public void onLocaleChanged(@Nullable Locale locale) {} /** * Called when the captioning font scaling factor changes. @@ -416,7 +509,6 @@ public class CaptioningManager { * @param fontScale the preferred font scaling factor * @see CaptioningManager#getFontScale() */ - public void onFontScaleChanged(float fontScale) { - } + public void onFontScaleChanged(float fontScale) {} } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index aa642fd..77559c0 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -755,6 +755,7 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar { mNavItemSelectedListener = l; if (mSpinner != null) { mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(l); } } diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index d841d53..7fe03f5 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -22,17 +22,18 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.os.Debug; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; @@ -110,14 +111,11 @@ public class LockPatternView extends View { private float mSquareWidth; private float mSquareHeight; - private Bitmap mBitmapBtnDefault; - private Bitmap mBitmapBtnTouched; - private Bitmap mBitmapCircleDefault; - private Bitmap mBitmapCircleGreen; - private Bitmap mBitmapCircleRed; - - private Bitmap mBitmapArrowGreenUp; - private Bitmap mBitmapArrowRedUp; + private final Bitmap mBitmapBtnDefault; + private final Bitmap mBitmapBtnTouched; + private final Bitmap mBitmapCircleDefault; + private final Bitmap mBitmapCircleAlpha; + private final Bitmap mBitmapArrowAlphaUp; private final Path mCurrentPath = new Path(); private final Rect mInvalidate = new Rect(); @@ -129,6 +127,10 @@ public class LockPatternView extends View { private int mAspect; private final Matrix mArrowMatrix = new Matrix(); private final Matrix mCircleMatrix = new Matrix(); + private final PorterDuffColorFilter mRegularColorFilter; + private final PorterDuffColorFilter mErrorColorFilter; + private final PorterDuffColorFilter mSuccessColorFilter; + /** * Represents a cell in the 3 X 3 matrix of the unlock pattern view. @@ -266,17 +268,22 @@ public class LockPatternView extends View { setClickable(true); + mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); - int defaultColor = Color.WHITE; - TypedValue outValue = new TypedValue(); - if (context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, outValue, true)) { - defaultColor = context.getResources().getColor(outValue.resourceId); - } + int regularColor = getResources().getColor(R.color.lock_pattern_view_regular_color); + int errorColor = getResources().getColor(R.color.lock_pattern_view_error_color); + int successColor = getResources().getColor(R.color.lock_pattern_view_success_color); + regularColor = a.getColor(R.styleable.LockPatternView_regularColor, regularColor); + errorColor = a.getColor(R.styleable.LockPatternView_errorColor, errorColor); + successColor = a.getColor(R.styleable.LockPatternView_successColor, successColor); + mRegularColorFilter = new PorterDuffColorFilter(regularColor, PorterDuff.Mode.SRC_ATOP); + mErrorColorFilter = new PorterDuffColorFilter(errorColor, PorterDuff.Mode.SRC_ATOP); + mSuccessColorFilter = new PorterDuffColorFilter(successColor, PorterDuff.Mode.SRC_ATOP); - final int color = a.getColor(R.styleable.LockPatternView_pathColor, defaultColor); - mPathPaint.setColor(color); + int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, regularColor); + mPathPaint.setColor(pathColor); mPathPaint.setAlpha(mStrokeAlpha); mPathPaint.setStyle(Paint.Style.STROKE); @@ -284,25 +291,26 @@ public class LockPatternView extends View { mPathPaint.setStrokeCap(Paint.Cap.ROUND); // lot's of bitmaps! - // TODO: those bitmaps are hardcoded to the Holo Theme which should not be the case! - mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo); - mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo); - mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo); - mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo); - mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo); - - mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up); - mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up); + // TODO: those bitmaps are hardcoded to the Quantum Theme which should not be the case! + mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_qntm_alpha); + mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_qntm_alpha); + mBitmapCircleDefault = getBitmapFor( + R.drawable.indicator_code_lock_point_area_default_qntm_alpha); + mBitmapCircleAlpha = getBitmapFor(R.drawable.indicator_code_lock_point_area_qntm_alpha); + mBitmapArrowAlphaUp = getBitmapFor( + R.drawable.indicator_code_lock_drag_direction_up_qntm_alpha); // bitmaps have the size of the largest bitmap in this group final Bitmap bitmaps[] = { mBitmapBtnDefault, mBitmapBtnTouched, mBitmapCircleDefault, - mBitmapCircleGreen, mBitmapCircleRed }; + mBitmapCircleAlpha}; for (Bitmap bitmap : bitmaps) { mBitmapWidth = Math.max(mBitmapWidth, bitmap.getWidth()); mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } + mPaint.setAntiAlias(true); + mPaint.setDither(true); mPaint.setFilterBitmap(true); mCellStates = new CellState[3][3]; @@ -963,7 +971,12 @@ public class LockPatternView extends View { } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { - boolean green = mPatternDisplayMode != DisplayMode.Wrong; + if (mPatternInProgress) { + mPaint.setColorFilter(mRegularColorFilter); + } else { + boolean success = mPatternDisplayMode != DisplayMode.Wrong; + mPaint.setColorFilter(success ? mSuccessColorFilter : mErrorColorFilter); + } final int endRow = end.row; final int startRow = start.row; @@ -977,7 +990,6 @@ public class LockPatternView extends View { // compute transform to place arrow bitmaps at correct angle inside circle. // This assumes that the arrow image is drawn at 12:00 with it's top edge // coincident with the circle bitmap's top edge. - Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp; final int cellWidth = mBitmapWidth; final int cellHeight = mBitmapHeight; @@ -994,8 +1006,8 @@ public class LockPatternView extends View { mArrowMatrix.preScale(sx, sy); mArrowMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); mArrowMatrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate about cell center - mArrowMatrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos - canvas.drawBitmap(arrow, mArrowMatrix, mPaint); + mArrowMatrix.preTranslate((cellWidth - mBitmapArrowAlphaUp.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos + canvas.drawBitmap(mBitmapArrowAlphaUp, mArrowMatrix, mPaint); } /** @@ -1008,24 +1020,28 @@ public class LockPatternView extends View { boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; - + ColorFilter outerFilter; if (!partOfPattern || mInStealthMode) { // unselected circle outerCircle = mBitmapCircleDefault; innerCircle = mBitmapBtnDefault; + outerFilter = mRegularColorFilter; } else if (mPatternInProgress) { // user is in middle of drawing a pattern - outerCircle = mBitmapCircleGreen; + outerCircle = mBitmapCircleAlpha; innerCircle = mBitmapBtnTouched; + outerFilter = mRegularColorFilter; } else if (mPatternDisplayMode == DisplayMode.Wrong) { // the pattern is wrong - outerCircle = mBitmapCircleRed; + outerCircle = mBitmapCircleAlpha; innerCircle = mBitmapBtnDefault; + outerFilter = mErrorColorFilter; } else if (mPatternDisplayMode == DisplayMode.Correct || mPatternDisplayMode == DisplayMode.Animate) { // the pattern is correct - outerCircle = mBitmapCircleGreen; + outerCircle = mBitmapCircleAlpha; innerCircle = mBitmapBtnDefault; + outerFilter = mSuccessColorFilter; } else { throw new IllegalStateException("unknown display mode " + mPatternDisplayMode); } @@ -1048,7 +1064,9 @@ public class LockPatternView extends View { mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); + mPaint.setColorFilter(outerFilter); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); + mPaint.setColorFilter(mRegularColorFilter); canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint); } diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java index 117463a..2f987e9 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -271,10 +271,13 @@ public class SubtitleView extends View { style = CaptionStyle.PRESETS[styleId]; } - mForegroundColor = style.foregroundColor; - mBackgroundColor = style.backgroundColor; - mEdgeType = style.edgeType; - mEdgeColor = style.edgeColor; + final CaptionStyle defStyle = CaptionStyle.DEFAULT; + mForegroundColor = style.hasForegroundColor() ? + style.foregroundColor : defStyle.foregroundColor; + mBackgroundColor = style.hasBackgroundColor() ? + style.backgroundColor : defStyle.backgroundColor; + mEdgeType = style.hasEdgeType() ? style.edgeType : defStyle.edgeType; + mEdgeColor = style.hasEdgeColor() ? style.edgeColor : defStyle.edgeColor; mHasMeasurements = false; final Typeface typeface = style.getTypeface(); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index ec935cc..8e56eec 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -879,8 +879,8 @@ public: #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface); - layout.doLayout(textArray + start, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + layout.doLayout(textArray, start, count, contextCount, css); drawGlyphsToSkia(canvas, paint, layout, x, y); #else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, diff --git a/core/jni/android/graphics/MinikinSkia.cpp b/core/jni/android/graphics/MinikinSkia.cpp index 243fa10..2b96f1b 100644 --- a/core/jni/android/graphics/MinikinSkia.cpp +++ b/core/jni/android/graphics/MinikinSkia.cpp @@ -46,8 +46,10 @@ bool MinikinFontSkia::GetGlyph(uint32_t codepoint, uint32_t *glyph) const { static void MinikinFontSkia_SetSkiaPaint(SkTypeface* typeface, SkPaint* skPaint, const MinikinPaint& paint) { skPaint->setTypeface(typeface); skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); - // TODO: set more paint parameters from Minikin skPaint->setTextSize(paint.size); + skPaint->setTextScaleX(paint.scaleX); + skPaint->setTextSkewX(paint.skewX); + MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); } float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, @@ -96,4 +98,21 @@ int32_t MinikinFontSkia::GetUniqueId() const { return mTypeface->uniqueID(); } +uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { + uint32_t flags = paint->getFlags(); + SkPaint::Hinting hinting = paint->getHinting(); + // select only flags that might affect text layout + flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | + SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag | + SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag | + SkPaint::kVerticalText_Flag); + flags |= (hinting << 16); + return flags; +} + +void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { + paint->setFlags(paintFlags & SkPaint::kAllFlags); + paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16)); +} + } diff --git a/core/jni/android/graphics/MinikinSkia.h b/core/jni/android/graphics/MinikinSkia.h index 1cc2c51..0452c57 100644 --- a/core/jni/android/graphics/MinikinSkia.h +++ b/core/jni/android/graphics/MinikinSkia.h @@ -38,6 +38,8 @@ public: SkTypeface *GetSkTypeface(); + static uint32_t packPaintFlags(const SkPaint* paint); + static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); private: SkTypeface *mTypeface; }; diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index a88b747..a9360ea 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +#define LOG_TAG "Minikin" +#include <cutils/log.h> +#include <string> + #include "SkPaint.h" #include "minikin/Layout.h" #include "TypefaceImpl.h" @@ -23,24 +27,39 @@ namespace android { -void MinikinUtils::SetLayoutProperties(Layout* layout, const SkPaint* paint, int flags, - TypefaceImpl* typeface) { +// Do an sprintf starting at offset n, abort on overflow +static int snprintfcat(char* buf, int off, int size, const char* format, ...) { + va_list args; + va_start(args, format); + int n = vsnprintf(buf + off, size - off, format, args); + LOG_ALWAYS_FATAL_IF(n >= size - off, "String overflow in setting layout properties"); + va_end(args); + return off + n; +} + +std::string MinikinUtils::setLayoutProperties(Layout* layout, const SkPaint* paint, int bidiFlags, + TypefaceImpl* typeface) { TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface); layout->setFontCollection(resolvedFace->fFontCollection); FontStyle style = resolvedFace->fStyle; char css[256]; - int off = snprintf(css, sizeof(css), - "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d;", + int off = snprintfcat(css, 0, sizeof(css), + "font-size: %d; font-scale-x: %f; font-skew-x: %f; -paint-flags: %d;" + " font-weight: %d; font-style: %s; -minikin-bidi: %d;", (int)paint->getTextSize(), + paint->getTextScaleX(), + paint->getTextSkewX(), + MinikinFontSkia::packPaintFlags(paint), style.getWeight() * 100, style.getItalic() ? "italic" : "normal", - flags); + bidiFlags); SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag(); - off += snprintf(css + off, sizeof(css) - off, " lang: %s;", langString.c_str()); + off = snprintfcat(css, off, sizeof(css), " lang: %s;", langString.c_str()); SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant(); const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact"; - off += snprintf(css + off, sizeof(css) - off, " -minikin-variant: %s;", varstr); + off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr); layout->setProperties(css); + return std::string(css); } float MinikinUtils::xOffsetForTextAlign(SkPaint* paint, const Layout& layout) { diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h index 997d6e3..ea7eb5d 100644 --- a/core/jni/android/graphics/MinikinUtils.h +++ b/core/jni/android/graphics/MinikinUtils.h @@ -26,10 +26,14 @@ namespace android { +class Layout; +class TypefaceImpl; + class MinikinUtils { public: - static void SetLayoutProperties(Layout* layout, const SkPaint* paint, int flags, - TypefaceImpl* face); + static std::string setLayoutProperties(Layout* layout, const SkPaint* paint, int bidiFlags, + TypefaceImpl* typeface); + static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout); // f is a functor of type void f(SkTypeface *, size_t start, size_t end); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 4000b07..3dc874e 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -520,8 +520,8 @@ public: #ifdef USE_MINIKIN Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray + index, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(textArray, index, count, textLength, css); result = layout.getAdvance(); #else TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength, @@ -554,8 +554,8 @@ public: #ifdef USE_MINIKIN Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray + start, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(textArray, start, count, textLength, css); width = layout.getAdvance(); #else TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength, @@ -582,8 +582,8 @@ public: #ifdef USE_MINIKIN Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray, textLength); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(textArray, 0, textLength, textLength, css); width = layout.getAdvance(); #else TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength, @@ -617,8 +617,8 @@ public: #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); layout.getAdvances(widthsArray); #else TextLayout::getTextRunAdvances(paint, text, 0, count, count, @@ -715,8 +715,8 @@ public: #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface); - layout.doLayout(text + start, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + layout.doLayout(text, start, count, contextCount, css); layout.getAdvances(advancesArray); totalAdvance = layout.getAdvance(); #else @@ -860,8 +860,8 @@ public: jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) { #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); size_t nGlyphs = layout.nGlyphs(); uint16_t* glyphs = new uint16_t[nGlyphs]; SkPoint* pos = new SkPoint[nGlyphs]; @@ -992,8 +992,8 @@ public: #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, &paint, bidiFlags, typeface); - layout.doLayout(text, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); MinikinRect rect; layout.getBounds(&rect); r.fLeft = rect.mLeft; diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp index ff52b07..27df7cf 100644 --- a/core/jni/android/graphics/TypefaceImpl.cpp +++ b/core/jni/android/graphics/TypefaceImpl.cpp @@ -32,6 +32,7 @@ #include <minikin/FontCollection.h> #include <minikin/FontFamily.h> #include <minikin/Layout.h> +#include "SkPaint.h" #include "MinikinSkia.h" #endif @@ -134,18 +135,19 @@ static TypefaceImpl* createFromSkTypeface(SkTypeface* typeface) { return result; } +// Delete when removing USE_MINIKIN ifdef TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style) { - // TODO: should create a font collection with all styles corresponding to - // the name SkTypeface* face = SkTypeface::CreateFromName(name, style); return createFromSkTypeface(face); } +// Delete when removing USE_MINIKIN ifdef TypefaceImpl* TypefaceImpl_createFromFile(const char* filename) { SkTypeface* face = SkTypeface::CreateFromFile(filename); return createFromSkTypeface(face); } +// Delete when removing USE_MINIKIN ifdef TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) { SkStream* stream = new AssetStreamAdaptor(asset, AssetStreamAdaptor::kYes_OwnAsset, @@ -158,7 +160,6 @@ TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) { } TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) { - ALOGD("createFromFamilies size=%d", size); std::vector<FontFamily *>familyVec; for (size_t i = 0; i < size; i++) { FontFamily* family = reinterpret_cast<FontFamily*>(families[i]); @@ -166,7 +167,18 @@ TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size } TypefaceImpl* result = new TypefaceImpl; result->fFontCollection = new FontCollection(familyVec); - result->fStyle = FontStyle(); // TODO: improve + if (size == 0) { + ALOGW("createFromFamilies creating empty collection"); + result->fStyle = FontStyle(); + } else { + const FontStyle defaultStyle; + FontFamily* firstFamily = reinterpret_cast<FontFamily*>(families[0]); + MinikinFont* mf = firstFamily->getClosestMatch(defaultStyle); + SkTypeface* skTypeface = reinterpret_cast<MinikinFontSkia*>(mf)->GetSkTypeface(); + // TODO: probably better to query more precise style from family, will be important + // when we open up API to access 100..900 weights + result->fStyle = styleFromSkiaStyle(skTypeface->style()); + } return result; } diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 820da17..a46ccd6 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -702,8 +702,8 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) { #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface); - layout.doLayout(text, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + layout.doLayout(text, 0, count, count, css); x += xOffsetForTextAlign(paint, layout.getAdvance()); renderTextLayout(renderer, &layout, x, y, paint); #else @@ -746,8 +746,8 @@ static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, int flags, SkPaint* paint, TypefaceImpl* typeface) { #ifdef USE_MINIKIN Layout layout; - MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface); - layout.doLayout(text + start, count); + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface); + layout.doLayout(text, start, count, contextCount, css); x += xOffsetForTextAlign(paint, layout.getAdvance()); renderTextLayout(renderer, &layout, x, y, paint); #else diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default.png b/core/res/res/drawable-hdpi/btn_code_lock_default.png Binary files differdeleted file mode 100644 index 4469ce0..0000000 --- a/core/res/res/drawable-hdpi/btn_code_lock_default.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png Binary files differdeleted file mode 100644 index 449d427..0000000 --- a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..7cc3c11 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched.png b/core/res/res/drawable-hdpi/btn_code_lock_touched.png Binary files differdeleted file mode 100644 index 0410dd3..0000000 --- a/core/res/res/drawable-hdpi/btn_code_lock_touched.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png Binary files differdeleted file mode 100644 index 66cb1ec..0000000 --- a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..70397d2 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index 698c3ec..0000000 --- a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..b9b400f --- /dev/null +++ b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png Binary files differdeleted file mode 100644 index c45b956..0000000 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_qntm_alpha.png Binary files differindex 7fe402a..b1601f4 100644 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png +++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png Binary files differdeleted file mode 100644 index b9fd0a4..0000000 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_qntm_alpha.png Binary files differindex 4052eed..a038a13 100644 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png +++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png Binary files differdeleted file mode 100644 index 94e947d..0000000 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png Binary files differdeleted file mode 100644 index 738d0fe..0000000 --- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/btn_code_lock_default.png b/core/res/res/drawable-ldpi/btn_code_lock_default.png Binary files differdeleted file mode 100644 index 149da9b..0000000 --- a/core/res/res/drawable-ldpi/btn_code_lock_default.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/btn_code_lock_touched.png b/core/res/res/drawable-ldpi/btn_code_lock_touched.png Binary files differdeleted file mode 100644 index ad9a313..0000000 --- a/core/res/res/drawable-ldpi/btn_code_lock_touched.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index ac8e42a..0000000 --- a/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png Binary files differdeleted file mode 100644 index 5b77b9f..0000000 --- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png Binary files differdeleted file mode 100644 index c7c0b9a..0000000 --- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png Binary files differdeleted file mode 100644 index ac02dc4..0000000 --- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default.png b/core/res/res/drawable-mdpi/btn_code_lock_default.png Binary files differdeleted file mode 100644 index 206f9b3..0000000 --- a/core/res/res/drawable-mdpi/btn_code_lock_default.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png Binary files differdeleted file mode 100644 index 4c4adf2..0000000 --- a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..14d0b32 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched.png b/core/res/res/drawable-mdpi/btn_code_lock_touched.png Binary files differdeleted file mode 100644 index fe5c1af..0000000 --- a/core/res/res/drawable-mdpi/btn_code_lock_touched.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png Binary files differdeleted file mode 100644 index ef701ed..0000000 --- a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..9cfbdf9 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index 7201e58..0000000 --- a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..2fb1325 --- /dev/null +++ b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png Binary files differdeleted file mode 100644 index 05c194b..0000000 --- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_qntm_alpha.png Binary files differindex 5762e5f..07d4afd 100644 --- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_holo.png +++ b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png Binary files differdeleted file mode 100644 index 8f24832..0000000 --- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_qntm_alpha.png Binary files differindex bfb0967..ea8c2b4 100644 --- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green_holo.png +++ b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png Binary files differdeleted file mode 100644 index 8c0386f..0000000 --- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png Binary files differdeleted file mode 100644 index cc46f19..0000000 --- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index cc46f19..0000000 --- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index 668cff7..d1e2df3 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -19,18 +19,21 @@ <viewport android:viewportHeight="25" android:viewportWidth="25" /> <path - android:name="shadow" - android:pathData="m12,2.5 a11,11 0 1,0 1,0 - M6.5,7.5 - l5,0 l0,7 l7,0 l0,5 l-12,0 z" - android:fill="#40000000" + android:name="torso" + android:pathData="m2,2 l21,0 l0,21 l-21,0 z" + android:fill="#FFFFFFFF" /> + + <path + android:name="|" + android:pathData="m4,4 l8,0 l0,17 l-8,0 z" + android:fill="#FF0000FF" + /> + <path - android:name="circle-L-ranch" - android:pathData="m12,1.5 a11,11 0 1,0 1,0 - M6.5,6.5 - l5,0 l0,7 l7,0 l0,5 l-12,0 z" - android:fill="#FFFFFF40" + android:name="_" + android:pathData="m5,14 l16,0 l0,6 l-16,0 z" + android:fill="#FFFF0000" /> </vector> diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml index b8ddb77..6b3be4a 100644 --- a/core/res/res/drawable-nodpi/stat_sys_adb.xml +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -18,13 +18,26 @@ <viewport android:viewportHeight="25" android:viewportWidth="25" /> - <path - android:name="adb" - android:pathData="m3,3l8,0l0,11l11,0l0,8l-19,0z" + android:name="L-card" + + android:pathData=" + m4,2 + a2,2,0,0,0,-2,2 l0,17 + a2,2,0,0,0,2,2 l17,0 + a2,2,0,0,0,2,-2 l0,-17 + a2,2,0,0,0,-2,-2 + z + + M7,2 l3,0 l0,13 l13,0 l0,3 l-16,0 + + M15,2 l3,0 l0,5 l5,0 l0,3 l-8,0 + + z" android:fill="#FFFFFFFF" /> + </vector> diff --git a/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index 2ab4547..0000000 --- a/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default.png b/core/res/res/drawable-xhdpi/btn_code_lock_default.png Binary files differdeleted file mode 100644 index c1358a2..0000000 --- a/core/res/res/drawable-xhdpi/btn_code_lock_default.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png Binary files differdeleted file mode 100644 index db1cbe6..0000000 --- a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..0c457b4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched.png Binary files differdeleted file mode 100644 index 0fafc3e..0000000 --- a/core/res/res/drawable-xhdpi/btn_code_lock_touched.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png Binary files differdeleted file mode 100644 index 073c3ac..0000000 --- a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..020d699 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png Binary files differdeleted file mode 100644 index 2d34cf6..0000000 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..fda5e37 --- /dev/null +++ b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png Binary files differdeleted file mode 100644 index 0812cb5..0000000 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_qntm_alpha.png Binary files differindex 6a97445..75d0221 100644 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_holo.png +++ b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png Binary files differdeleted file mode 100644 index 3ab2e99..0000000 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_qntm_alpha.png Binary files differindex f0e9ab9..225799b 100644 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green_holo.png +++ b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png Binary files differdeleted file mode 100644 index 170b833..0000000 --- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xxhdpi/btn_code_lock_default_qntm_alpha.png Binary files differindex 1b6c9b5..1b6c9b5 100755 --- a/core/res/res/drawable-xxhdpi/btn_code_lock_default_holo.png +++ b/core/res/res/drawable-xxhdpi/btn_code_lock_default_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xxhdpi/btn_code_lock_touched_qntm_alpha.png Binary files differindex dd13af8..dd13af8 100755 --- a/core/res/res/drawable-xxhdpi/btn_code_lock_touched_holo.png +++ b/core/res/res/drawable-xxhdpi/btn_code_lock_touched_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..d3e80be --- /dev/null +++ b/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_qntm_alpha.png Binary files differindex a11b6dd..a11b6dd 100644 --- a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_holo.png +++ b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_qntm_alpha.png Binary files differindex eae7ea8..eae7ea8 100644 --- a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_green_holo.png +++ b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png Binary files differdeleted file mode 100644 index f6c3e27..0000000 --- a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png +++ /dev/null diff --git a/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png Binary files differnew file mode 100644 index 0000000..23214fa --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0811c02..08c1680 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6344,6 +6344,12 @@ <attr name="aspect" format="string" /> <!-- Color to use when drawing LockPatternView paths. --> <attr name="pathColor" format="color|reference" /> + <!-- The regular pattern color --> + <attr name="regularColor" format="color|reference" /> + <!-- The error color --> + <attr name="errorColor" format="color|reference" /> + <!-- The success color --> + <attr name="successColor" format="color|reference"/> </declare-styleable> <!-- Use <code>recognition-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 5a2609e..9bf2ce8 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -115,6 +115,11 @@ <color name="kg_multi_user_text_inactive">#ff808080</color> <color name="kg_widget_pager_gradient">#ffffffff</color> + <!-- LockPatternView --> + <color name="lock_pattern_view_regular_color">#ffffffff</color> + <color name="lock_pattern_view_success_color">#ffffffff</color> + <color name="lock_pattern_view_error_color">#fff4511e</color> + <!-- FaceLock --> <color name="facelock_spotlight_mask">#CC000000</color> diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml index dffc9b0..f49861a 100644 --- a/core/res/res/values/colors_quantum.xml +++ b/core/res/res/values/colors_quantum.xml @@ -19,10 +19,8 @@ <color name="background_quantum_dark">#ff212121</color> <color name="background_quantum_light">#fffafafa</color> - <!-- Black 27% --> - <color name="ripple_quantum_light">#45000000</color> - <!-- White 19% --> - <color name="ripple_quantum_dark">#30ffffff</color> + <color name="ripple_quantum_light">#20444444</color> + <color name="ripple_quantum_dark">#20ffffff</color> <color name="button_quantum_dark">#ff5a595b</color> <color name="button_quantum_light">#ffd6d7d7</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8af45db..865d92a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -364,6 +364,9 @@ <!-- If this is true, the screen will come on when you unplug usb/power/whatever. --> <bool name="config_unplugTurnsOnScreen">false</bool> + <!-- Set this true only if the device has separate attention and notification lights. --> + <bool name="config_useAttentionLight">false</bool> + <!-- If this is true, the screen will fade off. --> <bool name="config_animateScreenLights">true</bool> diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml index a07d02b..b5afc2f 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -97,7 +97,6 @@ please see styles_device_defaults.xml. <item name="textColorLink">?attr/textColorLink</item> <item name="textSize">@dimen/text_size_body_1_quantum</item> <item name="fontFamily">@string/font_family_body_1_quantum</item> - <item name="elegantTextHeight">true</item> </style> <style name="TextAppearance.Quantum.Display4"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 41238a3..61b6a0d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1017,8 +1017,14 @@ <java-symbol type="drawable" name="text_edit_side_paste_window" /> <java-symbol type="drawable" name="text_edit_paste_window" /> <java-symbol type="drawable" name="btn_check_off" /> - <java-symbol type="drawable" name="btn_code_lock_default_holo" /> - <java-symbol type="drawable" name="btn_code_lock_touched_holo" /> + <java-symbol type="drawable" name="btn_code_lock_default_qntm_alpha" /> + <java-symbol type="drawable" name="btn_code_lock_touched_qntm_alpha" /> + <java-symbol type="drawable" name="indicator_code_lock_point_area_default_qntm_alpha" /> + <java-symbol type="drawable" name="indicator_code_lock_point_area_qntm_alpha" /> + <java-symbol type="drawable" name="indicator_code_lock_drag_direction_up_qntm_alpha" /> + <java-symbol type="color" name="lock_pattern_view_regular_color" /> + <java-symbol type="color" name="lock_pattern_view_success_color" /> + <java-symbol type="color" name="lock_pattern_view_error_color" /> <java-symbol type="drawable" name="clock_dial" /> <java-symbol type="drawable" name="clock_hand_hour" /> <java-symbol type="drawable" name="clock_hand_minute" /> @@ -1062,11 +1068,6 @@ <java-symbol type="drawable" name="ic_print" /> <java-symbol type="drawable" name="ic_print_error" /> <java-symbol type="drawable" name="ic_grayedout_printer" /> - <java-symbol type="drawable" name="indicator_code_lock_drag_direction_green_up" /> - <java-symbol type="drawable" name="indicator_code_lock_drag_direction_red_up" /> - <java-symbol type="drawable" name="indicator_code_lock_point_area_default_holo" /> - <java-symbol type="drawable" name="indicator_code_lock_point_area_green_holo" /> - <java-symbol type="drawable" name="indicator_code_lock_point_area_red_holo" /> <java-symbol type="drawable" name="jog_dial_arrow_long_left_green" /> <java-symbol type="drawable" name="jog_dial_arrow_long_right_red" /> <java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" /> @@ -1455,6 +1456,7 @@ <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> <java-symbol type="array" name="config_onlySingleDcAllowed" /> + <java-symbol type="bool" name="config_useAttentionLight" /> <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf Binary files differindex a30aa0c..b828205 100644 --- a/data/fonts/Roboto-MediumItalic.ttf +++ b/data/fonts/Roboto-MediumItalic.ttf diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 0ee253a..16548d0 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -94,7 +94,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An boolean changed = super.setVisible(visible, restart); if (visible) { if (changed || restart) { - setFrame(0, true, mCurFrame >= 0); + setFrame(0, true, restart || mCurFrame >= 0); } } else { unscheduleSelf(this); diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 1512da5..241b89e 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -1203,8 +1203,11 @@ public class GradientDrawable extends Drawable { st.mAttrStroke = a.extractThemeAttrs(); + // We have an explicit stroke defined, so the default stroke width + // must be at least 0 or the current stroke width. + final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); final int width = a.getDimensionPixelSize( - R.styleable.GradientDrawableStroke_width, st.mStrokeWidth); + R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); final float dashWidth = a.getDimension( R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); @@ -1406,10 +1409,13 @@ public class GradientDrawable extends Drawable { outline.setOval(bounds); return true; case LINE: - float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f; - float centerY = bounds.centerY(); - int top = (int) Math.floor(centerY - halfStrokeWidth); - int bottom = (int) Math.ceil(centerY + halfStrokeWidth); + // Hairlines (0-width stroke) must have a non-empty outline for + // shadows to draw correctly, so we'll use a very small width. + final float halfStrokeWidth = mStrokePaint == null ? + 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; + final float centerY = bounds.centerY(); + final int top = (int) Math.floor(centerY - halfStrokeWidth); + final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); outline.setRect(bounds.left, top, bounds.right, bottom); return true; diff --git a/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java index d756eb1..675355c 100644 --- a/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java +++ b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java @@ -54,6 +54,12 @@ class QuantumProgressDrawable extends Drawable implements Animatable { private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); + /** The duration of a single progress spin in milliseconds. */ + private static final int ANIMATION_DURATION = 1000 * 80 / 60; + + /** The number of points in the progress "star". */ + private static final int NUM_POINTS = 5; + /** The list of animators operating on this drawable. */ private final ArrayList<Animator> mAnimators = new ArrayList<Animator>(); @@ -62,6 +68,9 @@ class QuantumProgressDrawable extends Drawable implements Animatable { private QuantumProgressState mState; + /** Canvas rotation in degrees. */ + private float mRotation; + private boolean mMutated; public QuantumProgressDrawable() { @@ -187,7 +196,11 @@ class QuantumProgressDrawable extends Drawable implements Animatable { @Override public void draw(Canvas c) { - mRing.draw(c, getBounds()); + final Rect bounds = getBounds(); + final int saveCount = c.save(); + c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); + mRing.draw(c, bounds); + c.restoreToCount(saveCount); } @Override @@ -210,6 +223,15 @@ class QuantumProgressDrawable extends Drawable implements Animatable { return mRing.getColorFilter(); } + private void setRotation(float rotation) { + mRotation = rotation; + invalidateSelf(); + } + + private float getRotation() { + return mRotation; + } + @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; @@ -256,26 +278,33 @@ class QuantumProgressDrawable extends Drawable implements Animatable { final Ring ring = mRing; final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f); - endTrim.setDuration(1000 * 80 / 60); + endTrim.setDuration(ANIMATION_DURATION); endTrim.setInterpolator(START_CURVE_INTERPOLATOR); endTrim.setRepeatCount(ObjectAnimator.INFINITE); endTrim.setRepeatMode(ObjectAnimator.RESTART); final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f); - startTrim.setDuration(1000 * 80 / 60); + startTrim.setDuration(ANIMATION_DURATION); startTrim.setInterpolator(END_CURVE_INTERPOLATOR); startTrim.setRepeatCount(ObjectAnimator.INFINITE); startTrim.setRepeatMode(ObjectAnimator.RESTART); final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f); - rotation.setDuration(1000 * 80 / 60); + rotation.setDuration(ANIMATION_DURATION); rotation.setInterpolator(LINEAR_INTERPOLATOR); rotation.setRepeatCount(ObjectAnimator.INFINITE); rotation.setRepeatMode(ObjectAnimator.RESTART); + final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f); + groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION); + groupRotation.setInterpolator(LINEAR_INTERPOLATOR); + groupRotation.setRepeatCount(ObjectAnimator.INFINITE); + groupRotation.setRepeatMode(ObjectAnimator.RESTART); + mAnimators.add(endTrim); mAnimators.add(startTrim); mAnimators.add(rotation); + mAnimators.add(groupRotation); } private final Callback mCallback = new Callback() { diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 55d01ed..096e554 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.util.MathUtils; import android.view.HardwareCanvas; import android.view.RenderNodeAnimator; +import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import java.util.ArrayList; @@ -37,13 +38,16 @@ import java.util.ArrayList; */ class Ripple { private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + private static final TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator(4); private static final float GLOBAL_SPEED = 1.0f; - private static final float WAVE_TOUCH_DOWN_ACCELERATION = 512.0f * GLOBAL_SPEED; - private static final float WAVE_TOUCH_UP_ACCELERATION = 1024.0f * GLOBAL_SPEED; - private static final float WAVE_OPACITY_DECAY_VELOCITY = 1.6f / GLOBAL_SPEED; + private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED; + private static final float WAVE_TOUCH_UP_ACCELERATION = 3096.0f * GLOBAL_SPEED; + private static final float WAVE_OPACITY_DECAY_VELOCITY = 1.9f / GLOBAL_SPEED; private static final float WAVE_OUTER_OPACITY_VELOCITY = 1.2f * GLOBAL_SPEED; + private static final long RIPPLE_ENTER_DELAY = 100; + // Hardware animators. private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>(); private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>(); @@ -287,14 +291,19 @@ class Ripple { radius.setAutoCancel(true); radius.setDuration(radiusDuration); radius.setInterpolator(LINEAR_INTERPOLATOR); + radius.setStartDelay(RIPPLE_ENTER_DELAY); final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1); cX.setAutoCancel(true); cX.setDuration(radiusDuration); + cX.setInterpolator(LINEAR_INTERPOLATOR); + cX.setStartDelay(RIPPLE_ENTER_DELAY); final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1); cY.setAutoCancel(true); cY.setDuration(radiusDuration); + cY.setInterpolator(LINEAR_INTERPOLATOR); + cY.setStartDelay(RIPPLE_ENTER_DELAY); final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1); outer.setAutoCancel(true); @@ -377,15 +386,15 @@ class Ripple { final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius); radiusAnim.setDuration(radiusDuration); - radiusAnim.setInterpolator(LINEAR_INTERPOLATOR); + radiusAnim.setInterpolator(DECEL_INTERPOLATOR); final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX); xAnim.setDuration(radiusDuration); - xAnim.setInterpolator(LINEAR_INTERPOLATOR); + xAnim.setInterpolator(DECEL_INTERPOLATOR); final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY); yAnim.setDuration(radiusDuration); - yAnim.setInterpolator(LINEAR_INTERPOLATOR); + yAnim.setInterpolator(DECEL_INTERPOLATOR); final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0); @@ -439,17 +448,17 @@ class Ripple { final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1); radiusAnim.setAutoCancel(true); radiusAnim.setDuration(radiusDuration); - radiusAnim.setInterpolator(LINEAR_INTERPOLATOR); + radiusAnim.setInterpolator(DECEL_INTERPOLATOR); final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1); xAnim.setAutoCancel(true); xAnim.setDuration(radiusDuration); - xAnim.setInterpolator(LINEAR_INTERPOLATOR); + xAnim.setInterpolator(DECEL_INTERPOLATOR); final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1); yAnim.setAutoCancel(true); yAnim.setDuration(radiusDuration); - yAnim.setInterpolator(LINEAR_INTERPOLATOR); + yAnim.setInterpolator(DECEL_INTERPOLATOR); final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0); opacityAnim.setAutoCancel(true); diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 6f29825..58f6eaa 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -95,6 +95,9 @@ public class RippleDrawable extends LayerDrawable { private final RippleState mState; + /** The masking layer, e.g. the layer with id R.id.mask. */ + private Drawable mMask; + /** The current hotspot. May be actively animating or pending entry. */ private Ripple mHotspot; @@ -261,21 +264,14 @@ public class RippleDrawable extends LayerDrawable { super.inflate(r, parser, attrs, theme); setTargetDensity(r.getDisplayMetrics()); - - // Find the mask - final int N = getNumberOfLayers(); - for (int i = 0; i < N; i++) { - if (mLayerState.mChildren[i].mId == R.id.mask) { - mState.mMask = mLayerState.mChildren[i].mDrawable; - } - } + initializeFromState(); } @Override public boolean setDrawableByLayerId(int id, Drawable drawable) { if (super.setDrawableByLayerId(id, drawable)) { if (id == R.id.mask) { - mState.mMask = drawable; + mMask = drawable; } return true; @@ -361,6 +357,8 @@ public class RippleDrawable extends LayerDrawable { } finally { a.recycle(); } + + initializeFromState(); } @Override @@ -527,7 +525,7 @@ public class RippleDrawable extends LayerDrawable { private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { final int count = mLayerState.mNum; - if (count == 0 || (mState.mMask != null && count == 1)) { + if (count == 0 || (mMask != null && count == 1)) { return -1; } @@ -611,7 +609,7 @@ public class RippleDrawable extends LayerDrawable { } private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { - final Drawable mask = mState.mMask; + final Drawable mask = mMask; if (mask == null) { return -1; } @@ -667,7 +665,6 @@ public class RippleDrawable extends LayerDrawable { int[] mTouchThemeAttrs; ColorStateList mTint = null; PorterDuffXfermode mTintXfermode = SRC_ATOP; - Drawable mMask; int mMaxRadius = RADIUS_AUTO; boolean mPinned = false; @@ -763,8 +760,6 @@ public class RippleDrawable extends LayerDrawable { } mState = ns; - mState.mMask = findDrawableByLayerId(R.id.mask); - mLayerState = ns; if (ns.mNum > 0) { @@ -774,5 +769,12 @@ public class RippleDrawable extends LayerDrawable { if (needsTheme) { applyTheme(theme); } + + initializeFromState(); + } + + private void initializeFromState() { + // Initialize from constant state. + mMask = findDrawableByLayerId(R.id.mask); } } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index f0ae00f..dff4f6c 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -153,6 +153,11 @@ public: // TODO: rename for consistency virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty); +protected: + // NOTE: must override these to avoid calling into super class, which calls GL. These may be + // removed once DisplayListRenderer no longer inherits from OpenGLRenderer + virtual void onViewportInitialized() {}; + virtual void onSnapshotRestored() {}; private: void insertRestoreToCount(); diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java index 782ecd8..d5fea07 100644 --- a/media/java/android/media/AudioPortEventHandler.java +++ b/media/java/android/media/AudioPortEventHandler.java @@ -56,7 +56,6 @@ class AudioPortEventHandler { mHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { - Log.i(TAG, "handleMessage: "+msg.what); ArrayList<AudioManager.OnAudioPortUpdateListener> listeners; synchronized (this) { if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) { diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java index 1c9730f..7977988 100644 --- a/media/java/android/media/WebVttRenderer.java +++ b/media/java/android/media/WebVttRenderer.java @@ -1103,6 +1103,9 @@ class WebVttTrack extends SubtitleTrack implements WebVttCueListener { */ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget { private static final boolean DEBUG = false; + + private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT; + private static final int DEBUG_REGION_BACKGROUND = 0x800000FF; private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000; @@ -1144,7 +1147,8 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering this(context, attrs, defStyleAttr, 0); } - public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public WebVttRenderingWidget( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); // Cannot render text over video when layer type is hardware. @@ -1259,6 +1263,7 @@ class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.Rendering } private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + captionStyle = DEFAULT_CAPTION_STYLE.applyStyle(captionStyle); mCaptionStyle = captionStyle; mFontSize = fontSize; diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 6e0586e..5e650c2 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -86,6 +86,27 @@ public final class TvContract { } /** + * Builds a URI that points to a channel logo. See {@link Channels.Logo}. + * + * @param channelId The ID of the channel whose logo is pointed to. + */ + public static final Uri buildChannelLogoUri(long channelId) { + return buildChannelLogoUri(buildChannelUri(channelId)); + } + + /** + * Builds a URI that points to a channel logo. See {@link Channels.Logo}. + * + * @param channelUri The URI of the channel whose logo is pointed to. + */ + public static final Uri buildChannelLogoUri(Uri channelUri) { + if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) { + throw new IllegalArgumentException("Not a channel: " + channelUri); + } + return Uri.withAppendedPath(channelUri, Channels.Logo.CONTENT_DIRECTORY); + } + + /** * Builds a URI that points to all browsable channels from a given TV input. * * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that @@ -523,6 +544,48 @@ public final class TvContract { public static final String COLUMN_VERSION_NUMBER = "version_number"; private Channels() {} + + /** + * A sub-directory of a single TV channel that represents its primary logo. + * <p> + * To access this directory, append {@link Channels.Logo#CONTENT_DIRECTORY} to the raw + * channel URI. The resulting URI represents an image file, and should be interacted + * using ContentResolver.openAssetFileDescriptor. + * </p> + * <p> + * Note that this sub-directory also supports opening the logo as an asset file in write + * mode. Callers can create or replace the primary logo associated with this channel by + * opening the asset file and writing the full-size photo contents into it. When the file + * is closed, the image will be parsed, sized down if necessary, and stored. + * </p> + * <p> + * Usage example: + * <pre> + * public void writeChannelLogo(long channelId, byte[] logo) { + * Uri channelLogoUri = TvContract.buildChannelLogoUri(channelId); + * try { + * AssetFileDescriptor fd = + * getContentResolver().openAssetFileDescriptor(channelLogoUri, "rw"); + * OutputStream os = fd.createOutputStream(); + * os.write(logo); + * os.close(); + * fd.close(); + * } catch (IOException e) { + * // Handle error cases. + * } + * } + * </pre> + * </p> + */ + public static final class Logo { + + /** + * The directory twig for this sub-table. + */ + public static final String CONTENT_DIRECTORY = "logo"; + + private Logo() {} + } } /** Column definitions for the TV programs table. */ @@ -631,6 +694,26 @@ public final class TvContract { public static final String COLUMN_AUDIO_LANGUAGE = "audio_language"; /** + * The URI for the poster art of this TV program. + * <p> + * Can be empty. + * </p><p> + * Type: TEXT + * </p> + */ + public static final String COLUMN_POSTER_ART_URI = "poster_art_uri"; + + /** + * The URI for the thumbnail of this TV program. + * <p> + * Can be empty. + * </p><p> + * Type: TEXT + * </p> + */ + public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; + + /** * Internal data used by individual TV input services. * <p> * This is internal to the provider that inserted it, and should not be decoded by other diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java index 0cc09c8..116d755 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java @@ -61,6 +61,11 @@ public class Recents extends SystemUI implements RecentsComponent { @Override protected void onBootCompleted() { + if (mUseAlternateRecents) { + if (mAlternateRecents != null) { + mAlternateRecents.onBootCompleted(); + } + } mBootCompleted = true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index bb19415..2f6d58f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -153,6 +153,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta Messenger mService = null; Messenger mMessenger; RecentsMessageHandler mHandler; + boolean mBootCompleted = false; boolean mServiceIsBound = false; boolean mToggleRecentsUponServiceBound; RecentsServiceConnection mConnection = new RecentsServiceConnection(); @@ -182,6 +183,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta bindToRecentsService(false); } + public void onBootCompleted() { + mBootCompleted = true; + } + /** Shows the recents */ public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) { if (Console.Enabled) { @@ -208,7 +213,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta if (Console.Enabled) { Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]"); } - if (mServiceIsBound) { + if (mServiceIsBound && mBootCompleted) { // Notify recents to close it try { Bundle data = new Bundle(); @@ -278,7 +283,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Updates each of the task animation rects. */ void updateAnimationRects() { - if (mServiceIsBound) { + if (mServiceIsBound && mBootCompleted) { Resources res = mContext.getResources(); int statusBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index dcd187c..f013d13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -33,11 +33,13 @@ import android.graphics.Shader; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewAnimationUtils; import android.view.ViewConfiguration; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; + import com.android.systemui.R; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -219,7 +221,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView int heightHalf = mBackgroundNormal.getActualHeight()/2; float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf); ValueAnimator animator = - mBackgroundNormal.createRevealAnimator(widthHalf, heightHalf, 0, radius); + ViewAnimationUtils.createCircularReveal(mBackgroundNormal, + widthHalf, heightHalf, 0, radius); mBackgroundNormal.setVisibility(View.VISIBLE); Interpolator interpolator; Interpolator alphaInterpolator; 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 b28dad7..34179cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -48,7 +48,6 @@ public class NotificationPanelView extends PanelView implements View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, KeyguardPageSwipeHelper.Callback { - private static final float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f; private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; private KeyguardPageSwipeHelper mPageSwiper; @@ -698,11 +697,9 @@ public class NotificationPanelView extends PanelView implements @Override protected int getMaxPanelHeight() { // TODO: Figure out transition for collapsing when QS is open, adjust height here. - int maxPanelHeight = super.getMaxPanelHeight(); int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); - emptyBottomMargin += mStackScrollerContainer.getHeight() - - mNotificationStackScroller.getHeight(); - int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment; + int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin + - mTopPaddingAdjustment; maxHeight = Math.max(maxHeight, mStatusBarMinHeight); return maxHeight; } @@ -721,6 +718,16 @@ public class NotificationPanelView extends PanelView implements updateUnlockIcon(); } + @Override + protected float getOverExpansionAmount() { + return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); + } + + @Override + protected float getOverExpansionPixels() { + return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); + } + private void updateUnlockIcon() { if (mStatusBar.getBarState() == StatusBarState.KEYGUARD || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { @@ -807,14 +814,17 @@ public class NotificationPanelView extends PanelView implements } @Override - protected void onOverExpansionChanged(float overExpansion) { + protected void setOverExpansion(float overExpansion, boolean isPixels) { if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { - float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true); - float expansionChange = overExpansion - mOverExpansion; - expansionChange *= EXPANSION_RUBBER_BAND_EXTRA_FACTOR; - mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + expansionChange, - true /* onTop */, - false /* animate */); + mNotificationStackScroller.setOnHeightChangedListener(null); + if (isPixels) { + mNotificationStackScroller.setOverScrolledPixels( + overExpansion, true /* onTop */, false /* animate */); + } else { + mNotificationStackScroller.setOverScrollAmount( + overExpansion, true /* onTop */, false /* animate */); + } + mNotificationStackScroller.setOnHeightChangedListener(this); } } @@ -830,7 +840,10 @@ public class NotificationPanelView extends PanelView implements @Override protected void onTrackingStopped(boolean expand) { super.onTrackingStopped(expand); - mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, true /* animate */); + if (expand) { + mNotificationStackScroller.setOverScrolledPixels( + 0.0f, true /* onTop */, true /* animate */); + } if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { mPageSwiper.showAllIcons(true); 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 89a1907..772d0e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -41,7 +41,6 @@ import java.io.PrintWriter; public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); - protected float mOverExpansion; private final void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); @@ -61,6 +60,7 @@ public abstract class PanelView extends FrameLayout { private int mTrackingPointer; protected int mTouchSlop; protected boolean mHintAnimationRunning; + private boolean mOverExpandedBeforeFling; private ValueAnimator mHeightAnimator; private ObjectAnimator mPeekAnimator; @@ -370,13 +370,12 @@ public abstract class PanelView extends FrameLayout { protected void fling(float vel, boolean expand) { cancelPeek(); float target = expand ? getMaxPanelHeight() : 0.0f; - if (target == mExpandedHeight || mOverExpansion > 0) { + if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { onExpandingFinished(); - mExpandedHeight = target; - mOverExpansion = 0.0f; mBar.panelExpansionChanged(this, mExpandedFraction); return; } + mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; ValueAnimator animator = createHeightAnimator(target); if (expand) { mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); @@ -396,8 +395,8 @@ public abstract class PanelView extends FrameLayout { onExpandingFinished(); } }); - animator.start(); mHeightAnimator = animator; + animator.start(); } @Override @@ -433,7 +432,7 @@ public abstract class PanelView extends FrameLayout { public void setExpandedHeight(float height) { if (DEBUG) logf("setExpandedHeight(%.1f)", height); - setExpandedHeightInternal(height); + setExpandedHeightInternal(height + getOverExpansionPixels()); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); } @@ -451,32 +450,39 @@ public abstract class PanelView extends FrameLayout { // If the user isn't actively poking us, let's update the height if (!mTracking && mHeightAnimator == null && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) { - setExpandedHeightInternal(currentMaxPanelHeight); + setExpandedHeight(currentMaxPanelHeight); } } public void setExpandedHeightInternal(float h) { - float fh = getMaxPanelHeight(); - mExpandedHeight = Math.max(0, Math.min(fh, h)); - float overExpansion = h - fh; - overExpansion = Math.max(0, overExpansion); - if (overExpansion != mOverExpansion) { - onOverExpansionChanged(overExpansion); - mOverExpansion = overExpansion; - } - - if (DEBUG) { - logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh, mTracking ? "T" : "f"); + float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); + if (mHeightAnimator == null) { + float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); + if (getOverExpansionPixels() != overExpansionPixels && mTracking) { + setOverExpansion(overExpansionPixels, true /* isPixels */); + } + mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); + } else { + mExpandedHeight = h; + if (mOverExpandedBeforeFling) { + setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); + } } onHeightUpdated(mExpandedHeight); - mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : mExpandedHeight / fh); + mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 + ? 0 + : mExpandedHeight / fhWithoutOverExpansion); } - protected abstract void onOverExpansionChanged(float overExpansion); + protected abstract void setOverExpansion(float overExpansion, boolean isPixels); protected abstract void onHeightUpdated(float expandedHeight); + protected abstract float getOverExpansionAmount(); + + protected abstract float getOverExpansionPixels(); + /** * This returns the maximum height of the panel. Children should override this if their * desired height is not the full height. @@ -624,7 +630,8 @@ public abstract class PanelView extends FrameLayout { animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - setExpandedHeight((Float) animation.getAnimatedValue()); + setExpandedHeightInternal((Float) animation.getAnimatedValue()); + mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); } }); return animator; 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 e55de94..227304c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -77,6 +77,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; +import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewPropertyAnimator; @@ -757,7 +758,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public ValueAnimator createRevealAnimator(View v, int centerX, int centerY, float startRadius, float endRadius) { - return v.createRevealAnimator(centerX, centerY, startRadius, endRadius); + return ViewAnimationUtils.createCircularReveal(v, centerX, centerY, + startRadius, endRadius); } }); final QSTileHost qsh = new QSTileHost(mContext, this, @@ -2851,7 +2853,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void updatePublicMode() { - setLockscreenPublicMode(mState == StatusBarState.KEYGUARD + setLockscreenPublicMode( + (mStatusBarKeyguardViewManager.isShowing() || + mStatusBarKeyguardViewManager.isOccluded()) && mStatusBarKeyguardViewManager.isSecure()); } 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 e3145a6..09e4d94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -181,6 +181,10 @@ public class StatusBarKeyguardViewManager { reset(); } + public boolean isOccluded() { + return mOccluded; + } + /** * Hides the keyguard view */ 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 0383706..4d86213 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -52,6 +52,7 @@ public class NotificationStackScrollLayout extends ViewGroup private static final boolean DEBUG = false; private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; + private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; /** * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. @@ -1267,11 +1268,14 @@ public class NotificationStackScrollLayout extends ViewGroup } private float getRubberBandFactor() { - return mExpandedInThisMotion - ? RUBBER_BAND_FACTOR_AFTER_EXPAND - : (mScrolledToTopOnFirstDown - ? 1.0f - : RUBBER_BAND_FACTOR_NORMAL); + if (mExpandedInThisMotion) { + return RUBBER_BAND_FACTOR_AFTER_EXPAND; + } else if (mIsExpansionChanging) { + return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; + } else if (mScrolledToTopOnFirstDown) { + return 1.0f; + } + return RUBBER_BAND_FACTOR_NORMAL; } private void endDrag() { 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 2edd7d1..225398a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -726,11 +726,21 @@ public class StackStateAnimator { @Override public void onAnimationUpdate(ValueAnimator animation) { float currentOverScroll = (float) animation.getAnimatedValue(); - mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */, - false /* cancelAnimators */); + mHostLayout.setOverScrollAmount( + currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */); } }); overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator); + overScrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (onTop) { + mTopOverScrollAnimator = null; + } else { + mBottomOverScrollAnimator = null; + } + } + }); overScrollAnimator.start(); if (onTop) { mTopOverScrollAnimator = overScrollAnimator; diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index c7d2871..c502bf3 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -365,15 +365,8 @@ public final class ContentService extends IContentService.Stub { Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); SyncStorageEngine.EndPoint info; - if (!request.hasAuthority()) { - // Extra permissions checking for sync service. - verifySignatureForPackage(callerUid, - request.getService().getPackageName(), "sync"); - info = new SyncStorageEngine.EndPoint(request.getService(), userId); - } else { - info = new SyncStorageEngine.EndPoint( - request.getAccount(), request.getProvider(), userId); - } + info = new SyncStorageEngine.EndPoint( + request.getAccount(), request.getProvider(), userId); if (runAtTime < 60) { Slog.w(TAG, "Requested poll frequency of " + runAtTime + " seconds being rounded up to 60 seconds."); @@ -385,17 +378,10 @@ public final class ContentService extends IContentService.Stub { } else { long beforeRuntimeMillis = (flextime) * 1000; long runtimeMillis = runAtTime * 1000; - if (request.hasAuthority()) { syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, beforeRuntimeMillis, runtimeMillis, false /* onlyThoseWithUnknownSyncableState */); - } else { - syncManager.scheduleSync( - request.getService(), userId, callerUid, extras, - beforeRuntimeMillis, - runtimeMillis); // Empty function. - } } } finally { restoreCallingIdentity(identityToken); @@ -442,22 +428,14 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager == null) return; int userId = UserHandle.getCallingUserId(); - int callerUid = Binder.getCallingUid(); long identityToken = clearCallingIdentity(); try { SyncStorageEngine.EndPoint info; Bundle extras = new Bundle(request.getBundle()); - if (request.hasAuthority()) { - Account account = request.getAccount(); - String provider = request.getProvider(); - info = new SyncStorageEngine.EndPoint(account, provider, userId); - } else { - // Only allowed to manipulate syncs for a service which you own. - ComponentName service = request.getService(); - verifySignatureForPackage(callerUid, service.getPackageName(), "cancel"); - info = new SyncStorageEngine.EndPoint(service, userId); - } + Account account = request.getAccount(); + String provider = request.getProvider(); + info = new SyncStorageEngine.EndPoint(account, provider, userId); if (request.isPeriodic()) { // Remove periodic sync. mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, @@ -599,20 +577,11 @@ public final class ContentService extends IContentService.Stub { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); - int callerUid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { - if (cname == null) { - return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( - new SyncStorageEngine.EndPoint(account, providerName, userId)); - } else if (account == null && providerName == null) { - verifySignatureForPackage(callerUid, cname.getPackageName(), "getPeriodicSyncs"); - return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( - new SyncStorageEngine.EndPoint(cname, userId)); - } else { - throw new IllegalArgumentException("Invalid authority specified"); - } + return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( + new SyncStorageEngine.EndPoint(account, providerName, userId)); } finally { restoreCallingIdentity(identityToken); } @@ -656,45 +625,6 @@ public final class ContentService extends IContentService.Stub { } } - public void setServiceActive(ComponentName cname, boolean active) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, - "no permission to write the sync settings"); - verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(), - "setServiceActive"); - - int userId = UserHandle.getCallingUserId(); - long identityToken = clearCallingIdentity(); - try { - SyncManager syncManager = getSyncManager(); - if (syncManager != null) { - syncManager.getSyncStorageEngine().setIsTargetServiceActive( - cname, userId, active); - } - } finally { - restoreCallingIdentity(identityToken); - } - } - - public boolean isServiceActive(ComponentName cname) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, - "no permission to read the sync settings"); - verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(), - "isServiceActive"); - - int userId = UserHandle.getCallingUserId(); - long identityToken = clearCallingIdentity(); - try { - SyncManager syncManager = getSyncManager(); - if (syncManager != null) { - return syncManager.getSyncStorageEngine() - .getIsTargetServiceActive(cname, userId); - } - } finally { - restoreCallingIdentity(identityToken); - } - return false; - } - @Override public boolean getMasterSyncAutomatically() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, @@ -741,18 +671,11 @@ public final class ContentService extends IContentService.Stub { if (syncManager == null) { return false; } - if (cname == null) { - return syncManager.getSyncStorageEngine().isSyncActive( - new SyncStorageEngine.EndPoint(account, authority, userId)); - } else if (account == null && authority == null) { - verifySignatureForPackage(callingUid, cname.getPackageName(), "isSyncActive"); - return syncManager.getSyncStorageEngine().isSyncActive( - new SyncStorageEngine.EndPoint(cname, userId)); - } + return syncManager.getSyncStorageEngine().isSyncActive( + new SyncStorageEngine.EndPoint(account, authority, userId)); } finally { restoreCallingIdentity(identityToken); } - return false; } public List<SyncInfo> getCurrentSyncs() { @@ -784,11 +707,8 @@ public final class ContentService extends IContentService.Stub { return null; } SyncStorageEngine.EndPoint info; - if (cname == null) { + if (!(account == null || authority == null)) { info = new SyncStorageEngine.EndPoint(account, authority, userId); - } else if (account == null && authority == null) { - verifySignatureForPackage(callerUid, cname.getPackageName(), "getSyncStatus"); - info = new SyncStorageEngine.EndPoint(cname, userId); } else { throw new IllegalArgumentException("Must call sync status with valid authority"); } @@ -810,11 +730,8 @@ public final class ContentService extends IContentService.Stub { try { SyncStorageEngine.EndPoint info; - if (cname == null) { + if (!(account == null || authority == null)) { info = new SyncStorageEngine.EndPoint(account, authority, userId); - } else if (account == null && authority == null) { - verifySignatureForPackage(callerUid, cname.getPackageName(), "isSyncPending"); - info = new SyncStorageEngine.EndPoint(cname, userId); } else { throw new IllegalArgumentException("Invalid authority specified"); } @@ -855,30 +772,6 @@ public final class ContentService extends IContentService.Stub { } /** - * Helper to verify that the provided package name shares the same cert as the caller. - * @param callerUid uid of the calling process. - * @param packageName package to verify against package of calling application. - * @param tag a tag to use when throwing an exception if the signatures don't - * match. Cannot be null. - * @return true if the calling application and the provided package are signed with the same - * certificate. - */ - private boolean verifySignatureForPackage(int callerUid, String packageName, String tag) { - PackageManager pm = mContext.getPackageManager(); - try { - int serviceUid = pm.getApplicationInfo(packageName, 0).uid; - if (pm.checkSignatures(callerUid, serviceUid) == PackageManager.SIGNATURE_MATCH) { - return true; - } else { - throw new SecurityException(tag + ": Caller certificate does not match that for - " - + packageName); - } - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException(tag + ": " + packageName + " package not found."); - } - } - - /** * Hide this class since it is not part of api, * but current unittest framework requires it to be public * @hide diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 35c494d..9499370 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -925,10 +925,7 @@ public class SyncStorageEngine extends Handler { period, flextime); } else { - toUpdate = new PeriodicSync(info.service, - extras, - period, - flextime); + return; } AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, false); @@ -1246,7 +1243,6 @@ public class SyncStorageEngine extends Handler { authorityInfo.ident, authorityInfo.target.account, authorityInfo.target.provider, - authorityInfo.target.service, activeSyncContext.mStartTime); getCurrentSyncs(authorityInfo.target.userId).add(syncInfo); } @@ -1262,8 +1258,7 @@ public class SyncStorageEngine extends Handler { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeActiveSync: account=" + syncInfo.account + " user=" + userId - + " auth=" + syncInfo.authority - + " service=" + syncInfo.service); + + " auth=" + syncInfo.authority); } getCurrentSyncs(userId).remove(syncInfo); } @@ -2109,12 +2104,8 @@ public class SyncStorageEngine extends Handler { extras, period, flextime); } else { - periodicSync = - new PeriodicSync( - authorityInfo.target.service, - extras, - period, - flextime); + Log.e(TAG, "Unknown target."); + return null; } authorityInfo.periodicSyncs.add(periodicSync); return periodicSync; @@ -2700,7 +2691,10 @@ public class SyncStorageEngine extends Handler { if (authorityInfo.target.target_provider) { req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider); } else { - req.setSyncAdapter(authorityInfo.target.service); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Unknown target, skipping sync request."); + } + return; } ContentResolver.requestSync(req.build()); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 495db20..386402b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -163,6 +163,7 @@ public class NotificationManagerService extends SystemService { private long[] mDefaultVibrationPattern; private long[] mFallbackVibrationPattern; + private boolean mUseAttentionLight; boolean mSystemReady; private boolean mDisableNotificationAlerts; @@ -182,7 +183,7 @@ public class NotificationManagerService extends SystemService { new ArrayMap<String, NotificationRecord>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>(); - ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); + ArrayList<String> mLights = new ArrayList<String>(); NotificationRecord mLedNotification; private AppOpsManager mAppOps; @@ -797,6 +798,8 @@ public class NotificationManagerService extends SystemService { VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); + mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight); + // Don't start allowing notifications until the setup wizard has run once. // After that, including subsequent boots, init with notifications turned on. // This works on the first boot because the setup wizard will toggle this @@ -1478,14 +1481,14 @@ public class NotificationManagerService extends SystemService { } } - // 1. initial score: buckets of 10, around the app - int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] + // 1. initial score: buckets of 10, around the app [-20..20] + final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; // 2. extract ranking signals from the notification data final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, callingUid, callingPid, score, notification, user); - NotificationRecord r = new NotificationRecord(n); + NotificationRecord r = new NotificationRecord(n, score); NotificationRecord old = mNotificationsByKey.get(n.getKey()); if (old != null) { // Retain ranking information from previous record @@ -1507,13 +1510,13 @@ public class NotificationManagerService extends SystemService { // blocked apps if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { if (!isSystemNotification) { - score = JUNK_SCORE; + r.score = JUNK_SCORE; Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request."); } } - if (score < SCORE_DISPLAY_THRESHOLD) { + if (r.score < SCORE_DISPLAY_THRESHOLD) { // Notification will be blocked because the score is too low. return; } @@ -1529,28 +1532,16 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerUpdatedByApp(r, old); // Make sure we don't lose the foreground service state. notification.flags |= - old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; mNotificationsByKey.remove(old.sbn.getKey()); + r.isUpdate = true; } mNotificationsByKey.put(n.getKey(), r); applyZenModeLocked(r); - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) && - !r.isIntercepted(); - if (DBG || r.isIntercepted()) Slog.v(TAG, - "pkg=" + pkg + " canInterrupt=" + canInterrupt + - " intercept=" + r.isIntercepted()); Collections.sort(mNotificationList, mRankingComparator); - // Ensure if this is a foreground service that the proper additional - // flags are set. - if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; - } - final int currentUser; final long token = Binder.clearCallingIdentity(); try { @@ -1571,20 +1562,11 @@ public class NotificationManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { mStatusBar.addNotification(n); - if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mAttentionLight.pulse(); - } } finally { Binder.restoreCallingIdentity(identity); } } - // Send accessibility events only for the current user. - if (currentUser == userId) { - sendAccessibilityEvent(notification, pkg); - } - - mListeners.notifyPostedLocked(r.sbn); + mListeners.notifyPostedLocked(n); } else { Slog.e(TAG, "Not posting notification with icon==0: " + notification); if (old != null && !old.isCanceled) { @@ -1595,7 +1577,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } - mListeners.notifyRemovedLocked(r.sbn); + mListeners.notifyRemovedLocked(n); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid @@ -1604,141 +1586,171 @@ public class NotificationManagerService extends SystemService { + n.getPackageName()); } - // If we're not supposed to beep, vibrate, etc. then don't. - if (!mDisableNotificationAlerts - && (!(old != null - && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) - && (r.getUserId() == UserHandle.USER_ALL || - (r.getUserId() == userId && r.getUserId() == currentUser) || - mUserProfiles.isCurrentProfile(r.getUserId())) - && canInterrupt - && mSystemReady - && mAudioManager != null) { - if (DBG) Slog.v(TAG, "Interrupting!"); - // sound - - // should we use the default notification sound? (indicated either by - // DEFAULT_SOUND or because notification.sound is pointing at - // Settings.System.NOTIFICATION_SOUND) - final boolean useDefaultSound = - (notification.defaults & Notification.DEFAULT_SOUND) != 0 || - Settings.System.DEFAULT_NOTIFICATION_URI - .equals(notification.sound); - - Uri soundUri = null; - boolean hasValidSound = false; - - if (useDefaultSound) { - soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; - - // check to see if the default notification sound is silent - ContentResolver resolver = getContext().getContentResolver(); - hasValidSound = Settings.System.getString(resolver, - Settings.System.NOTIFICATION_SOUND) != null; - } else if (notification.sound != null) { - soundUri = notification.sound; - hasValidSound = (soundUri != null); - } + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + } - if (hasValidSound) { - boolean looping = - (notification.flags & Notification.FLAG_INSISTENT) != 0; - int audioStreamType; - if (notification.audioStreamType >= 0) { - audioStreamType = notification.audioStreamType; - } else { - audioStreamType = DEFAULT_STREAM_TYPE; - } - mSoundNotification = r; - // do not play notifications if stream volume is 0 (typically because - // ringer mode is silent) or if there is a user of exclusive audio focus - if ((mAudioManager.getStreamVolume(audioStreamType) != 0) - && !mAudioManager.isAudioFocusExclusive()) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = - mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DBG) Slog.v(TAG, "Playing sound " + soundUri - + " on stream " + audioStreamType); - player.playAsync(soundUri, user, looping, audioStreamType); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } + buzzBeepBlinkLocked(r); + } + } + }); - // vibrate - // Does the notification want to specify its own vibration? - final boolean hasCustomVibrate = notification.vibrate != null; - - // new in 4.2: if there was supposed to be a sound and we're in vibrate - // mode, and no other vibration is specified, we fall back to vibration - final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (mAudioManager.getRingerMode() - == AudioManager.RINGER_MODE_VIBRATE); - - // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. - final boolean useDefaultVibrate = - (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(mAudioManager.getRingerMode() - == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotification = r; - - if (useDefaultVibrate || convertSoundToVibration) { - // Escalate privileges so we can use the vibrator even if the - // notifying app does not have the VIBRATE permission. - long identity = Binder.clearCallingIdentity(); - try { - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1, notification.audioStreamType); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else if (notification.vibrate.length > 1) { - // If you want your own vibration pattern, you need the VIBRATE - // permission - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(), - notification.vibrate, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1, notification.audioStreamType); - } + idOut[0] = id; + } + + private void buzzBeepBlinkLocked(NotificationRecord record) { + final Notification notification = record.sbn.getNotification(); + + // Should this notification make noise, vibe, or use the LED? + final boolean canInterrupt = (record.score >= SCORE_INTERRUPTION_THRESHOLD) && + !record.isIntercepted(); + if (DBG || record.isIntercepted()) + Slog.v(TAG, + "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt + + " intercept=" + record.isIntercepted() + ); + + final int currentUser; + final long token = Binder.clearCallingIdentity(); + try { + currentUser = ActivityManager.getCurrentUser(); + } finally { + Binder.restoreCallingIdentity(token); + } + + // If we're not supposed to beep, vibrate, etc. then don't. + if (!mDisableNotificationAlerts + && (!(record.isUpdate + && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) + && (record.getUserId() == UserHandle.USER_ALL || + record.getUserId() == currentUser || + mUserProfiles.isCurrentProfile(record.getUserId())) + && canInterrupt + && mSystemReady + && mAudioManager != null) { + if (DBG) Slog.v(TAG, "Interrupting!"); + + sendAccessibilityEvent(notification, record.sbn.getPackageName()); + + // sound + + // should we use the default notification sound? (indicated either by + // DEFAULT_SOUND or because notification.sound is pointing at + // Settings.System.NOTIFICATION_SOUND) + final boolean useDefaultSound = + (notification.defaults & Notification.DEFAULT_SOUND) != 0 || + Settings.System.DEFAULT_NOTIFICATION_URI + .equals(notification.sound); + + Uri soundUri = null; + boolean hasValidSound = false; + + if (useDefaultSound) { + soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + + // check to see if the default notification sound is silent + ContentResolver resolver = getContext().getContentResolver(); + hasValidSound = Settings.System.getString(resolver, + Settings.System.NOTIFICATION_SOUND) != null; + } else if (notification.sound != null) { + soundUri = notification.sound; + hasValidSound = (soundUri != null); + } + + if (hasValidSound) { + boolean looping = + (notification.flags & Notification.FLAG_INSISTENT) != 0; + int audioStreamType; + if (notification.audioStreamType >= 0) { + audioStreamType = notification.audioStreamType; + } else { + audioStreamType = DEFAULT_STREAM_TYPE; + } + mSoundNotification = record; + // do not play notifications if stream volume is 0 (typically because + // ringer mode is silent) or if there is a user of exclusive audio focus + if ((mAudioManager.getStreamVolume(audioStreamType) != 0) + && !mAudioManager.isAudioFocusExclusive()) { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = + mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DBG) Slog.v(TAG, "Playing sound " + soundUri + + " on stream " + audioStreamType); + player.playAsync(soundUri, record.sbn.getUser(), looping, + audioStreamType); } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); } + } + } - // light - // the most recent thing gets the light - mLights.remove(old); - if (mLedNotification == old) { - mLedNotification = null; - } - //Slog.i(TAG, "notification.lights=" - // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) - // != 0)); - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mLights.add(r); - updateLightsLocked(); - } else { - if (old != null - && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { - updateLightsLocked(); - } + // vibrate + // Does the notification want to specify its own vibration? + final boolean hasCustomVibrate = notification.vibrate != null; + + // new in 4.2: if there was supposed to be a sound and we're in vibrate + // mode, and no other vibration is specified, we fall back to vibration + final boolean convertSoundToVibration = + !hasCustomVibrate + && hasValidSound + && (mAudioManager.getRingerMode() + == AudioManager.RINGER_MODE_VIBRATE); + + // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. + final boolean useDefaultVibrate = + (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; + + if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) + && !(mAudioManager.getRingerMode() + == AudioManager.RINGER_MODE_SILENT)) { + mVibrateNotification = record; + + if (useDefaultVibrate || convertSoundToVibration) { + // Escalate privileges so we can use the vibrator even if the + // notifying app does not have the VIBRATE permission. + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), + useDefaultVibrate ? mDefaultVibrationPattern + : mFallbackVibrationPattern, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1, notification.audioStreamType); + } finally { + Binder.restoreCallingIdentity(identity); } + } else if (notification.vibrate.length > 1) { + // If you want your own vibration pattern, you need the VIBRATE + // permission + mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), + notification.vibrate, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1, notification.audioStreamType); } } - }); + } - idOut[0] = id; + // light + // release the light + boolean wasShowLights = mLights.remove(record.getKey()); + if (mLedNotification != null && record.getKey().equals(mLedNotification.getKey())) { + mLedNotification = null; + } + if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) { + mLights.add(record.getKey()); + updateLightsLocked(); + if (mUseAttentionLight) { + mAttentionLight.pulse(); + } + } else if (wasShowLights) { + updateLightsLocked(); + } } void showNextToastLocked() { @@ -1866,6 +1878,9 @@ public class NotificationManagerService extends SystemService { int indexAfter = findNotificationRecordIndexLocked(record); boolean interceptAfter = record.isIntercepted(); changed = indexBefore != indexAfter || interceptBefore != interceptAfter; + if (interceptBefore && !interceptAfter) { + buzzBeepBlinkLocked(record); + } } if (changed) { scheduleSendRankingUpdate(); @@ -2011,7 +2026,7 @@ public class NotificationManagerService extends SystemService { } // light - mLights.remove(r); + mLights.remove(r.getKey()); if (mLedNotification == r) { mLedNotification = null; } @@ -2196,7 +2211,7 @@ public class NotificationManagerService extends SystemService { // get next notification, if any int n = mLights.size(); if (n > 0) { - mLedNotification = mLights.get(n-1); + mLedNotification = mNotificationsByKey.get(mLights.get(n-1)); } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 08f8eb4..30d4fec 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -42,6 +42,7 @@ public final class NotificationRecord { final StatusBarNotification sbn; NotificationUsageStats.SingleNotificationStats stats; boolean isCanceled; + int score; // These members are used by NotificationSignalExtractors // to communicate with the ranking module. @@ -53,9 +54,13 @@ public final class NotificationRecord { // InterceptedNotifications needs to know if this has been previously evaluated. private boolean mTouchedByZen; - NotificationRecord(StatusBarNotification sbn) + // Is this record an update of an old record? + public boolean isUpdate; + + NotificationRecord(StatusBarNotification sbn, int score) { this.sbn = sbn; + this.score = score; } // copy any notes that the ranking system may have made before the update diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java index d5b70e6..a5f865f 100644 --- a/services/core/java/com/android/server/task/TaskManagerService.java +++ b/services/core/java/com/android/server/task/TaskManagerService.java @@ -36,6 +36,7 @@ import android.os.SystemClock; import android.util.Slog; import android.util.SparseArray; +import com.android.server.task.controllers.BatteryController; import com.android.server.task.controllers.ConnectivityController; import com.android.server.task.controllers.IdleController; import com.android.server.task.controllers.StateController; @@ -48,12 +49,19 @@ import java.util.LinkedList; * Responsible for taking tasks representing work to be performed by a client app, and determining * based on the criteria specified when that task should be run against the client application's * endpoint. + * Implements logic for scheduling, and rescheduling tasks. The TaskManagerService knows nothing + * about constraints, or the state of active tasks. It receives callbacks from the various + * controllers and completed tasks and operates accordingly. + * + * Note on locking: Any operations that manipulate {@link #mTasks} need to lock on that object, and + * similarly for {@link #mActiveServices}. If both locks need to be held take mTasksSet first and then + * mActiveService afterwards. * @hide */ public class TaskManagerService extends com.android.server.SystemService - implements StateChangedListener, TaskCompletedListener { + implements StateChangedListener, TaskCompletedListener, TaskMapReadFinishedListener { // TODO: Switch this off for final version. - private static final boolean DEBUG = true; + static final boolean DEBUG = true; /** The number of concurrent tasks we run at one time. */ private static final int MAX_TASK_CONTEXTS_COUNT = 3; static final String TAG = "TaskManager"; @@ -113,8 +121,8 @@ public class TaskManagerService extends com.android.server.SystemService */ public int schedule(Task task, int uId, boolean canPersistTask) { TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask); - return startTrackingTask(taskStatus) ? - TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE; + startTrackingTask(taskStatus); + return TaskManager.RESULT_SUCCESS; } public List<Task> getPendingTasks(int uid) { @@ -210,7 +218,7 @@ public class TaskManagerService extends com.android.server.SystemService */ public TaskManagerService(Context context) { super(context); - mTasks = new TaskStore(context); + mTasks = TaskStore.initAndGet(this); mHandler = new TaskHandler(context.getMainLooper()); mTaskManagerStub = new TaskManagerStub(); // Create the "runners". @@ -218,12 +226,12 @@ public class TaskManagerService extends com.android.server.SystemService mActiveServices.add( new TaskServiceContext(this, context.getMainLooper())); } - + // Create the controllers. mControllers = new LinkedList<StateController>(); mControllers.add(ConnectivityController.get(this)); mControllers.add(TimeController.get(this)); mControllers.add(IdleController.get(this)); - // TODO: Add BatteryStateController when implemented. + mControllers.add(BatteryController.get(this)); } @Override @@ -236,17 +244,14 @@ public class TaskManagerService extends com.android.server.SystemService * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know * about. */ - private boolean startTrackingTask(TaskStatus taskStatus) { - boolean added = false; + private void startTrackingTask(TaskStatus taskStatus) { synchronized (mTasks) { - added = mTasks.add(taskStatus); + mTasks.add(taskStatus); } - if (added) { - for (StateController controller : mControllers) { - controller.maybeStartTrackingTask(taskStatus); - } + for (StateController controller : mControllers) { + controller.maybeStartTrackingTask(taskStatus); + } - return added; } /** @@ -404,6 +409,27 @@ public class TaskManagerService extends com.android.server.SystemService mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus); } + /** + * Disk I/O is finished, take the list of tasks we read from disk and add them to our + * {@link TaskStore}. + * This is run on the {@link com.android.server.IoThread} instance, which is a separate thread, + * and is called once at boot. + */ + @Override + public void onTaskMapReadFinished(List<TaskStatus> tasks) { + synchronized (mTasks) { + for (TaskStatus ts : tasks) { + if (mTasks.contains(ts)) { + // An app with BOOT_COMPLETED *might* have decided to reschedule their task, in + // the same amount of time it took us to read it from disk. If this is the case + // we leave it be. + continue; + } + startTrackingTask(ts); + } + } + } + private class TaskHandler extends Handler { public TaskHandler(Looper looper) { diff --git a/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java b/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java new file mode 100644 index 0000000..c68d8db --- /dev/null +++ b/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java @@ -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.server.task; + +import java.util.List; + +import com.android.server.task.controllers.TaskStatus; + +/** + * Callback definition for I/O thread to let the TaskManagerService know when + * I/O read has completed. Done this way so we don't stall the main thread on + * boot. + */ +public interface TaskMapReadFinishedListener { + + /** + * Called by the {@link TaskStore} at boot, when the disk read is finished. + */ + public void onTaskMapReadFinished(List<TaskStatus> tasks); +} diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java index f72ab22..6bb00b1 100644 --- a/services/core/java/com/android/server/task/TaskStore.java +++ b/services/core/java/com/android/server/task/TaskStore.java @@ -16,17 +16,37 @@ package com.android.server.task; +import android.content.ComponentName; import android.app.task.Task; import android.content.Context; +import android.os.Environment; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.SystemClock; +import android.util.AtomicFile; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; -import android.util.SparseArray; +import android.util.Xml; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.server.IoThread; import com.android.server.task.controllers.TaskStatus; -import java.util.HashSet; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; -import java.util.Set; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; /** * Maintain a list of classes, and accessor methods/logic for these tasks. @@ -38,57 +58,108 @@ import java.util.Set; * - Handles rescheduling of tasks. * - When a periodic task is executed and must be re-added. * - When a task fails and the client requests that it be retried with backoff. - * - This class is <strong>not</strong> thread-safe. + * - This class <strong>is not</strong> thread-safe. + * + * Note on locking: + * All callers to this class must <strong>lock on the class object they are calling</strong>. + * This is important b/c {@link com.android.server.task.TaskStore.WriteTasksMapToDiskRunnable} + * and {@link com.android.server.task.TaskStore.ReadTaskMapFromDiskRunnable} lock on that + * object. */ public class TaskStore { private static final String TAG = "TaskManagerStore"; + private static final boolean DEBUG = TaskManagerService.DEBUG; + /** Threshold to adjust how often we want to write to the db. */ private static final int MAX_OPS_BEFORE_WRITE = 1; - final ArraySet<TaskStatus> mTasks; + final ArraySet<TaskStatus> mTasksSet; final Context mContext; private int mDirtyOperations; - TaskStore(Context context) { - mTasks = intialiseTasksFromDisk(); + private static final Object sSingletonLock = new Object(); + private final AtomicFile mTasksFile; + /** Handler backed by IoThread for writing to disk. */ + private final Handler mIoHandler = IoThread.getHandler(); + private static TaskStore sSingleton; + + /** Used by the {@Link TaskManagerService} to instantiate the TaskStore. */ + static TaskStore initAndGet(TaskManagerService taskManagerService) { + synchronized (sSingletonLock) { + if (sSingleton == null) { + sSingleton = new TaskStore(taskManagerService.getContext(), + Environment.getDataDirectory(), taskManagerService); + } + return sSingleton; + } + } + + @VisibleForTesting + public static TaskStore initAndGetForTesting(Context context, File dataDir, + TaskMapReadFinishedListener callback) { + return new TaskStore(context, dataDir, callback); + } + + private TaskStore(Context context, File dataDir, TaskMapReadFinishedListener callback) { mContext = context; mDirtyOperations = 0; + + File systemDir = new File(dataDir, "system"); + File taskDir = new File(systemDir, "task"); + taskDir.mkdirs(); + mTasksFile = new AtomicFile(new File(taskDir, "tasks.xml")); + + mTasksSet = new ArraySet<TaskStatus>(); + + readTaskMapFromDiskAsync(callback); } /** * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists, * it will be replaced. * @param taskStatus Task to add. - * @return true if the operation succeeded. + * @return Whether or not an equivalent TaskStatus was replaced by this operation. */ public boolean add(TaskStatus taskStatus) { + boolean replaced = mTasksSet.remove(taskStatus); + mTasksSet.add(taskStatus); if (taskStatus.isPersisted()) { - if (!maybeWriteStatusToDisk()) { - return false; - } + maybeWriteStatusToDiskAsync(); } - mTasks.remove(taskStatus); - mTasks.add(taskStatus); - return true; + return replaced; + } + + /** + * Whether this taskStatus object already exists in the TaskStore. + */ + public boolean contains(TaskStatus taskStatus) { + return mTasksSet.contains(taskStatus); } public int size() { - return mTasks.size(); + return mTasksSet.size(); } /** * Remove the provided task. Will also delete the task if it was persisted. - * @return The TaskStatus that was removed, or null if an invalid token was provided. + * @return Whether or not the task existed to be removed. */ public boolean remove(TaskStatus taskStatus) { - boolean removed = mTasks.remove(taskStatus); + boolean removed = mTasksSet.remove(taskStatus); if (!removed) { - Slog.e(TAG, "Error removing task: " + taskStatus); + if (DEBUG) { + Slog.d(TAG, "Couldn't remove task: didn't exist: " + taskStatus); + } return false; - } else { - maybeWriteStatusToDisk(); } - return true; + maybeWriteStatusToDiskAsync(); + return removed; + } + + @VisibleForTesting + public void clear() { + mTasksSet.clear(); + maybeWriteStatusToDiskAsync(); } /** @@ -100,19 +171,16 @@ public class TaskStore { * was found. */ public boolean removeAllByUid(int uid) { - Iterator<TaskStatus> it = mTasks.iterator(); - boolean removed = false; + Iterator<TaskStatus> it = mTasksSet.iterator(); while (it.hasNext()) { TaskStatus ts = it.next(); if (ts.getUid() == uid) { it.remove(); - removed = true; + maybeWriteStatusToDiskAsync(); + return true; } } - if (removed) { - maybeWriteStatusToDisk(); - } - return removed; + return false; } /** @@ -124,48 +192,464 @@ public class TaskStore { * @return true if a removal occurred, false if the provided parameters didn't match anything. */ public boolean remove(int uid, int taskId) { - Iterator<TaskStatus> it = mTasks.iterator(); + boolean changed = false; + Iterator<TaskStatus> it = mTasksSet.iterator(); while (it.hasNext()) { TaskStatus ts = it.next(); if (ts.getUid() == uid && ts.getTaskId() == taskId) { it.remove(); - maybeWriteStatusToDisk(); - return true; + changed = true; } } - return false; + if (changed) { + maybeWriteStatusToDiskAsync(); + } + return changed; } /** * @return The live array of TaskStatus objects. */ - public Set<TaskStatus> getTasks() { - return mTasks; + public ArraySet<TaskStatus> getTasks() { + return mTasksSet; } + /** Version of the db schema. */ + private static final int TASKS_FILE_VERSION = 0; + /** Tag corresponds to constraints this task needs. */ + private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints"; + /** Tag corresponds to execution parameters. */ + private static final String XML_TAG_PERIODIC = "periodic"; + private static final String XML_TAG_ONEOFF = "one-off"; + private static final String XML_TAG_EXTRAS = "extras"; + /** * Every time the state changes we write all the tasks in one swathe, instead of trying to * track incremental changes. * @return Whether the operation was successful. This will only fail for e.g. if the system is * low on storage. If this happens, we continue as normal */ - private boolean maybeWriteStatusToDisk() { + private void maybeWriteStatusToDiskAsync() { mDirtyOperations++; - if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) { - for (TaskStatus ts : mTasks) { - // + if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) { + if (DEBUG) { + Slog.v(TAG, "Writing tasks to disk."); + } + mIoHandler.post(new WriteTasksMapToDiskRunnable()); + } + } + + private void readTaskMapFromDiskAsync(TaskMapReadFinishedListener callback) { + mIoHandler.post(new ReadTaskMapFromDiskRunnable(callback)); + } + + public void readTaskMapFromDisk(TaskMapReadFinishedListener callback) { + new ReadTaskMapFromDiskRunnable(callback).run(); + } + + /** + * Runnable that writes {@link #mTasksSet} out to xml. + * NOTE: This Runnable locks on TaskStore.this + */ + private class WriteTasksMapToDiskRunnable implements Runnable { + @Override + public void run() { + final long startElapsed = SystemClock.elapsedRealtime(); + synchronized (TaskStore.this) { + writeTasksMapImpl(); + } + if (TaskManagerService.DEBUG) { + Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime() + - startElapsed) + "ms"); + } + } + + private void writeTasksMapImpl() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(baos, "utf-8"); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + out.startTag(null, "task-info"); + out.attribute(null, "version", Integer.toString(TASKS_FILE_VERSION)); + for (int i = 0; i < mTasksSet.size(); i++) { + final TaskStatus taskStatus = mTasksSet.valueAt(i); + if (DEBUG) { + Slog.d(TAG, "Saving task " + taskStatus.getTaskId()); + } + out.startTag(null, "task"); + addIdentifierAttributesToTaskTag(out, taskStatus); + writeConstraintsToXml(out, taskStatus); + writeExecutionCriteriaToXml(out, taskStatus); + writeBundleToXml(taskStatus.getExtras(), out); + out.endTag(null, "task"); + } + out.endTag(null, "task-info"); + out.endDocument(); + + // Write out to disk in one fell sweep. + FileOutputStream fos = mTasksFile.startWrite(); + fos.write(baos.toByteArray()); + mTasksFile.finishWrite(fos); + mDirtyOperations = 0; + } catch (IOException e) { + if (DEBUG) { + Slog.v(TAG, "Error writing out task data.", e); + } + } catch (XmlPullParserException e) { + if (DEBUG) { + Slog.d(TAG, "Error persisting bundle.", e); + } + } + } + + /** Write out a tag with data comprising the required fields of this task and its client. */ + private void addIdentifierAttributesToTaskTag(XmlSerializer out, TaskStatus taskStatus) + throws IOException { + out.attribute(null, "taskid", Integer.toString(taskStatus.getTaskId())); + out.attribute(null, "package", taskStatus.getServiceComponent().getPackageName()); + out.attribute(null, "class", taskStatus.getServiceComponent().getClassName()); + out.attribute(null, "uid", Integer.toString(taskStatus.getUid())); + } + + private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) + throws IOException, XmlPullParserException { + out.startTag(null, XML_TAG_EXTRAS); + extras.saveToXml(out); + out.endTag(null, XML_TAG_EXTRAS); + } + /** + * Write out a tag with data identifying this tasks constraints. If the constraint isn't here + * it doesn't apply. + */ + private void writeConstraintsToXml(XmlSerializer out, TaskStatus taskStatus) throws IOException { + out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS); + if (taskStatus.hasMeteredConstraint()) { + out.attribute(null, "unmetered", Boolean.toString(true)); + } + if (taskStatus.hasConnectivityConstraint()) { + out.attribute(null, "connectivity", Boolean.toString(true)); + } + if (taskStatus.hasIdleConstraint()) { + out.attribute(null, "idle", Boolean.toString(true)); + } + if (taskStatus.hasChargingConstraint()) { + out.attribute(null, "charging", Boolean.toString(true)); + } + out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS); + } + + private void writeExecutionCriteriaToXml(XmlSerializer out, TaskStatus taskStatus) + throws IOException { + final Task task = taskStatus.getTask(); + if (taskStatus.getTask().isPeriodic()) { + out.startTag(null, XML_TAG_PERIODIC); + out.attribute(null, "period", Long.toString(task.getIntervalMillis())); + } else { + out.startTag(null, XML_TAG_ONEOFF); + } + + if (taskStatus.hasDeadlineConstraint()) { + // Wall clock deadline. + final long deadlineWallclock = System.currentTimeMillis() + + (taskStatus.getLatestRunTimeElapsed() - SystemClock.elapsedRealtime()); + out.attribute(null, "deadline", Long.toString(deadlineWallclock)); + } + if (taskStatus.hasTimingDelayConstraint()) { + final long delayWallclock = System.currentTimeMillis() + + (taskStatus.getEarliestRunTime() - SystemClock.elapsedRealtime()); + out.attribute(null, "delay", Long.toString(delayWallclock)); + } + + // Only write out back-off policy if it differs from the default. + // This also helps the case where the task is idle -> these aren't allowed to specify + // back-off. + if (taskStatus.getTask().getInitialBackoffMillis() != Task.DEFAULT_INITIAL_BACKOFF_MILLIS + || taskStatus.getTask().getBackoffPolicy() != Task.DEFAULT_BACKOFF_POLICY) { + out.attribute(null, "backoff-policy", Integer.toString(task.getBackoffPolicy())); + out.attribute(null, "initial-backoff", Long.toString(task.getInitialBackoffMillis())); + } + if (task.isPeriodic()) { + out.endTag(null, XML_TAG_PERIODIC); + } else { + out.endTag(null, XML_TAG_ONEOFF); } - mDirtyOperations = 0; } - return true; } /** - * - * @return + * Runnable that reads list of persisted task from xml. + * NOTE: This Runnable locks on TaskStore.this */ - // TODO: Implement this. - private ArraySet<TaskStatus> intialiseTasksFromDisk() { - return new ArraySet<TaskStatus>(); + private class ReadTaskMapFromDiskRunnable implements Runnable { + private TaskMapReadFinishedListener mCallback; + public ReadTaskMapFromDiskRunnable(TaskMapReadFinishedListener callback) { + mCallback = callback; + } + + @Override + public void run() { + try { + List<TaskStatus> tasks; + synchronized (TaskStore.this) { + tasks = readTaskMapImpl(); + } + if (tasks != null) { + mCallback.onTaskMapReadFinished(tasks); + } + } catch (FileNotFoundException e) { + if (TaskManagerService.DEBUG) { + Slog.d(TAG, "Could not find tasks file, probably there was nothing to load."); + } + } catch (XmlPullParserException e) { + if (TaskManagerService.DEBUG) { + Slog.d(TAG, "Error parsing xml.", e); + } + } catch (IOException e) { + if (TaskManagerService.DEBUG) { + Slog.d(TAG, "Error parsing xml.", e); + } + } + } + + private List<TaskStatus> readTaskMapImpl() throws XmlPullParserException, IOException { + FileInputStream fis = mTasksFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + Slog.d(TAG, parser.getName()); + } + if (eventType == XmlPullParser.END_DOCUMENT) { + if (DEBUG) { + Slog.d(TAG, "No persisted tasks."); + } + return null; + } + + String tagName = parser.getName(); + if ("task-info".equals(tagName)) { + final List<TaskStatus> tasks = new ArrayList<TaskStatus>(); + // Read in version info. + try { + int version = Integer.valueOf(parser.getAttributeValue(null, "version")); + if (version != TASKS_FILE_VERSION) { + Slog.d(TAG, "Invalid version number, aborting tasks file read."); + return null; + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Invalid version number, aborting tasks file read."); + return null; + } + eventType = parser.next(); + do { + // Read each <task/> + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + // Start reading task. + if ("task".equals(tagName)) { + TaskStatus persistedTask = restoreTaskFromXml(parser); + if (persistedTask != null) { + if (DEBUG) { + Slog.d(TAG, "Read out " + persistedTask); + } + tasks.add(persistedTask); + } else { + Slog.d(TAG, "Error reading task from file."); + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + return tasks; + } + return null; + } + + /** + * @param parser Xml parser at the beginning of a "<task/>" tag. The next "parser.next()" call + * will take the parser into the body of the task tag. + * @return Newly instantiated task holding all the information we just read out of the xml tag. + */ + private TaskStatus restoreTaskFromXml(XmlPullParser parser) throws XmlPullParserException, + IOException { + Task.Builder taskBuilder; + int uid; + + // Read out task identifier attributes. + try { + taskBuilder = buildBuilderFromXml(parser); + uid = Integer.valueOf(parser.getAttributeValue(null, "uid")); + } catch (NumberFormatException e) { + Slog.e(TAG, "Error parsing task's required fields, skipping"); + return null; + } + + int eventType; + // Read out constraints tag. + do { + eventType = parser.next(); + } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG. + + if (!(eventType == XmlPullParser.START_TAG && + XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) { + // Expecting a <constraints> start tag. + return null; + } + try { + buildConstraintsFromXml(taskBuilder, parser); + } catch (NumberFormatException e) { + Slog.d(TAG, "Error reading constraints, skipping."); + return null; + } + parser.next(); // Consume </constraints> + + // Read out execution parameters tag. + do { + eventType = parser.next(); + } while (eventType == XmlPullParser.TEXT); + if (eventType != XmlPullParser.START_TAG) { + return null; + } + + Pair<Long, Long> runtimes; + try { + runtimes = buildExecutionTimesFromXml(parser); + } catch (NumberFormatException e) { + if (DEBUG) { + Slog.d(TAG, "Error parsing execution time parameters, skipping."); + } + return null; + } + + if (XML_TAG_PERIODIC.equals(parser.getName())) { + try { + String val = parser.getAttributeValue(null, "period"); + taskBuilder.setPeriodic(Long.valueOf(val)); + } catch (NumberFormatException e) { + Slog.d(TAG, "Error reading periodic execution criteria, skipping."); + return null; + } + } else if (XML_TAG_ONEOFF.equals(parser.getName())) { + try { + if (runtimes.first != TaskStatus.DEFAULT_EARLIEST_RUNTIME) { + taskBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime()); + } + if (runtimes.second != TaskStatus.DEFAULT_LATEST_RUNTIME) { + taskBuilder.setOverrideDeadline( + runtimes.second - SystemClock.elapsedRealtime()); + } + } catch (NumberFormatException e) { + Slog.d(TAG, "Error reading task execution criteria, skipping."); + return null; + } + } else { + if (DEBUG) { + Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName()); + } + // Expecting a parameters start tag. + return null; + } + maybeBuildBackoffPolicyFromXml(taskBuilder, parser); + + parser.nextTag(); // Consume parameters end tag. + + // Read out extras Bundle. + do { + eventType = parser.next(); + } while (eventType == XmlPullParser.TEXT); + if (!(eventType == XmlPullParser.START_TAG && XML_TAG_EXTRAS.equals(parser.getName()))) { + if (DEBUG) { + Slog.d(TAG, "Error reading extras, skipping."); + } + return null; + } + + PersistableBundle extras = PersistableBundle.restoreFromXml(parser); + taskBuilder.setExtras(extras); + parser.nextTag(); // Consume </extras> + + return new TaskStatus(taskBuilder.build(), uid, runtimes.first, runtimes.second); + } + + private Task.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException { + // Pull out required fields from <task> attributes. + int taskId = Integer.valueOf(parser.getAttributeValue(null, "taskid")); + String packageName = parser.getAttributeValue(null, "package"); + String className = parser.getAttributeValue(null, "class"); + ComponentName cname = new ComponentName(packageName, className); + + return new Task.Builder(taskId, cname); + } + + private void buildConstraintsFromXml(Task.Builder taskBuilder, XmlPullParser parser) { + String val = parser.getAttributeValue(null, "unmetered"); + if (val != null) { + taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED); + } + val = parser.getAttributeValue(null, "connectivity"); + if (val != null) { + taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.ANY); + } + val = parser.getAttributeValue(null, "idle"); + if (val != null) { + taskBuilder.setRequiresDeviceIdle(true); + } + val = parser.getAttributeValue(null, "charging"); + if (val != null) { + taskBuilder.setRequiresCharging(true); + } + } + + /** + * Builds the back-off policy out of the params tag. These attributes may not exist, depending + * on whether the back-off was set when the task was first scheduled. + */ + private void maybeBuildBackoffPolicyFromXml(Task.Builder taskBuilder, XmlPullParser parser) { + String val = parser.getAttributeValue(null, "initial-backoff"); + if (val != null) { + long initialBackoff = Long.valueOf(val); + val = parser.getAttributeValue(null, "backoff-policy"); + int backoffPolicy = Integer.valueOf(val); // Will throw NFE which we catch higher up. + taskBuilder.setBackoffCriteria(initialBackoff, backoffPolicy); + } + } + + /** + * Convenience function to read out and convert deadline and delay from xml into elapsed real + * time. + * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime + * and the second is the latest elapsed runtime. + */ + private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser) + throws NumberFormatException { + // Pull out execution time data. + final long nowWallclock = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + + long earliestRunTimeElapsed = TaskStatus.DEFAULT_EARLIEST_RUNTIME; + long latestRunTimeElapsed = TaskStatus.DEFAULT_LATEST_RUNTIME; + String val = parser.getAttributeValue(null, "deadline"); + if (val != null) { + long latestRuntimeWallclock = Long.valueOf(val); + long maxDelayElapsed = + Math.max(latestRuntimeWallclock - nowWallclock, 0); + latestRunTimeElapsed = nowElapsed + maxDelayElapsed; + } + val = parser.getAttributeValue(null, "delay"); + if (val != null) { + long earliestRuntimeWallclock = Long.valueOf(val); + long minDelayElapsed = + Math.max(earliestRuntimeWallclock - nowWallclock, 0); + earliestRunTimeElapsed = nowElapsed + minDelayElapsed; + + } + return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed); + } } } diff --git a/services/core/java/com/android/server/task/controllers/BatteryController.java b/services/core/java/com/android/server/task/controllers/BatteryController.java new file mode 100644 index 0000000..585b41f --- /dev/null +++ b/services/core/java/com/android/server/task/controllers/BatteryController.java @@ -0,0 +1,213 @@ +/* + * 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.task.controllers; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryProperty; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.BatteryService; +import com.android.server.task.StateChangedListener; +import com.android.server.task.TaskManagerService; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple controller that tracks whether the phone is charging or not. The phone is considered to + * be charging when it's been plugged in for more than two minutes, and the system has broadcast + * ACTION_BATTERY_OK. + */ +public class BatteryController extends StateController { + private static final String TAG = "BatteryController"; + + private static final Object sCreationLock = new Object(); + private static volatile BatteryController sController; + private static final String ACTION_CHARGING_STABLE = + "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE"; + /** Wait this long after phone is plugged in before doing any work. */ + private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes. + + private List<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>(); + private ChargingTracker mChargeTracker; + + public static BatteryController get(TaskManagerService taskManagerService) { + synchronized (sCreationLock) { + if (sController == null) { + sController = new BatteryController(taskManagerService, + taskManagerService.getContext()); + } + } + return sController; + } + + @VisibleForTesting + public ChargingTracker getTracker() { + return mChargeTracker; + } + + @VisibleForTesting + public static BatteryController getForTesting(StateChangedListener stateChangedListener, + Context context) { + return new BatteryController(stateChangedListener, context); + } + + private BatteryController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); + mChargeTracker = new ChargingTracker(); + mChargeTracker.startTracking(); + } + + @Override + public void maybeStartTrackingTask(TaskStatus taskStatus) { + if (taskStatus.hasChargingConstraint()) { + synchronized (mTrackedTasks) { + mTrackedTasks.add(taskStatus); + taskStatus.chargingConstraintSatisfied.set(mChargeTracker.isOnStablePower()); + } + } + + } + + @Override + public void maybeStopTrackingTask(TaskStatus taskStatus) { + if (taskStatus.hasChargingConstraint()) { + synchronized (mTrackedTasks) { + mTrackedTasks.remove(taskStatus); + } + } + } + + private void maybeReportNewChargingState() { + final boolean stablePower = mChargeTracker.isOnStablePower(); + boolean reportChange = false; + synchronized (mTrackedTasks) { + for (TaskStatus ts : mTrackedTasks) { + boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower); + if (previous != stablePower) { + reportChange = true; + } + } + } + if (reportChange) { + mStateChangedListener.onControllerStateChanged(); + } + } + + public class ChargingTracker extends BroadcastReceiver { + private final AlarmManager mAlarm; + private final PendingIntent mStableChargingTriggerIntent; + /** + * Track whether we're "charging", where charging means that we're ready to commit to + * doing work. + */ + private boolean mCharging; + /** Keep track of whether the battery is charged enough that we want to do work. */ + private boolean mBatteryHealthy; + + public ChargingTracker() { + mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(ACTION_CHARGING_STABLE) + .setComponent(new ComponentName(mContext, this.getClass())); + mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + public void startTracking() { + IntentFilter filter = new IntentFilter(); + + // Battery health. + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_OKAY); + // Charging/not charging. + filter.addAction(Intent.ACTION_POWER_CONNECTED); + filter.addAction(Intent.ACTION_POWER_DISCONNECTED); + mContext.registerReceiver(this, filter); + + // Initialise tracker state. + BatteryService batteryService = (BatteryService) ServiceManager.getService("battery"); + if (batteryService != null) { + mBatteryHealthy = !batteryService.isBatteryLow(); + mCharging = batteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + } else { + // Unavailable for some reason, we default to false and let ACTION_BATTERY_[OK,LOW] + // sort it out. + } + } + + boolean isOnStablePower() { + return mCharging && mBatteryHealthy; + } + + @Override + public void onReceive(Context context, Intent intent) { + onReceiveInternal(intent); + } + + @VisibleForTesting + public void onReceiveInternal(Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_BATTERY_LOW.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Battery life too low to do work. @ " + + SystemClock.elapsedRealtime()); + } + // If we get this action, the battery is discharging => it isn't plugged in so + // there's no work to cancel. We track this variable for the case where it is + // charging, but hasn't been for long enough to be healthy. + mBatteryHealthy = false; + } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { + if (DEBUG) { + Slog.d(TAG, "Battery life healthy enough to do work. @ " + + SystemClock.elapsedRealtime()); + } + mBatteryHealthy = true; + maybeReportNewChargingState(); + } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) { + // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks + // here if the user unplugs the phone immediately. + mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS, + mStableChargingTriggerIntent); + mCharging = true; + } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { + // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted. + mAlarm.cancel(mStableChargingTriggerIntent); + mCharging = false; + maybeReportNewChargingState(); + }else if (ACTION_CHARGING_STABLE.equals(action)) { + // Here's where we actually do the notify for a task being ready. + if (DEBUG) { + Slog.d(TAG, "Battery connected fired @ " + SystemClock.elapsedRealtime()); + } + if (mCharging) { // Should never receive this intent if mCharging is false. + maybeReportNewChargingState(); + } + } + } + } +} 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 474af8f..4819460 100644 --- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import com.android.server.task.StateChangedListener; import com.android.server.task.TaskManagerService; import java.util.LinkedList; @@ -39,7 +40,6 @@ import java.util.List; */ public class ConnectivityController extends StateController { private static final String TAG = "TaskManager.Connectivity"; - private static final boolean DEBUG = true; private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>(); private final BroadcastReceiver mConnectivityChangedReceiver = @@ -54,13 +54,13 @@ public class ConnectivityController extends StateController { public static synchronized ConnectivityController get(TaskManagerService taskManager) { if (mSingleton == null) { - mSingleton = new ConnectivityController(taskManager); + mSingleton = new ConnectivityController(taskManager, taskManager.getContext()); } return mSingleton; } - private ConnectivityController(TaskManagerService service) { - super(service); + private ConnectivityController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); // Register connectivity changed BR. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java index 9489644..c47faca 100644 --- a/services/core/java/com/android/server/task/controllers/IdleController.java +++ b/services/core/java/com/android/server/task/controllers/IdleController.java @@ -28,11 +28,11 @@ import android.content.IntentFilter; import android.os.SystemClock; import android.util.Slog; +import com.android.server.task.StateChangedListener; import com.android.server.task.TaskManagerService; public class IdleController extends StateController { private static final String TAG = "IdleController"; - private static final boolean DEBUG = false; // Policy: we decide that we're "idle" if the device has been unused / // screen off or dreaming for at least this long @@ -52,14 +52,14 @@ public class IdleController extends StateController { public static IdleController get(TaskManagerService service) { synchronized (sCreationLock) { if (sController == null) { - sController = new IdleController(service); + sController = new IdleController(service, service.getContext()); } return sController; } } - private IdleController(TaskManagerService service) { - super(service); + private IdleController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); initIdleStateTracking(); } diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java index ed31eac..cbe6ff8 100644 --- a/services/core/java/com/android/server/task/controllers/StateController.java +++ b/services/core/java/com/android/server/task/controllers/StateController.java @@ -27,13 +27,13 @@ import com.android.server.task.TaskManagerService; * are ready to run, or whether they must be stopped. */ public abstract class StateController { - + protected static final boolean DEBUG = true; protected Context mContext; protected StateChangedListener mStateChangedListener; - public StateController(TaskManagerService service) { - mStateChangedListener = service; - mContext = service.getContext(); + public StateController(StateChangedListener stateChangedListener, Context context) { + mStateChangedListener = stateChangedListener; + mContext = context; } /** 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 b7f84ec..33670a1 100644 --- a/services/core/java/com/android/server/task/controllers/TaskStatus.java +++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java @@ -18,7 +18,7 @@ package com.android.server.task.controllers; import android.app.task.Task; import android.content.ComponentName; -import android.os.Bundle; +import android.os.PersistableBundle; import android.os.SystemClock; import android.os.UserHandle; @@ -37,6 +37,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * @hide */ public class TaskStatus { + public static final long DEFAULT_LATEST_RUNTIME = Long.MAX_VALUE; + public static final long DEFAULT_EARLIEST_RUNTIME = 0L; + final Task task; final int uId; @@ -61,7 +64,7 @@ public class TaskStatus { * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}. */ private long latestRunTimeElapsedMillis; - + /** How many times this task has failed, used to compute back-off. */ private final int numFailures; /** Provide a handle to the service that this task will be run on. */ @@ -69,36 +72,52 @@ public class TaskStatus { return uId; } - /** Create a newly scheduled task. */ - public TaskStatus(Task task, int uId, boolean persisted) { + private TaskStatus(Task task, int uId, boolean persisted, int numFailures) { this.task = task; this.uId = uId; - this.numFailures = 0; + this.numFailures = numFailures; this.persisted = persisted; + } + + /** Create a newly scheduled task. */ + public TaskStatus(Task task, int uId, boolean persisted) { + this(task, uId, persisted, 0); final long elapsedNow = SystemClock.elapsedRealtime(); - // Timing constraints + if (task.isPeriodic()) { earliestRunTimeElapsedMillis = elapsedNow; latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis(); } else { earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ? - elapsedNow + task.getMinLatencyMillis() : 0L; + elapsedNow + task.getMinLatencyMillis() : DEFAULT_EARLIEST_RUNTIME; latestRunTimeElapsedMillis = task.hasLateConstraint() ? - elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE; + elapsedNow + task.getMaxExecutionDelayMillis() : DEFAULT_LATEST_RUNTIME; } } - public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed, - long newLatestRuntimeElapsed, int backoffAttempt) { - this.task = rescheduling.task; + /** + * Create a new TaskStatus that was loaded from disk. We ignore the provided + * {@link android.app.task.Task} time criteria because we can load a persisted periodic task + * from the {@link com.android.server.task.TaskStore} and still want to respect its + * wallclock runtime rather than resetting it on every boot. + * We consider a freshly loaded task to no longer be in back-off. + */ + public TaskStatus(Task task, int uId, long earliestRunTimeElapsedMillis, + long latestRunTimeElapsedMillis) { + this(task, uId, true, 0); + + this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; + this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; + } - this.uId = rescheduling.getUid(); - this.persisted = rescheduling.isPersisted(); - this.numFailures = backoffAttempt; + /** Create a new task to be rescheduled with the provided parameters. */ + public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsedMillis, + long newLatestRuntimeElapsedMillis, int backoffAttempt) { + this(rescheduling.task, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt); - earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed; - latestRunTimeElapsedMillis = newLatestRuntimeElapsed; + earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis; + latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis; } public Task getTask() { @@ -125,7 +144,7 @@ public class TaskStatus { return uId; } - public Bundle getExtras() { + public PersistableBundle getExtras() { return task.getExtras(); } @@ -142,11 +161,11 @@ public class TaskStatus { } public boolean hasTimingDelayConstraint() { - return earliestRunTimeElapsedMillis != 0L; + return earliestRunTimeElapsedMillis != DEFAULT_EARLIEST_RUNTIME; } public boolean hasDeadlineConstraint() { - return latestRunTimeElapsedMillis != Long.MAX_VALUE; + return latestRunTimeElapsedMillis != DEFAULT_LATEST_RUNTIME; } public boolean hasIdleConstraint() { diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java index 72f312c..8c6dd27 100644 --- a/services/core/java/com/android/server/task/controllers/TimeController.java +++ b/services/core/java/com/android/server/task/controllers/TimeController.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.SystemClock; +import com.android.server.task.StateChangedListener; import com.android.server.task.TaskManagerService; import java.util.Iterator; @@ -58,13 +59,13 @@ public class TimeController extends StateController { public static synchronized TimeController get(TaskManagerService taskManager) { if (mSingleton == null) { - mSingleton = new TimeController(taskManager); + mSingleton = new TimeController(taskManager, taskManager.getContext()); } return mSingleton; } - private TimeController(TaskManagerService service) { - super(service); + private TimeController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); mTaskExpiredAlarmIntent = PendingIntent.getBroadcast(mContext, 0 /* ignored */, new Intent(ACTION_TASK_EXPIRED), 0); diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java new file mode 100644 index 0000000..e7f9ca0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java @@ -0,0 +1,199 @@ +package com.android.server.task; + + +import android.content.ComponentName; +import android.content.Context; +import android.app.task.Task; +import android.app.task.Task.Builder; +import android.os.PersistableBundle; +import android.test.AndroidTestCase; +import android.test.RenamingDelegatingContext; +import android.util.Log; + +import com.android.server.task.controllers.TaskStatus; + +import java.util.List; + +import static com.android.server.task.TaskStore.initAndGet; +/** + * Test reading and writing correctly from file. + */ +public class TaskStoreTest extends AndroidTestCase { + private static final String TAG = "TaskStoreTest"; + private static final String TEST_PREFIX = "_test_"; + // private static final int USER_NON_0 = 3; + private static final int SOME_UID = 34234; + private ComponentName mComponent; + private static final long IO_WAIT = 600L; + + TaskStore mTaskStoreUnderTest; + Context mTestContext; + TaskMapReadFinishedListener mTaskMapReadFinishedListenerStub = + new TaskMapReadFinishedListener() { + @Override + public void onTaskMapReadFinished(List<TaskStatus> tasks) { + // do nothing. + } + }; + + @Override + public void setUp() throws Exception { + mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX); + Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'"); + mTaskStoreUnderTest = TaskStore.initAndGetForTesting(mTestContext, + mTestContext.getFilesDir(), mTaskMapReadFinishedListenerStub); + mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName()); + } + + @Override + public void tearDown() throws Exception { + mTaskStoreUnderTest.clear(); + } + + public void testMaybeWriteStatusToDisk() throws Exception { + int taskId = 5; + long runByMillis = 20000L; // 20s + long runFromMillis = 2000L; // 2s + long initialBackoff = 10000L; // 10s + + final Task task = new Builder(taskId, mComponent) + .setRequiresCharging(true) + .setRequiredNetworkCapabilities(Task.NetworkType.ANY) + .setBackoffCriteria(initialBackoff, Task.BackoffPolicy.EXPONENTIAL) + .setOverrideDeadline(runByMillis) + .setMinimumLatency(runFromMillis) + .build(); + final TaskStatus ts = new TaskStatus(task, SOME_UID, true /* persisted */); + mTaskStoreUnderTest.add(ts); + Thread.sleep(IO_WAIT); + // Manually load tasks from xml file. + mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() { + @Override + public void onTaskMapReadFinished(List<TaskStatus> tasks) { + assertEquals("Didn't get expected number of persisted tasks.", 1, tasks.size()); + TaskStatus loadedTaskStatus = tasks.get(0); + assertTasksEqual(task, loadedTaskStatus.getTask()); + assertEquals("Different uids.", SOME_UID, tasks.get(0).getUid()); + compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", + ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime()); + compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", + ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed()); + } + }); + + } + + public void testWritingTwoFilesToDisk() throws Exception { + final Task task1 = new Builder(8, mComponent) + .setRequiresDeviceIdle(true) + .setPeriodic(10000L) + .setRequiresCharging(true) + .build(); + final Task task2 = new Builder(12, mComponent) + .setMinimumLatency(5000L) + .setBackoffCriteria(15000L, Task.BackoffPolicy.LINEAR) + .setOverrideDeadline(30000L) + .setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED) + .build(); + final TaskStatus taskStatus1 = new TaskStatus(task1, SOME_UID, true /* persisted */); + final TaskStatus taskStatus2 = new TaskStatus(task2, SOME_UID, true /* persisted */); + mTaskStoreUnderTest.add(taskStatus1); + mTaskStoreUnderTest.add(taskStatus2); + Thread.sleep(IO_WAIT); + mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() { + @Override + public void onTaskMapReadFinished(List<TaskStatus> tasks) { + assertEquals("Incorrect # of persisted tasks.", 2, tasks.size()); + TaskStatus loaded1 = tasks.get(0); + TaskStatus loaded2 = tasks.get(1); + assertTasksEqual(task1, loaded1.getTask()); + assertTasksEqual(task2, loaded2.getTask()); + + // Check that the loaded task has the correct runtimes. + compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", + taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime()); + compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", + taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed()); + compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", + taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime()); + compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", + taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed()); + } + }); + + } + + public void testWritingTaskWithExtras() throws Exception { + Task.Builder b = new Builder(8, mComponent) + .setRequiresDeviceIdle(true) + .setPeriodic(10000L) + .setRequiresCharging(true); + + PersistableBundle extras = new PersistableBundle(); + extras.putDouble("hello", 3.2); + extras.putString("hi", "there"); + extras.putInt("into", 3); + b.setExtras(extras); + final Task task = b.build(); + TaskStatus taskStatus = new TaskStatus(task, SOME_UID, true /* persisted */); + + mTaskStoreUnderTest.add(taskStatus); + Thread.sleep(IO_WAIT); + mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() { + @Override + public void onTaskMapReadFinished(List<TaskStatus> tasks) { + assertEquals("Incorrect # of persisted tasks.", 1, tasks.size()); + TaskStatus loaded = tasks.get(0); + assertTasksEqual(task, loaded.getTask()); + } + }); + + } + + /** + * Helper function to throw an error if the provided task and TaskStatus objects are not equal. + */ + private void assertTasksEqual(Task first, Task second) { + assertEquals("Different task ids.", first.getId(), second.getId()); + assertEquals("Different components.", first.getService(), second.getService()); + assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic()); + assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis()); + assertEquals("Different inital backoff.", first.getInitialBackoffMillis(), + second.getInitialBackoffMillis()); + assertEquals("Different backoff policy.", first.getBackoffPolicy(), + second.getBackoffPolicy()); + + assertEquals("Invalid charging constraint.", first.isRequireCharging(), + second.isRequireCharging()); + assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(), + second.isRequireDeviceIdle()); + assertEquals("Invalid unmetered constraint.", + first.getNetworkCapabilities() == Task.NetworkType.UNMETERED, + second.getNetworkCapabilities() == Task.NetworkType.UNMETERED); + assertEquals("Invalid connectivity constraint.", + first.getNetworkCapabilities() == Task.NetworkType.ANY, + second.getNetworkCapabilities() == Task.NetworkType.ANY); + assertEquals("Invalid deadline constraint.", + first.hasLateConstraint(), + second.hasLateConstraint()); + assertEquals("Invalid delay constraint.", + first.hasEarlyConstraint(), + second.hasEarlyConstraint()); + assertEquals("Extras don't match", + first.getExtras().toString(), second.getExtras().toString()); + } + + /** + * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading + * the correct values), there is some latency involved that terrorises a naive assertEquals(). + * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision + * more reasonable. + */ + private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) { + final long DELTA_MILLIS = 700L; // We allow up to 700ms of latency for IO read/writes. + assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT); + } + + private static class StubClass {} + +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java new file mode 100644 index 0000000..e617caf --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java @@ -0,0 +1,66 @@ +/* + * 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.task.controllers; + + +import android.content.ComponentName; +import android.content.Intent; +import android.test.AndroidTestCase; + +import com.android.server.task.StateChangedListener; + +import static com.android.server.task.controllers.BatteryController.getForTesting; + +import static org.mockito.Mockito.*; + +/** + * + */ +public class BatteryControllerTest extends AndroidTestCase { + BatteryController mBatteryControllerUnderTest; + + StateChangedListener mStateChangedListenerStub = new StateChangedListener() { + @Override + public void onControllerStateChanged() { + + } + + @Override + public void onTaskDeadlineExpired(TaskStatus taskStatus) { + + } + }; + BatteryController.ChargingTracker mTrackerUnderTest; + + public void setUp() throws Exception { + mBatteryControllerUnderTest = getForTesting(mStateChangedListenerStub, getTestContext()); + mTrackerUnderTest = mBatteryControllerUnderTest.getTracker(); + } + + public void testSendBatteryChargingIntent() throws Exception { + Intent batteryConnectedIntent = new Intent(Intent.ACTION_POWER_CONNECTED) + .setComponent(new ComponentName(getContext(), mTrackerUnderTest.getClass())); + Intent batteryHealthyIntent = new Intent(Intent.ACTION_BATTERY_OKAY) + .setComponent(new ComponentName(getContext(), mTrackerUnderTest.getClass())); + + mTrackerUnderTest.onReceiveInternal(batteryConnectedIntent); + mTrackerUnderTest.onReceiveInternal(batteryHealthyIntent); + + assertTrue(mTrackerUnderTest.isOnStablePower()); + } + +} diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java index 3526f46..fb5c871 100644 --- a/telecomm/java/android/telecomm/CallServiceAdapter.java +++ b/telecomm/java/android/telecomm/CallServiceAdapter.java @@ -84,12 +84,16 @@ public final class CallServiceAdapter { /** * Tells Telecomm that an attempt to place the specified outgoing call failed. * - * @param callId The ID of the outgoing call. - * @param errorMessage The error associated with the failed call attempt. + * @param request The originating request for a connection. + * @param errorCode The error code associated with the failed call attempt. + * @param errorMsg The error message associated with the failed call attempt. */ - public void handleFailedOutgoingCall(String callId, String errorMessage) { + public void handleFailedOutgoingCall( + ConnectionRequest request, + int errorCode, + String errorMsg) { try { - mAdapter.handleFailedOutgoingCall(callId, errorMessage); + mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg); } catch (RemoteException e) { } } diff --git a/telecomm/java/android/telecomm/ConnectionRequest.aidl b/telecomm/java/android/telecomm/ConnectionRequest.aidl new file mode 100644 index 0000000..72e5c8c --- /dev/null +++ b/telecomm/java/android/telecomm/ConnectionRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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.telecomm; + +parcelable ConnectionRequest; diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java index c1f1871..bf5727b 100644 --- a/telecomm/java/android/telecomm/ConnectionRequest.java +++ b/telecomm/java/android/telecomm/ConnectionRequest.java @@ -18,23 +18,37 @@ package android.telecomm; import android.os.Bundle; import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; /** * Simple data container encapsulating a request to some entity to * create a new {@link Connection}. */ -public final class ConnectionRequest { +public final class ConnectionRequest implements Parcelable { // TODO: Token to limit recursive invocations // TODO: Consider upgrading "mHandle" to ordered list of handles, indicating a set of phone // numbers that would satisfy the client's needs, in order of preference + private final String mCallId; private final Uri mHandle; private final Bundle mExtras; public ConnectionRequest(Uri handle, Bundle extras) { - mHandle = handle; mExtras = extras; + this(null, handle, extras); } + public ConnectionRequest(String callId, Uri handle, Bundle extras) { + mCallId = callId; + mHandle = handle; + mExtras = extras; + } + + /** + * An identifier for this call. + */ + public String getCallId() { return mCallId; } + /** * The handle (e.g., phone number) to which the {@link Connection} is to connect. */ @@ -54,4 +68,40 @@ public final class ConnectionRequest { : ConnectionService.toLogSafePhoneNumber(mHandle.toString()), mExtras == null ? "" : mExtras); } -} + + /** + * Responsible for creating CallInfo objects for deserialized Parcels. + */ + public static final Parcelable.Creator<ConnectionRequest> CREATOR = + new Parcelable.Creator<ConnectionRequest> () { + @Override + public ConnectionRequest createFromParcel(Parcel source) { + String callId = source.readString(); + Uri handle = (Uri) source.readParcelable(getClass().getClassLoader()); + Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader()); + return new ConnectionRequest(callId, handle, extras); + } + + @Override + public ConnectionRequest[] newArray(int size) { + return new ConnectionRequest[size]; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Writes CallInfo object into a serializeable Parcel. + */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeString(mCallId); + destination.writeParcelable(mHandle, 0); + destination.writeParcelable(mExtras, 0); + }} diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 6e51206..59e977d 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -18,6 +18,7 @@ package android.telecomm; import android.net.Uri; import android.os.Bundle; +import android.telephony.DisconnectCause; import java.util.HashMap; import java.util.LinkedList; @@ -115,9 +116,8 @@ public abstract class ConnectionService extends CallService { } @Override - public void onError(Uri handle, String reason) { - Log.w(this, "Error in onFindSubscriptions " + callInfo.getHandle() - + " error: " + reason); + public void onError(Uri handle, int code, String msg) { + Log.w(this, "Error in onFindSubscriptions %s %d %s", handle, code, msg); getAdapter().setIsCompatibleWith(callInfo.getId(), false); } } @@ -129,6 +129,7 @@ public abstract class ConnectionService extends CallService { Log.d(this, "call %s", callInfo); onCreateConnections( new ConnectionRequest( + callInfo.getId(), callInfo.getHandle(), callInfo.getExtras()), new Response<ConnectionRequest, Connection>() { @@ -137,7 +138,8 @@ public abstract class ConnectionService extends CallService { if (result.length != 1) { Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo); getAdapter().handleFailedOutgoingCall( - callInfo.getId(), + request, + DisconnectCause.ERROR_UNSPECIFIED, "Created " + result.length + " Connections, expected 1"); for (Connection c : result) { c.abort(); @@ -151,8 +153,8 @@ public abstract class ConnectionService extends CallService { } @Override - public void onError(ConnectionRequest request, String reason) { - getAdapter().handleFailedOutgoingCall(callInfo.getId(), reason); + public void onError(ConnectionRequest request, int code, String msg) { + getAdapter().handleFailedOutgoingCall(request, code, msg); } } ); @@ -169,6 +171,7 @@ public abstract class ConnectionService extends CallService { Log.d(this, "setIncomingCallId %s %s", callId, extras); onCreateIncomingConnection( new ConnectionRequest( + callId, null, // TODO: Can we obtain this from "extras"? extras), new Response<ConnectionRequest, Connection>() { @@ -177,7 +180,8 @@ public abstract class ConnectionService extends CallService { if (result.length != 1) { Log.d(this, "adapter handleFailedOutgoingCall %s", callId); getAdapter().handleFailedOutgoingCall( - callId, + request, + DisconnectCause.ERROR_UNSPECIFIED, "Created " + result.length + " Connections, expected 1"); for (Connection c : result) { c.abort(); @@ -196,8 +200,9 @@ public abstract class ConnectionService extends CallService { } @Override - public void onError(ConnectionRequest request, String reason) { - Log.d(this, "adapter failed setIncomingCallId %s %s", request, reason); + public void onError(ConnectionRequest request, int code, String msg) { + Log.d(this, "adapter failed setIncomingCallId %s %d %s", + request, code, msg); } } ); diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java index 346d207..b531ccd 100644 --- a/telecomm/java/android/telecomm/InCallCall.java +++ b/telecomm/java/android/telecomm/InCallCall.java @@ -31,7 +31,8 @@ import java.util.List; public final class InCallCall implements Parcelable { private final String mId; private final CallState mState; - private final int mDisconnectCause; + private final int mDisconnectCauseCode; + private final String mDisconnectCauseMsg; private final int mCapabilities; private final long mConnectTimeMillis; private final Uri mHandle; @@ -47,15 +48,16 @@ public final class InCallCall implements Parcelable { public InCallCall( String id, CallState state, - int disconnectCause, + int disconnectCauseCode, + String disconnectCauseMsg, int capabilities, long connectTimeMillis, Uri handle, GatewayInfo gatewayInfo, CallServiceDescriptor descriptor, CallServiceDescriptor handoffDescriptor) { - this(id, state, disconnectCause, capabilities, connectTimeMillis, handle, gatewayInfo, - descriptor, handoffDescriptor, Collections.EMPTY_LIST, null, + this(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities, connectTimeMillis, + handle, gatewayInfo, descriptor, handoffDescriptor, Collections.EMPTY_LIST, null, Collections.EMPTY_LIST); } @@ -63,7 +65,8 @@ public final class InCallCall implements Parcelable { public InCallCall( String id, CallState state, - int disconnectCause, + int disconnectCauseCode, + String disconnectCauseMsg, int capabilities, long connectTimeMillis, Uri handle, @@ -75,7 +78,8 @@ public final class InCallCall implements Parcelable { List<String> childCallIds) { mId = id; mState = state; - mDisconnectCause = disconnectCause; + mDisconnectCauseCode = disconnectCauseCode; + mDisconnectCauseMsg = disconnectCauseMsg; mCapabilities = capabilities; mConnectTimeMillis = connectTimeMillis; mHandle = handle; @@ -101,8 +105,16 @@ public final class InCallCall implements Parcelable { * Reason for disconnection, values are defined in {@link DisconnectCause}. Valid when call * state is {@link CallState#DISCONNECTED}. */ - public int getDisconnectCause() { - return mDisconnectCause; + public int getDisconnectCauseCode() { + return mDisconnectCauseCode; + } + + /** + * Further optional textual information about the reason for disconnection. Valid when call + * state is {@link CallState#DISCONNECTED}. + */ + public String getDisconnectCauseMsg() { + return mDisconnectCauseMsg; } // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}. @@ -170,7 +182,8 @@ public final class InCallCall implements Parcelable { public InCallCall createFromParcel(Parcel source) { String id = source.readString(); CallState state = CallState.valueOf(source.readString()); - int disconnectCause = source.readInt(); + int disconnectCauseCode = source.readInt(); + String disconnectCauseMsg = source.readString(); int capabilities = source.readInt(); long connectTimeMillis = source.readLong(); ClassLoader classLoader = InCallCall.class.getClassLoader(); @@ -183,9 +196,9 @@ public final class InCallCall implements Parcelable { String parentCallId = source.readString(); List<String> childCallIds = new ArrayList<>(); source.readList(childCallIds, classLoader); - return new InCallCall(id, state, disconnectCause, capabilities, connectTimeMillis, - handle, gatewayInfo, descriptor, handoffDescriptor, conferenceCapableCallIds, - parentCallId, childCallIds); + return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities, + connectTimeMillis, handle, gatewayInfo, descriptor, handoffDescriptor, + conferenceCapableCallIds, parentCallId, childCallIds); } @Override @@ -205,7 +218,8 @@ public final class InCallCall implements Parcelable { public void writeToParcel(Parcel destination, int flags) { destination.writeString(mId); destination.writeString(mState.name()); - destination.writeInt(mDisconnectCause); + destination.writeInt(mDisconnectCauseCode); + destination.writeString(mDisconnectCauseMsg); destination.writeInt(mCapabilities); destination.writeLong(mConnectTimeMillis); destination.writeParcelable(mHandle, 0); diff --git a/telecomm/java/android/telecomm/Response.java b/telecomm/java/android/telecomm/Response.java index 14f8340..13c0702 100644 --- a/telecomm/java/android/telecomm/Response.java +++ b/telecomm/java/android/telecomm/Response.java @@ -33,7 +33,8 @@ public interface Response<IN, OUT> { * Indicates the inability to provide results. * * @param request The original request. - * @param reason The reason for the failure. + * @param code An integer code indicating the reason for failure. + * @param msg A message explaining the reason for failure. */ - void onError(IN request, String reason); + void onError(IN request, int code, String msg); } diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl index 240b1ea..17e0487 100644 --- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl @@ -17,6 +17,7 @@ package com.android.internal.telecomm; import android.telecomm.CallInfo; +import android.telecomm.ConnectionRequest; /** * Internal remote callback interface for call services. @@ -32,7 +33,7 @@ oneway interface ICallServiceAdapter { void handleSuccessfulOutgoingCall(String callId); - void handleFailedOutgoingCall(String callId, String errorMessage); + void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage); void setActive(String callId); diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index 8681344..604b895 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -18,6 +18,8 @@ package android.telephony; /** * Contains disconnect call causes generated by the framework and the RIL. + * + * @hide */ public class DisconnectCause { @@ -97,11 +99,62 @@ public class DisconnectCause { public static final int CDMA_ACCESS_BLOCKED = 35; /** Unknown error or not specified */ public static final int ERROR_UNSPECIFIED = 36; + /** + * Only emergency numbers are allowed, but we tried to dial + * a non-emergency number. + */ + // TODO: This should be the same as NOT_EMERGENCY + public static final int EMERGENCY_ONLY = 37; + /** + * The supplied CALL Intent didn't contain a valid phone number. + */ + public static final int NO_PHONE_NUMBER_SUPPLIED = 38; + /** + * Our initial phone number was actually an MMI sequence. + */ + public static final int DIALED_MMI = 39; + /** + * We tried to call a voicemail: URI but the device has no + * voicemail number configured. + */ + public static final int VOICEMAIL_NUMBER_MISSING = 40; + /** + * This status indicates that InCallScreen should display the + * CDMA-specific "call lost" dialog. (If an outgoing call fails, + * and the CDMA "auto-retry" feature is enabled, *and* the retried + * call fails too, we display this specific dialog.) + * + * TODO: this is currently unused, since the "call lost" dialog + * needs to be triggered by a *disconnect* event, rather than when + * the InCallScreen first comes to the foreground. For now we use + * the needToShowCallLostDialog field for this (see below.) + */ + public static final int CDMA_CALL_LOST = 41; + /** + * This status indicates that the call was placed successfully, + * but additionally, the InCallScreen needs to display the + * "Exiting ECM" dialog. + * + * (Details: "Emergency callback mode" is a CDMA-specific concept + * where the phone disallows data connections over the cell + * network for some period of time after you make an emergency + * call. If the phone is in ECM and you dial a non-emergency + * number, that automatically *cancels* ECM, but we additionally + * need to warn the user that ECM has been canceled (see bug + * 4207607.)) + * + * TODO: Rethink where the best place to put this is. It is not a notification + * of a failure of the connection -- it is an additional message that accompanies + * a successful connection giving the user important information about what happened. + * + * {@hide} + */ + public static final int EXITED_ECM = 42; /** Smallest valid value for call disconnect codes. */ public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED; /** Largest valid value for call disconnect codes. */ - public static final int MAXIMUM_VALID_VALUE = ERROR_UNSPECIFIED; + public static final int MAXIMUM_VALID_VALUE = EXITED_ECM; /** Private constructor to avoid class instantiation. */ private DisconnectCause() { @@ -181,6 +234,18 @@ public class DisconnectCause { return "CDMA_NOT_EMERGENCY"; case CDMA_ACCESS_BLOCKED: return "CDMA_ACCESS_BLOCKED"; + case EMERGENCY_ONLY: + return "EMERGENCY_ONLY"; + case NO_PHONE_NUMBER_SUPPLIED: + return "NO_PHONE_NUMBER_SUPPLIED"; + case DIALED_MMI: + return "DIALED_MMI"; + case VOICEMAIL_NUMBER_MISSING: + return "VOICEMAIL_NUMBER_MISSING"; + case CDMA_CALL_LOST: + return "CDMA_CALL_LOST"; + case EXITED_ECM: + return "EXITED_ECM"; case ERROR_UNSPECIFIED: return "ERROR_UNSPECIFIED"; default: diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 9da032a..6996659 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1742,16 +1742,14 @@ public class PhoneNumberUtils /** * Checks if a given number is an emergency number for the country that the user is in. - * - * @param number the number to look up. * @param context the specific context which the number should be checked against + * @param number the number to look up. + * * @return true if the specified number is an emergency number for the country the user * is currently in. */ - public static boolean isLocalEmergencyNumber(String number, Context context) { - return isLocalEmergencyNumberInternal(number, - context, - true /* useExactMatch */); + public static boolean isLocalEmergencyNumber(Context context, String number) { + return isLocalEmergencyNumberInternal(context, number, true /* useExactMatch */); } /** @@ -1767,27 +1765,24 @@ public class PhoneNumberUtils * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) - * - * @param number the number to look up. * @param context the specific context which the number should be checked against + * @param number the number to look up. + * * @return true if the specified number is an emergency number for a local country, based on the * CountryDetector. * * @see android.location.CountryDetector * @hide */ - public static boolean isPotentialLocalEmergencyNumber(String number, Context context) { - return isLocalEmergencyNumberInternal(number, - context, - false /* useExactMatch */); + public static boolean isPotentialLocalEmergencyNumber(Context context, String number) { + return isLocalEmergencyNumberInternal(context, number, false /* useExactMatch */); } /** * Helper function for isLocalEmergencyNumber() and * isPotentialLocalEmergencyNumber(). - * - * @param number the number to look up. * @param context the specific context which the number should be checked against + * @param number the number to look up. * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an @@ -1799,9 +1794,8 @@ public class PhoneNumberUtils * * @see android.location.CountryDetector */ - private static boolean isLocalEmergencyNumberInternal(String number, - Context context, - boolean useExactMatch) { + private static boolean isLocalEmergencyNumberInternal(Context context, String number, + boolean useExactMatch) { String countryIso; CountryDetector detector = (CountryDetector) context.getSystemService( Context.COUNTRY_DETECTOR); diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index f6143ed..f8dd7cf 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -276,7 +276,7 @@ public class CallerInfo { // Change the callerInfo number ONLY if it is an emergency number // or if it is the voicemail number. If it is either, take a // shortcut and skip the query. - if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) { + if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) { return new CallerInfo().markAsEmergency(context); } else if (PhoneNumberUtils.isVoiceMailNumber(number)) { return new CallerInfo().markAsVoiceMail(); diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java index 74f73b5..34fed5e 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java +++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java @@ -399,7 +399,7 @@ public class CallerInfoAsyncQuery { cw.number = number; // check to see if these are recognized numbers, and use shortcuts if we can. - if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) { + if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) { cw.event = EVENT_EMERGENCY_NUMBER; } else if (PhoneNumberUtils.isVoiceMailNumber(number)) { cw.event = EVENT_VOICEMAIL_NUMBER; diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index e9daffd..a417479 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -977,7 +977,7 @@ public final class Canvas_Delegate { /*package*/ static void native_drawText(long nativeCanvas, final char[] text, final int index, final int count, final float startX, final float startY, final int flags, long paint, - long typeface) { + final long typeface) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @@ -985,6 +985,11 @@ public final class Canvas_Delegate { public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { // WARNING: the logic in this method is similar to Paint_Delegate.measureText. // Any change to this method should be reflected in Paint.measureText + + // assert that the typeface passed is actually the one stored in paint. + assert (typeface == paintDelegate.mNativeTypeface); + + // Paint.TextAlign indicates how the text is positioned relative to X. // LEFT is the default and there's nothing to do. float x = startX; diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index 9ea4538..d73adab 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -21,6 +21,8 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.content.res.AssetManager; + import java.awt.Font; import java.io.File; import java.util.ArrayList; @@ -47,7 +49,6 @@ public class FontFamily_Delegate { 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. @@ -56,11 +57,6 @@ public class FontFamily_Delegate { 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 ---- @@ -75,6 +71,14 @@ public class FontFamily_Delegate { // ---- delegate data ---- private List<FontInfo> mFonts = new ArrayList<FontInfo>(); + /** + * The variant of the Font Family - compact or elegant. + * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in + * android.graphics.FontFamily + * + * @see Paint#setElegantTextHeight(boolean) + */ + private FontVariant mVariant; // Path of fonts that haven't been created since sFontLoader hasn't been initialized. private List<String> mPath = new ArrayList<String>(); @@ -93,37 +97,22 @@ public class FontFamily_Delegate { sPostInitDelegate.clear(); } - public Font getFont(int style, boolean isCompact) { + public Font getFont(int style) { 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; + return font.mFont; } - 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 (font.mStyle == Font.PLAIN && plainFont == null) { + 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(); + FontInfo 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; @@ -131,11 +120,20 @@ public class FontFamily_Delegate { return null; } + public FontVariant getVariant() { + return mVariant; + } + + // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long nCreateFamily() { + /*package*/ static long nCreateFamily(String lang, int variant) { + // TODO: support lang. This is required for japanese locale. FontFamily_Delegate delegate = new FontFamily_Delegate(); + // variant can be 0, 1 or 2. + assert variant < 3; + delegate.mVariant = FontVariant.values()[variant]; if (sFontLocation != null) { delegate.init(); } else { @@ -164,6 +162,13 @@ public class FontFamily_Delegate { return false; } + @LayoutlibDelegate + /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "FontFamily.addFontFromAsset is not supported.", null /*throwable*/, null /*data*/); + return false; + } + private void init() { for (String path : mPath) { addFont(path); @@ -195,13 +200,6 @@ public class FontFamily_Delegate { 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) { @@ -214,7 +212,7 @@ public class FontFamily_Delegate { } catch (Exception e) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, String.format("Unable to load font %1$s", relativePath), - null /*throwable*/, null /*data*/); + e /*throwable*/, null /*data*/); } } else { Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -224,4 +222,12 @@ public class FontFamily_Delegate { return null; } + + + // ---- Public helper class ---- + + public enum FontVariant { + // The order needs to be kept in sync with android.graphics.FontFamily. + NONE, COMPACT, ELEGANT + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 911f4e7..6ee307e 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.FontFamily_Delegate.FontVariant; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.FontMetricsInt; import android.text.TextUtils; @@ -30,7 +31,6 @@ import java.awt.Font; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; -import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.Collections; @@ -81,7 +81,8 @@ public class Paint_Delegate { private float mTextScaleX; private float mTextSkewX; private int mHintingMode = Paint.HINTING_ON; - private boolean mIsCompact = true; + // Variant of the font. + private FontVariant mFontVariant = FontVariant.NONE; private Xfermode_Delegate mXfermode; private ColorFilter_Delegate mColorFilter; @@ -92,6 +93,8 @@ public class Paint_Delegate { private Locale mLocale = Locale.getDefault(); + // Used only to assert invariants. + public long mNativeTypeface; // ---- Public Helper methods ---- @@ -437,7 +440,7 @@ public class Paint_Delegate { /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); - return delegate != null && !delegate.mIsCompact; + return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; } @LayoutlibDelegate @@ -448,7 +451,7 @@ public class Paint_Delegate { return; } - delegate.mIsCompact = !elegant; + delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT; } @LayoutlibDelegate @@ -887,6 +890,7 @@ public class Paint_Delegate { } delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.mNativeTypeface = typeface; delegate.updateFontObject(); return typeface; } @@ -965,15 +969,10 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, - long native_typeface /*ignored*/, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, 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; @@ -982,6 +981,12 @@ public class Paint_Delegate { if (delegate == null) { return 0.f; } + + // 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. + assert (native_typeface == delegate.mNativeTypeface); + boolean isRtl = isRtl(flags); int limit = index + count; @@ -1022,37 +1027,41 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, int bidiFlags, - char[] text, int index, int count, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + int bidiFlags, char[] text, int index, int count, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextPath is not supported.", null, null /*data*/); } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, int bidiFlags, - String text, int start, int end, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + int bidiFlags, String text, int start, int end, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextPath is not supported.", null, null /*data*/); } @LayoutlibDelegate - /*package*/ static void nativeGetStringBounds(long nativePaint, String text, int start, - int end, int bidiFlags, Rect bounds) { - nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bidiFlags, - bounds); + /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface, + String text, int start, int end, int bidiFlags, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, bounds); } @LayoutlibDelegate - /*package*/ static void nativeGetCharArrayBounds(long nativePaint, char[] text, int index, - int count, int bidiFlags, Rect bounds) { + /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface, + char[] text, int index, int count, int bidiFlags, Rect bounds) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return; } + + // assert that the typeface passed is actually the one that we had stored. + assert (native_typeface == delegate.mNativeTypeface); + delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); } @@ -1079,6 +1088,7 @@ public class Paint_Delegate { mJoin = paint.mJoin; mTextAlign = paint.mTextAlign; mTypeface = paint.mTypeface; + mNativeTypeface = paint.mNativeTypeface; mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; mTextSize = paint.mTextSize; @@ -1102,6 +1112,7 @@ public class Paint_Delegate { mJoin = Paint.Join.MITER.nativeInt; mTextAlign = 0; mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mNativeTypeface = 0; mStrokeWidth = 1.f; mStrokeMiter = 4.f; mTextSize = 20.f; @@ -1124,7 +1135,7 @@ public class Paint_Delegate { private void updateFontObject() { if (mTypeface != null) { // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(mIsCompact); + List<Font> fonts = mTypeface.getFonts(mFontVariant); // 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/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 9746b48..908bb64 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -22,6 +22,7 @@ import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.AssetManager; +import android.graphics.FontFamily_Delegate.FontVariant; import java.awt.Font; import java.io.File; @@ -69,17 +70,28 @@ public final class Typeface_Delegate { return sManager.getDelegate(nativeTypeface); } - public List<Font> getFonts(boolean compact) { + public List<Font> getFonts(FontVariant variant) { List<Font> fonts = new ArrayList<Font>(mFontFamilies.length); + // If we are unable to find fonts matching the variant, we return the fonts from the + // other variant since we always want to draw something, rather than nothing. + // TODO: check this behaviour with platform. + List<Font> otherVariantFonts = new ArrayList<Font>(); for (FontFamily_Delegate ffd : mFontFamilies) { if (ffd != null) { - Font font = ffd.getFont(mStyle, compact); + Font font = ffd.getFont(mStyle); if (font != null) { - fonts.add(font); + if (ffd.getVariant() == variant || ffd.getVariant() == FontVariant.NONE) { + fonts.add(font); + } else { + otherVariantFonts.add(font); + } } } } - return fonts; + if (fonts.size() > 0) { + return fonts; + } + return otherVariantFonts; } // ---- native methods ---- 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 ffab4de..cc69af2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -214,7 +214,8 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.EXTENDED_VIEWINFO, Capability.FIXED_SCALABLE_NINE_PATCH, Capability.RTL, - Capability.ACTION_BAR); + Capability.ACTION_BAR, + Capability.SIMULATE_PLATFORM); BridgeAssetManager.initSystem(); 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 e59ccd7..d95c815 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 @@ -23,10 +23,8 @@ import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; -import com.android.ide.common.rendering.api.SystemViewCookie; import com.android.internal.R; import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.util.Predicate; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.internal.widget.ActionBarAccessor; @@ -50,7 +48,6 @@ import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ActionMenuView; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ListAdapter; @@ -59,8 +56,6 @@ import android.widget.RelativeLayout; import java.util.ArrayList; -import static com.android.ide.common.rendering.api.SystemViewCookie.ACTION_BAR_OVERFLOW; - /** * A layout representing the action bar. */ @@ -174,29 +169,6 @@ public class ActionBarLayout extends LinearLayout { mActionBarView.setSplitToolbar(mSplit); inflateMenus(); - - // Find if the Overflow Menu Button (the three dots) exists. If yes, - // add the view cookie. - Predicate<View> overflowMenuButtonTest = new Predicate<View>() { - @Override - public boolean apply(View view) { - ViewGroup.LayoutParams lp = view.getLayoutParams(); - return lp instanceof ActionMenuView.LayoutParams && - ((ActionMenuView.LayoutParams) lp).isOverflowButton; - } - }; - View overflowMenu = null; - if (mSplit) { - if (splitView != null) { - overflowMenu = splitView.findViewByPredicate(overflowMenuButtonTest); - } - } - else { - overflowMenu = mActionBarView.findViewByPredicate(overflowMenuButtonTest); - } - if (overflowMenu != null) { - mBridgeContext.addViewKey(overflowMenu, new SystemViewCookie(ACTION_BAR_OVERFLOW)); - } } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index 1498044..2421f29 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.bars; +import com.android.layoutlib.bridge.impl.Config; import com.android.resources.Density; import com.android.resources.ResourceType; @@ -30,14 +31,14 @@ import android.widget.TextView; public class StatusBar extends CustomBar { - public StatusBar(Context context, Density density, int direction, boolean RtlEnabled) - throws XmlPullParserException { + public StatusBar(Context context, Density density, int direction, boolean RtlEnabled, + int simulatedPlatformVersion) throws XmlPullParserException { // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml"); // FIXME: use FILL_H? setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT); - setBackgroundColor(0xFF000000); + setBackgroundColor(Config.getStatusBarColor(simulatedPlatformVersion)); // Cannot access the inside items through id because no R.id values have been // created for them. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Config.java new file mode 100644 index 0000000..e8bc292 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Config.java @@ -0,0 +1,33 @@ +/* + * 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.layoutlib.bridge.impl; + +/** + * Various helper methods to simulate older versions of platform. + */ +public class Config { + + public static boolean showOnScreenNavBar(int platformVersion) { + // return true if ICS or later. + return platformVersion >= 14 || platformVersion == 0; + } + + public static int getStatusBarColor(int platformVersion) { + // return white for froyo and earlier; black otherwise. + return platformVersion >= 9 || platformVersion == 0 ? 0xFF000000 : 0xFFFFFFFF; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 4af73cf..75db8e1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -37,6 +37,7 @@ import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; import com.android.internal.util.XmlUtils; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.BridgeMenuItemImpl; @@ -83,9 +84,11 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewParent; import android.view.WindowManagerGlobal_Delegate; import android.widget.AbsListView; import android.widget.AbsSpinner; +import android.widget.ActionMenuView; import android.widget.AdapterView; import android.widget.ExpandableListView; import android.widget.FrameLayout; @@ -267,12 +270,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot = topLayout; topLayout.setOrientation(LinearLayout.HORIZONTAL); - try { - NavigationBar navigationBar = createNavigationBar(context, - hardwareConfig.getDensity(), isRtl, params.isRtlSupported()); - topLayout.addView(navigationBar); - } catch (XmlPullParserException ignored) { - + if (Config.showOnScreenNavBar(params.getSimulatedPlatformVersion())) { + try { + NavigationBar navigationBar = createNavigationBar(context, + hardwareConfig.getDensity(), isRtl, params.isRtlSupported()); + topLayout.addView(navigationBar); + } catch (XmlPullParserException ignored) { + } } } @@ -325,7 +329,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // system bar try { StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(), - layoutDirection, params.isRtlSupported()); + layoutDirection, params.isRtlSupported(), + params.getSimulatedPlatformVersion()); topLayout.addView(statusBar); } catch (XmlPullParserException ignored) { @@ -368,7 +373,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { backgroundLayout.addView(mContentRoot); } - if (mNavigationBarOrientation == LinearLayout.HORIZONTAL && + if (Config.showOnScreenNavBar(params.getSimulatedPlatformVersion()) && + mNavigationBarOrientation == LinearLayout.HORIZONTAL && mNavigationBarSize > 0) { // system bar try { @@ -1473,16 +1479,49 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { ViewInfo result; if (isContentFrame) { + // The view is part of the layout added by the user. Hence, + // the ViewCookie may be obtained only through the Context. result = new ViewInfo(view.getClass().getName(), - getViewKey(view), + getContext().getViewKey(view), view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, view, view.getLayoutParams()); - } else { - result = new SystemViewInfo(view.getClass().getName(), + // We are part of the system decor. + SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), getViewKey(view), view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), view, view.getLayoutParams()); + result = r; + // We currently mark three kinds of views: + // 1. Menus in the Action Bar + // 2. Menus in the Overflow popup. + // 3. The overflow popup button. + if (view instanceof ListMenuItemView) { + // Mark 2. + // All menus in the popup are of type ListMenuItemView. + r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU); + } else { + // Mark 3. + ViewGroup.LayoutParams lp = view.getLayoutParams(); + if (lp instanceof ActionMenuView.LayoutParams && + ((ActionMenuView.LayoutParams) lp).isOverflowButton) { + r.setViewType(ViewType.ACTION_BAR_OVERFLOW); + } else { + // Mark 1. + // A view is a menu in the Action Bar is it is not the overflow button and of + // its parent is of type ActionMenuView. We can also check if the view is + // instanceof ActionMenuItemView but that will fail for menus using + // actionProviderClass. + ViewParent parent = view.getParent(); + while (parent != mViewRoot && parent instanceof ViewGroup) { + if (parent instanceof ActionMenuView) { + r.setViewType(ViewType.ACTION_BAR_MENU); + break; + } + parent = parent.getParent(); + } + } + } } if (setExtendedInfo) { @@ -1501,7 +1540,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return result; } - /** + /* (non-Javadoc) * The cookie for menu items are stored in menu item and not in the map from View stored in * BridgeContext. */ @@ -1535,9 +1574,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * Creates the status bar with wifi and battery icons. */ private StatusBar createStatusBar(BridgeContext context, Density density, int direction, - boolean isRtlSupported) throws XmlPullParserException { + boolean isRtlSupported, int platformVersion) throws XmlPullParserException { StatusBar statusBar = new StatusBar(context, density, - direction, isRtlSupported); + direction, isRtlSupported, platformVersion); statusBar.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, mStatusBarSize)); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java index 5c267df..9fea167 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java @@ -17,9 +17,15 @@ package com.android.layoutlib.bridge.impl; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; +/** + * ViewInfo for views added by the platform. + */ public class SystemViewInfo extends ViewInfo { + private ViewType mViewType; + public SystemViewInfo(String name, Object cookie, int left, int top, int right, int bottom) { super(name, cookie, left, top, right, bottom); @@ -32,7 +38,14 @@ public class SystemViewInfo extends ViewInfo { } @Override - public boolean isSystemView() { - return true; + public ViewType getViewType() { + if (mViewType != null) { + return mViewType; + } + return ViewType.SYSTEM_UNKNOWN; + } + + public void setViewType(ViewType type) { + mViewType = type; } } |
