Merge "VolumeZen: Only display active condition when entering." into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index 79fb8df..daf7f84 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -490,6 +490,7 @@
field public static final int editTextStyle = 16842862; // 0x101006e
field public static final deprecated int editable = 16843115; // 0x101016b
field public static final int editorExtras = 16843300; // 0x1010224
+ field public static final int elegantTextHeight = 16843867; // 0x101045b
field public static final int elevation = 16843843; // 0x1010443
field public static final int ellipsize = 16842923; // 0x10100ab
field public static final int ems = 16843096; // 0x1010158
@@ -5362,7 +5363,7 @@
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 +5387,7 @@
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 +5414,7 @@
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;
@@ -11901,7 +11902,6 @@
method public int getMinDelay();
method public java.lang.String getName();
method public float getPower();
- method public java.lang.String getRequiredPermission();
method public float getResolution();
method public java.lang.String getStringType();
method public int getType();
@@ -12146,7 +12146,6 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_INFO_MINIMUM_FOCUS_DISTANCE;
field public static final android.hardware.camera2.CameraCharacteristics.Key NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_AVAILABLE_CAPABILITIES;
- field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_INPUT_STREAMS;
field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_PROC;
field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_PROC_STALLING;
field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_MAX_NUM_OUTPUT_RAW;
@@ -12371,7 +12370,6 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; // 0x5
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; // 0x3
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; // 0x2
- field public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; // 0x4
field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0
field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1
field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR = 3; // 0x3
@@ -12670,6 +12668,7 @@
}
public final class TonemapCurve {
+ ctor public TonemapCurve(float[], float[], float[]);
method public void copyColorCurve(int, float[], int);
method public android.graphics.PointF getPoint(int, int);
method public int getPointCount(int);
@@ -15824,6 +15823,8 @@
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);
@@ -15881,6 +15882,10 @@
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";
@@ -15889,8 +15894,10 @@
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";
@@ -27394,55 +27401,6 @@
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_CALL_LOST = 41; // 0x29
- 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 DIALED_MMI = 39; // 0x27
- field public static final int EMERGENCY_ONLY = 37; // 0x25
- 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 = 42; // 0x2a
- 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 NO_PHONE_NUMBER_SUPPLIED = 38; // 0x26
- 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
- field public static final int VOICEMAIL_NUMBER_MISSING = 40; // 0x28
- }
-
public class NeighboringCellInfo implements android.os.Parcelable {
ctor public deprecated NeighboringCellInfo();
ctor public deprecated NeighboringCellInfo(int, int);
@@ -27494,7 +27452,7 @@
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);
@@ -31990,7 +31948,6 @@
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);
@@ -32638,6 +32595,10 @@
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);
@@ -33797,11 +33758,17 @@
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..87d57fb4 100644
--- a/core/java/android/app/task/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -20,6 +20,7 @@
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 @@
}
/**
+ * 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 @@
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 @@
/**
* Bundle of extras which are returned to your application at execution time.
*/
- public Bundle getExtras() {
+ public PersistableBundle getExtras() {
return extras;
}
@@ -171,7 +184,7 @@
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 @@
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 @@
@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 @@
}
};
- /**
- * 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 @@
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 @@
* 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 @@
* @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 dacb3480..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 @@
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 @@
/**
* @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 @@
private TaskParams(Parcel in) {
taskId = in.readInt();
- extras = in.readBundle();
+ extras = in.readPersistableBundle();
callback = in.readStrongBinder();
}
@@ -73,7 +73,7 @@
@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/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 86208fc..c593e9e 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -613,6 +613,7 @@
}
/**
+ * @hide
* @return The permission required to access this sensor. If empty, no permission is required.
*/
public String getRequiredPermission() {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 222374a..08cfc87 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -751,6 +751,7 @@
* <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input
* stream image format will be RAW_OPAQUE, the associated output stream image format
* should be JPEG.</p>
+ * @hide
*/
public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS =
new Key<Integer>("android.request.maxNumInputStreams", int.class);
@@ -974,7 +975,7 @@
* <p>The mapping of image formats that are supported by this
* camera device for input streams, to their corresponding output formats.</p>
* <p>All camera devices with at least 1
- * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one
+ * android.request.maxNumInputStreams will have at least one
* available input format.</p>
* <p>The camera device will support the following map of formats,
* if its dependent capability is supported:</p>
@@ -1021,8 +1022,6 @@
* <p>Attempting to configure an input stream with output streams not
* listed as available in this map is not valid.</p>
* <p>TODO: typedef to ReprocessFormatMap</p>
- *
- * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @hide
*/
public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index b3e165e..94a5a79 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -340,6 +340,7 @@
* (both input/output) will match the maximum available
* resolution of JPEG streams.</li>
* </ul>
+ * <p>@hide this, TODO: remove it when input related APIs are ready.</p>
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4;
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index f74fbaa..c3e042e 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -446,7 +446,12 @@
@Override
public void onUnconfigured(CameraDevice camera) {
- mUnconfigureDrainer.taskFinished();
+ synchronized (session) {
+ // Ignore #onUnconfigured before #close is called
+ if (mClosed) {
+ mUnconfigureDrainer.taskFinished();
+ }
+ }
}
};
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 9795082..d4adae1 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -101,11 +101,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onOpened(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onOpened(CameraDeviceImpl.this);
}
+ mDeviceListener.onOpened(CameraDeviceImpl.this);
}
}
};
@@ -114,11 +114,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onUnconfigured(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onUnconfigured(CameraDeviceImpl.this);
}
+ mDeviceListener.onUnconfigured(CameraDeviceImpl.this);
}
}
};
@@ -127,11 +127,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onActive(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onActive(CameraDeviceImpl.this);
}
+ mDeviceListener.onActive(CameraDeviceImpl.this);
}
}
};
@@ -140,11 +140,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onBusy(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onBusy(CameraDeviceImpl.this);
}
+ mDeviceListener.onBusy(CameraDeviceImpl.this);
}
}
};
@@ -152,11 +152,11 @@
private final Runnable mCallOnClosed = new Runnable() {
@Override
public void run() {
- mDeviceListener.onClosed(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onClosed(CameraDeviceImpl.this);
}
+ mDeviceListener.onClosed(CameraDeviceImpl.this);
}
};
@@ -164,11 +164,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onIdle(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onIdle(CameraDeviceImpl.this);
}
+ mDeviceListener.onIdle(CameraDeviceImpl.this);
}
}
};
@@ -177,11 +177,11 @@
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
- mDeviceListener.onDisconnected(CameraDeviceImpl.this);
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onDisconnected(CameraDeviceImpl.this);
}
+ mDeviceListener.onDisconnected(CameraDeviceImpl.this);
}
}
};
diff --git a/core/java/android/hardware/camera2/params/TonemapCurve.java b/core/java/android/hardware/camera2/params/TonemapCurve.java
index 0fcffac..481d67a 100644
--- a/core/java/android/hardware/camera2/params/TonemapCurve.java
+++ b/core/java/android/hardware/camera2/params/TonemapCurve.java
@@ -78,7 +78,7 @@
/**
* Create a new immutable TonemapCurve instance.
*
- * <p>Values are stored as a contiguous {@code (Pin, Pout}) point.</p>
+ * <p>Values are stored as a contiguous array of {@code (Pin, Pout)} points.</p>
*
* <p>All parameters may have independent length but should have at most
* {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS} * {@value #POINT_SIZE} elements.</p>
@@ -88,15 +88,16 @@
*
* <p>This constructor copies the array contents and does not retain ownership of the array.</p>
*
- * @param elements An array of elements whose length is {@code CHANNEL_COUNT * rows * columns}
+ * @param red An array of elements whose length is divisible by {@value #POINT_SIZE}
+ * @param green An array of elements whose length is divisible by {@value #POINT_SIZE}
+ * @param blue An array of elements whose length is divisible by {@value #POINT_SIZE}
*
* @throws IllegalArgumentException
- * if the {@code elements} array length is invalid,
- * if any of the subelems are not finite
+ * if any of input array length is invalid,
+ * or if any of the elements in the array are not in the range of
+ * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}]
* @throws NullPointerException
- * if any of the parameters is {@code null}
- *
- * @hide
+ * if any of the parameters are {@code null}
*/
public TonemapCurve(float[] red, float[] green, float[] blue) {
// TODO: maxCurvePoints check?
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 @@
}
/**
- * 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 @@
* language
* @hide
*/
+ @Nullable
public final String getRawLocale() {
return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
}
@@ -86,6 +89,7 @@
* @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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
/** 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 @@
/** @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 @@
/**
* 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 @@
}
/**
+ * 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 @@
/**
* @hide
*/
+ @NonNull
public static CaptionStyle getCustomStyle(ContentResolver cr) {
final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
final int foregroundColor = Secure.getInt(
@@ -370,12 +461,17 @@
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 @@
*
* @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 @@
* @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 @@
* @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/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a4a9680e..43c8dde 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -222,6 +222,7 @@
* @attr ref android.R.styleable#TextView_imeActionLabel
* @attr ref android.R.styleable#TextView_imeActionId
* @attr ref android.R.styleable#TextView_editorExtras
+ * @attr ref android.R.styleable#TextView_elegantTextHeight
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -2637,6 +2638,8 @@
* metrics, and also increases top and bottom bounds to provide more space.
*
* @param elegant set the paint's elegant metrics flag.
+ *
+ * @attr ref android.R.styleable#TextView_elegantTextHeight
*/
public void setElegantTextHeight(boolean elegant) {
mTextPaint.setElegantTextHeight(elegant);
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 @@
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 @@
#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 @@
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 @@
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 @@
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 @@
#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 @@
#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 @@
#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 @@
#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 @@
#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 @@
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 @@
#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 786d19c..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
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 @@
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 @@
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/layout/preference_category_quantum.xml b/core/res/res/layout/preference_category_quantum.xml
index 032e09d..9dd0d86 100644
--- a/core/res/res/layout/preference_category_quantum.xml
+++ b/core/res/res/layout/preference_category_quantum.xml
@@ -21,8 +21,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
android:textAppearance="@style/TextAppearance.Quantum.Body2"
- android:textColor="?android:attr/textColorSecondary"
- android:textStyle="bold"
+ android:textColor="?android:attr/colorAccent"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingTop="16dip" />
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/public.xml b/core/res/res/values/public.xml
index 88e1cda..5d9f3e5 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2179,6 +2179,7 @@
<public type="attr" name="contentInsetRight" />
<public type="attr" name="paddingMode" />
<public type="attr" name="selectableItemBackgroundBorderless" />
+ <public type="attr" name="elegantTextHeight" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6a032e2..61b6a0d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1456,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
index a30aa0c..b828205 100644
--- a/data/fonts/Roboto-MediumItalic.ttf
+++ b/data/fonts/Roboto-MediumItalic.ttf
Binary files differ
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 @@
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 @@
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 @@
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/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 @@
// 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 @@
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 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 @@
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 @@
}
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 @@
}
/**
+ * 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 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 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/native/android/sensor.cpp b/native/android/sensor.cpp
index acfcd83..3f37ed1 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -171,8 +171,3 @@
{
return static_cast<Sensor const*>(sensor)->getStringType().string();
}
-
-const char* ASensor_getRequiredPermission(ASensor const* sensor)
-{
- return static_cast<Sensor const*>(sensor)->getRequiredPermission().string();
-}
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png
deleted file mode 100644
index 7ed4c78..0000000
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png
deleted file mode 100644
index 08c07b2..0000000
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth.png
deleted file mode 100644
index bd4e1ae..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png
deleted file mode 100644
index e82c6e4..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth.png
deleted file mode 100644
index 757dbf3..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth_connected.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth_connected.png
deleted file mode 100644
index d431dc2..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_bluetooth_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth.png
deleted file mode 100644
index 17ffdb9..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth_connected.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth_connected.png
deleted file mode 100644
index 6ec234e..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_data_bluetooth_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml
new file mode 100644
index 0000000..e28490b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="18dp"
+ android:height="18dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml
new file mode 100644
index 0000000..c012d14
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="18dp"
+ android:height="18dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
+</vector>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 20ed629..7431e69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -84,7 +84,7 @@
} else if (connecting) {
state.iconId = R.drawable.ic_qs_bluetooth_connecting;
stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
- state.label = mController.getLastDeviceName();
+ state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
} else {
state.iconId = R.drawable.ic_qs_bluetooth_on;
stateContentDescription = mContext.getString(R.string.accessibility_desc_on);
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 0cc09c8..116d755d 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 @@
@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 @@
Messenger mService = null;
Messenger mMessenger;
RecentsMessageHandler mHandler;
+ boolean mBootCompleted = false;
boolean mServiceIsBound = false;
boolean mToggleRecentsUponServiceBound;
RecentsServiceConnection mConnection = new RecentsServiceConnection();
@@ -182,6 +183,10 @@
bindToRecentsService(false);
}
+ public void onBootCompleted() {
+ mBootCompleted = true;
+ }
+
/** Shows the recents */
public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
if (Console.Enabled) {
@@ -208,7 +213,7 @@
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 @@
/** 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.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 @@
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 7926d03..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 @@
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;
@@ -719,6 +718,16 @@
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) {
@@ -805,14 +814,17 @@
}
@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);
}
}
@@ -828,7 +840,10 @@
@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 @@
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 @@
private int mTrackingPointer;
protected int mTouchSlop;
protected boolean mHintAnimationRunning;
+ private boolean mOverExpandedBeforeFling;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -370,13 +370,12 @@
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 @@
onExpandingFinished();
}
});
- animator.start();
mHeightAnimator = animator;
+ animator.start();
}
@Override
@@ -433,7 +432,7 @@
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 @@
// 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 @@
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.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 @@
@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 @@
}
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 @@
reset();
}
+ public boolean isOccluded() {
+ return mOccluded;
+ }
+
/**
* Hides the keyguard view
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 117bf61..379b509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -45,6 +45,7 @@
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
context.registerReceiver(this, filter);
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -104,8 +105,8 @@
@Override
public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getName()
- : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getName()
+ return mLastDevice != null ? mLastDevice.getAliasName()
+ : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getAliasName()
: null;
}
@@ -122,6 +123,9 @@
== BluetoothAdapter.STATE_CONNECTING;
mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
}
+ if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
+ mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ }
fireCallbacks();
updateBondedBluetoothDevices();
}
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 @@
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 @@
}
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 @@
@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/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 @@
private long[] mDefaultVibrationPattern;
private long[] mFallbackVibrationPattern;
+ private boolean mUseAttentionLight;
boolean mSystemReady;
private boolean mDisableNotificationAlerts;
@@ -182,7 +183,7 @@
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 @@
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 @@
}
}
- // 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 @@
// 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 @@
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 @@
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 @@
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,136 +1586,14 @@
+ 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);
- }
-
- 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);
- }
- }
- }
-
- // 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);
- }
- }
+ // 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;
}
- // 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();
- }
- }
+ buzzBeepBlinkLocked(r);
}
}
});
@@ -1741,6 +1601,158 @@
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);
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ // 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() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
@@ -1866,6 +1878,9 @@
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 @@
}
// light
- mLights.remove(r);
+ mLights.remove(r.getKey());
if (mLedNotification == r) {
mLedNotification = null;
}
@@ -2196,7 +2211,7 @@
// 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 @@
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 @@
// 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.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 @@
* 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 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 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 @@
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 @@
* {@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 @@
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 @@
* - 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 @@
* 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 @@
* @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.");
}
- mDirtyOperations = 0;
+ mIoHandler.post(new WriteTasksMapToDiskRunnable());
}
- return true;
+ }
+
+ private void readTaskMapFromDiskAsync(TaskMapReadFinishedListener callback) {
+ mIoHandler.post(new ReadTaskMapFromDiskRunnable(callback));
+ }
+
+ public void readTaskMapFromDisk(TaskMapReadFinishedListener callback) {
+ new ReadTaskMapFromDiskRunnable(callback).run();
}
/**
- *
- * @return
+ * Runnable that writes {@link #mTasksSet} out to xml.
+ * NOTE: This Runnable locks on TaskStore.this
*/
- // TODO: Implement this.
- private ArraySet<TaskStatus> intialiseTasksFromDisk() {
- return new ArraySet<TaskStatus>();
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Runnable that reads list of persisted task from xml.
+ * NOTE: This Runnable locks on TaskStore.this
+ */
+ 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.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 @@
*/
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 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.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 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 @@
* 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 @@
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 @@
* @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 @@
* 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 @@
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.uId = rescheduling.getUid();
- this.persisted = rescheduling.isPersisted();
- this.numFailures = backoffAttempt;
+ this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
+ this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
+ }
- earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed;
- latestRunTimeElapsedMillis = newLatestRuntimeElapsed;
+ /** 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 = newEarliestRuntimeElapsedMillis;
+ latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
}
public Task getTask() {
@@ -125,7 +144,7 @@
return uId;
}
- public Bundle getExtras() {
+ public PersistableBundle getExtras() {
return task.getExtras();
}
@@ -142,11 +161,11 @@
}
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.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 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/CallService.java b/telecomm/java/android/telecomm/CallService.java
index d452172..a254459 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -63,6 +63,7 @@
private static final int MSG_STOP_DTMF_TONE = 13;
private static final int MSG_ADD_TO_CONFERENCE = 14;
private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
+ private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
/**
* Default Handler used to consolidate binder method calls onto a single thread.
@@ -150,6 +151,17 @@
}
break;
}
+ case MSG_ON_POST_DIAL_CONTINUE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ boolean proceed = (args.argi1 == 1);
+ onPostDialContinue(callId, proceed);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
default:
break;
}
@@ -247,6 +259,14 @@
args.arg2 = callId;
mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
}
+
+ @Override
+ public void onPostDialContinue(String callId, boolean proceed) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.argi1 = proceed ? 1 : 0;
+ mMessageHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
+ }
}
/**
@@ -422,4 +442,7 @@
* @hide
*/
public abstract void splitFromConference(String conferenceCallId, String callId);
+
+ public void onPostDialContinue(String callId, boolean proceed) {}
+ public void onPostDialWait(Connection conn, String remaining) {}
}
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index 0c57828..fb5c871 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -217,4 +217,11 @@
} catch (RemoteException ignored) {
}
}
+
+ public void onPostDialWait(String callId, String remaining) {
+ try {
+ mAdapter.onPostDialWait(callId, remaining);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 8cce8e6..344814f 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -444,6 +444,11 @@
*/
protected void onReject() {}
+ /**
+ * Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
+ */
+ protected void onPostDialContinue(boolean proceed) {}
+
private void setState(int state) {
Log.d(this, "setState: %s", stateToString(state));
onSetState(state);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 31de15c..59e977d 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -146,7 +146,8 @@
}
} else {
addConnection(callInfo.getId(), result[0]);
- Log.d(this, "adapter handleSuccessfulOutgoingCall %s", callInfo.getId());
+ Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
+ callInfo.getId());
getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
}
}
@@ -288,6 +289,25 @@
// TODO(santoscordon): Find existing conference call and invoke split(connection).
}
+ @Override
+ public final void onPostDialContinue(String callId, boolean proceed) {
+ Log.d(this, "onPostDialContinue(%s)", callId);
+
+ Connection connection = findConnectionForAction(callId, "onPostDialContinue");
+ if (connection == NULL_CONNECTION) {
+ Log.w(this, "Connection missing in post-dial request %s.", callId);
+ return;
+ }
+ connection.onPostDialContinue(proceed);
+ }
+
+ @Override
+ public final void onPostDialWait(Connection conn, String remaining) {
+ Log.d(this, "onPostDialWait(%s, %s)", conn, remaining);
+
+ getAdapter().onPostDialWait(mIdByConnection.get(conn), remaining);
+ }
+
/**
* Find a set of Subscriptions matching a given handle (e.g. phone number).
*
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index 6838ede..0bef419 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -174,13 +174,14 @@
* will pause playing the tones and notify the {@link InCallService} that the call is in the
* {@link InCallService#setPostDialWait(String,String)} state. When the user decides to continue
* the postdial sequence, the {@link InCallService} should invoke the
- * {@link #postDialContinue(String)} method.
+ * {@link #postDialContinue(String,boolean)} method.
*
* @param callId The unique ID of the call for which postdial string playing should continue.
+ * @param proceed Whether or not to continue with the post-dial sequence.
*/
- public void postDialContinue(String callId) {
+ public void postDialContinue(String callId, boolean proceed) {
try {
- mAdapter.postDialContinue(callId);
+ mAdapter.postDialContinue(callId, proceed);
} catch (RemoteException e) {
}
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallService.aidl b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
index 771a3ae..9139aa6 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
@@ -59,4 +59,6 @@
void addToConference(String conferenceCallId, in List<String> callIds);
void splitFromConference(String conferenceCallId, String callId);
+
+ void onPostDialContinue(String callId, boolean proceed);
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index f94eb32..17e0487 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -52,4 +52,6 @@
void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced);
void removeCall(String callId);
+
+ void onPostDialWait(String callId, String remaining);
}
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 6a27217..f144043 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -44,7 +44,7 @@
void stopDtmfTone(String callId);
- void postDialContinue(String callId);
+ void postDialContinue(String callId, boolean proceed);
void handoffCall(String callId);
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index d2044be..604b895 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -18,6 +18,8 @@
/**
* Contains disconnect call causes generated by the framework and the RIL.
+ *
+ * @hide
*/
public class DisconnectCause {
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 @@
/**
* 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 @@
* 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 @@
*
* @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 @@
// 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 @@
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 @@
/*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 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.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 @@
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 @@
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 @@
// ---- 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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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.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.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 @@
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 @@
private Locale mLocale = Locale.getDefault();
+ // Used only to assert invariants.
+ public long mNativeTypeface;
// ---- Public Helper methods ----
@@ -437,7 +440,7 @@
/*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 @@
return;
}
- delegate.mIsCompact = !elegant;
+ delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT;
}
@LayoutlibDelegate
@@ -887,6 +890,7 @@
}
delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
+ delegate.mNativeTypeface = typeface;
delegate.updateFontObject();
return typeface;
}
@@ -965,15 +969,10 @@
}
@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 @@
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 @@
}
@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 @@
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 @@
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 @@
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.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 @@
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 @@
Capability.EXTENDED_VIEWINFO,
Capability.FIXED_SCALABLE_NINE_PATCH,
Capability.RTL,
- Capability.ACTION_BAR);
+ Capability.ACTION_BAR,
+ Capability.SIMULATE_PLATFORM);
BridgeAssetManager.initSystem();