Merge "Add a debug assert to track down infinite loop"
diff --git a/api/current.txt b/api/current.txt
index dd9b5a1..9df1023 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28,7 +28,6 @@
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
- field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
@@ -34425,39 +34424,6 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment);
- method public final void clearAnnotation(java.lang.String);
- method public void onNotificationActionClick(java.lang.String, long, int);
- method public void onNotificationClick(java.lang.String, long);
- method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
- method public void onNotificationRemoved(java.lang.String, long, int);
- method public void onNotificationVisibilityChanged(java.lang.String, long, boolean);
- method public final void setAnnotation(java.lang.String, android.app.Notification);
- field public static final int REASON_APP_CANCEL = 8; // 0x8
- field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
- field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
- field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
- field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
- field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
- field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
- field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
- field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
- field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
- field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
- field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
- field public static final int REASON_PACKAGE_SUSPENDED = 15; // 0xf
- field public static final int REASON_PROFILE_TURNED_OFF = 16; // 0x10
- field public static final int REASON_TOPIC_BANNED = 14; // 0xe
- field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- }
-
- public class NotificationAssistantService.Adjustment {
- ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri);
- }
-
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
diff --git a/api/system-current.txt b/api/system-current.txt
index ef4841e..03167f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -41,7 +41,6 @@
field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
- field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
@@ -36839,13 +36838,11 @@
public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
ctor public NotificationAssistantService();
method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment);
- method public final void clearAnnotation(java.lang.String);
method public void onNotificationActionClick(java.lang.String, long, int);
method public void onNotificationClick(java.lang.String, long);
method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public void onNotificationRemoved(java.lang.String, long, int);
method public void onNotificationVisibilityChanged(java.lang.String, long, boolean);
- method public final void setAnnotation(java.lang.String, android.app.Notification);
field public static final int REASON_APP_CANCEL = 8; // 0x8
field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index 80d82c3..d6175af 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -28,7 +28,6 @@
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
- field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
@@ -34440,39 +34439,6 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment);
- method public final void clearAnnotation(java.lang.String);
- method public void onNotificationActionClick(java.lang.String, long, int);
- method public void onNotificationClick(java.lang.String, long);
- method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
- method public void onNotificationRemoved(java.lang.String, long, int);
- method public void onNotificationVisibilityChanged(java.lang.String, long, boolean);
- method public final void setAnnotation(java.lang.String, android.app.Notification);
- field public static final int REASON_APP_CANCEL = 8; // 0x8
- field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
- field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
- field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
- field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
- field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
- field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
- field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
- field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
- field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
- field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
- field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
- field public static final int REASON_PACKAGE_SUSPENDED = 15; // 0xf
- field public static final int REASON_PROFILE_TURNED_OFF = 16; // 0x10
- field public static final int REASON_TOPIC_BANNED = 14; // 0xe
- field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- }
-
- public class NotificationAssistantService.Adjustment {
- ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri);
- }
-
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 1ab55dd..980329f 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1030,6 +1030,20 @@
}
}
+ /**
+ * @hide
+ * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
+ * if defined (i.e. sequential or together), then we can use the flag instead of calculate
+ * dynamically.
+ * @return whether all the animators in the set are supposed to play together
+ */
+ public boolean shouldPlayTogether() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ // All the child nodes are set out to play right after the delay animation
+ return mRootNode.mChildNodes.size() == mNodes.size() - 1;
+ }
+
@Override
public long getTotalDuration() {
updateAnimatorsDuration();
diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java
index 2a47b68..8230ac5 100644
--- a/core/java/android/animation/PathKeyframes.java
+++ b/core/java/android/animation/PathKeyframes.java
@@ -231,7 +231,7 @@
}
}
- private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
+ abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
@Override
public Class getType() {
return Integer.class;
@@ -243,7 +243,7 @@
}
}
- private abstract static class FloatKeyframesBase extends SimpleKeyframes
+ abstract static class FloatKeyframesBase extends SimpleKeyframes
implements FloatKeyframes {
@Override
public Class getType() {
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index e993cca..6ba5b96 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -21,6 +21,7 @@
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
+import android.util.PathParser;
import android.util.Property;
import java.lang.reflect.InvocationTargetException;
@@ -1046,6 +1047,43 @@
return mAnimatedValue;
}
+ /**
+ * PropertyValuesHolder is Animators use to hold internal animation related data.
+ * Therefore, in order to replicate the animation behavior, we need to get data out of
+ * PropertyValuesHolder.
+ * @hide
+ */
+ public void getPropertyValues(PropertyValues values) {
+ init();
+ values.propertyName = mPropertyName;
+ values.type = mValueType;
+ values.startValue = mKeyframes.getValue(0);
+ if (values.startValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue);
+ }
+ values.endValue = mKeyframes.getValue(1);
+ if (values.endValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue);
+ }
+ // TODO: We need a better way to get data out of keyframes.
+ if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
+ || mKeyframes instanceof PathKeyframes.IntKeyframesBase) {
+ // property values will animate based on external data source (e.g. Path)
+ values.dataSource = new PropertyValues.DataSource() {
+ @Override
+ public Object getValueAtFraction(float fraction) {
+ return mKeyframes.getValue(fraction);
+ }
+ };
+ } else {
+ values.dataSource = null;
+ }
+ }
+
@Override
public String toString() {
return mPropertyName + ": " + mKeyframes.toString();
@@ -1601,6 +1639,24 @@
}
};
+ /**
+ * @hide
+ */
+ public static class PropertyValues {
+ public String propertyName;
+ public Class type;
+ public Object startValue;
+ public Object endValue;
+ public DataSource dataSource = null;
+ public interface DataSource {
+ Object getValueAtFraction(float fraction);
+ }
+ public String toString() {
+ return ("property name: " + propertyName + ", type: " + type + ", startValue: "
+ + startValue.toString() + ", endValue: " + endValue.toString());
+ }
+ }
+
native static private long nGetIntMethod(Class targetClass, String methodName);
native static private long nGetFloatMethod(Class targetClass, String methodName);
native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 34c90c1..a78076b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3236,6 +3236,7 @@
return;
}
contentView.setTextViewText(R.id.app_name_text, appName);
+ contentView.setTextColor(R.id.app_name_text, resolveColor());
}
private void bindSmallIcon(RemoteViews contentView) {
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index a88aa3125..2937ccc 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -47,20 +47,6 @@
public long mLastTimeUsed;
/**
- * The last time the package was used via implicit, non-user initiated actions (service
- * was bound, etc).
- * {@hide}
- */
- public long mLastTimeSystemUsed;
-
- /**
- * Last time the package was used and the beginning of the idle countdown.
- * This uses a different timebase that is about how much the device has been in use in general.
- * {@hide}
- */
- public long mBeginIdleTime;
-
- /**
* {@hide}
*/
public long mTotalTimeInForeground;
@@ -89,8 +75,6 @@
mTotalTimeInForeground = stats.mTotalTimeInForeground;
mLaunchCount = stats.mLaunchCount;
mLastEvent = stats.mLastEvent;
- mBeginIdleTime = stats.mBeginIdleTime;
- mLastTimeSystemUsed = stats.mLastTimeSystemUsed;
}
public String getPackageName() {
@@ -127,25 +111,6 @@
}
/**
- * @hide
- * Get the last time this package was used by the system (not the user). This can be different
- * from {@link #getLastTimeUsed()} when the system binds to one of this package's services.
- * See {@link System#currentTimeMillis()}.
- */
- public long getLastTimeSystemUsed() {
- return mLastTimeSystemUsed;
- }
-
- /**
- * @hide
- * Get the last time this package was active, measured in milliseconds. This timestamp
- * uses a timebase that represents how much the device was used and not wallclock time.
- */
- public long getBeginIdleTime() {
- return mBeginIdleTime;
- }
-
- /**
* Get the total time this package spent in the foreground, measured in milliseconds.
*/
public long getTotalTimeInForeground() {
@@ -172,8 +137,6 @@
// regards to their mEndTimeStamp.
mLastEvent = right.mLastEvent;
mLastTimeUsed = right.mLastTimeUsed;
- mBeginIdleTime = right.mBeginIdleTime;
- mLastTimeSystemUsed = right.mLastTimeSystemUsed;
}
mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
@@ -195,8 +158,6 @@
dest.writeLong(mTotalTimeInForeground);
dest.writeInt(mLaunchCount);
dest.writeInt(mLastEvent);
- dest.writeLong(mBeginIdleTime);
- dest.writeLong(mLastTimeSystemUsed);
}
public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
@@ -210,8 +171,6 @@
stats.mTotalTimeInForeground = in.readLong();
stats.mLaunchCount = in.readInt();
stats.mLastEvent = in.readInt();
- stats.mBeginIdleTime = in.readLong();
- stats.mLastTimeSystemUsed = in.readLong();
return stats;
}
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 4135487..9221fbb 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -148,8 +148,7 @@
return null;
}
- if ("com.google.android.gms".equals(mPackageName)
- || "com.google.android.syncadapters.contacts".equals(mPackageName)) {
+ if ("com.google.android.gms".equals(mPackageName)) {
// They're casting to a concrete subclass, sigh
return cursor;
} else {
diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java
index b3e3ab6..4487121 100644
--- a/core/java/android/nfc/tech/NfcF.java
+++ b/core/java/android/nfc/tech/NfcF.java
@@ -98,8 +98,13 @@
/**
* Send raw NFC-F commands to the tag and receive the response.
*
- * <p>Applications must not append the SoD (length) or EoD (CRC) to the payload,
- * it will be automatically calculated.
+ * <p>Applications must not prefix the SoD (preamble and sync code)
+ * and/or append the EoD (CRC) to the payload, it will be automatically calculated.
+ *
+ * <p>A typical NFC-F frame for this method looks like:
+ * <pre>
+ * LENGTH (1 byte) --- CMD (1 byte) -- IDm (8 bytes) -- PARAMS (LENGTH - 10 bytes)
+ * </pre>
*
* <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum amount of bytes
* that can be sent with {@link #transceive}.
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 344d06e..24666fe 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -74,6 +74,9 @@
/** @hide A user id constant to indicate the "system" user of the device */
public static final @UserIdInt int USER_SYSTEM = 0;
+ /** @hide A user serial constant to indicate the "system" user of the device */
+ public static final int USER_SERIAL_SYSTEM = 0;
+
/** @hide A user handle to indicate the "system" user of the device */
public static final UserHandle SYSTEM = new UserHandle(USER_SYSTEM);
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 9b3f02d..dd8eb5f 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1284,7 +1284,7 @@
@Override
public void prepareUserStorage(
- String volumeUuid, int userId, int serialNumber, boolean ephemeral)
+ String volumeUuid, int userId, int serialNumber, int flags)
throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
@@ -1293,7 +1293,7 @@
_data.writeString(volumeUuid);
_data.writeInt(userId);
_data.writeInt(serialNumber);
- _data.writeInt(ephemeral ? 1 : 0);
+ _data.writeInt(flags);
mRemote.transact(Stub.TRANSACTION_prepareUserStorage, _data, _reply, 0);
_reply.readException();
} finally {
@@ -2055,8 +2055,8 @@
String volumeUuid = data.readString();
int userId = data.readInt();
int serialNumber = data.readInt();
- boolean ephemeral = data.readInt() != 0;
- prepareUserStorage(volumeUuid, userId, serialNumber, ephemeral);
+ int _flags = data.readInt();
+ prepareUserStorage(volumeUuid, userId, serialNumber, _flags);
reply.writeNoException();
return true;
}
@@ -2389,7 +2389,7 @@
public boolean isUserKeyUnlocked(int userId) throws RemoteException;
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber,
- boolean ephemeral) throws RemoteException;
+ int flags) throws RemoteException;
public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index c8b942b..b82638a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -92,8 +92,14 @@
/** {@hide} */
public static final int DEBUG_EMULATE_FBE = 1 << 1;
+ // NOTE: keep in sync with installd
/** {@hide} */
- public static final int FLAG_FOR_WRITE = 1 << 0;
+ public static final int FLAG_STORAGE_DE = 1 << 0;
+ /** {@hide} */
+ public static final int FLAG_STORAGE_CE = 1 << 1;
+
+ /** {@hide} */
+ public static final int FLAG_FOR_WRITE = 1 << 8;
private final Context mContext;
private final ContentResolver mResolver;
@@ -1003,10 +1009,9 @@
}
/** {@hide} */
- public void prepareUserStorage(
- String volumeUuid, int userId, int serialNumber, boolean ephemeral) {
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
try {
- mMountService.prepareUserStorage(volumeUuid, userId, serialNumber, ephemeral);
+ mMountService.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 7a9d062..ea54f92 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -20,35 +20,126 @@
import android.os.Bundle;
/**
- * Constants and methods to access blocked phone numbers for incoming calls and texts.
+ * <p>
+ * The contract between the blockednumber provider and applications. Contains definitions for
+ * the supported URIs and columns.
+ * </p>
*
- * TODO javadoc
- * - Proper javadoc tagging.
- * - Code sample?
- * - Describe who can access it.
+ * <h3> Overview </h3>
+ * <p>
+ * The content provider exposes a table containing blocked numbers. The columns and URIs for
+ * accessing this table are defined by the {@link BlockedNumbers} class. Messages, and calls from
+ * blocked numbers are discarded by the platform. Notifications upon provider changes can be
+ * received using a {@link android.database.ContentObserver}.
+ * </p>
+ *
+ * <h3> Permissions </h3>
+ * <p>
+ * Only the system, the default SMS application, and the default phone app
+ * (See {@link android.telecom.TelecomManager#getDefaultDialerPackage()}), and carrier apps
+ * (See {@link android.service.carrier.CarrierService}) can read, and write to the blockednumber
+ * provider.
+ * </p>
+ *
+ * <h3> Data </h3>
+ * <p>
+ * Other than regular phone numbers, the blocked number provider can also store addresses (such
+ * as email) from which a user can receive messages, and calls. The blocked numbers are stored
+ * in the {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column. A normalized version of phone
+ * numbers (if normalization is possible) is stored in {@link BlockedNumbers#COLUMN_E164_NUMBER}
+ * column. The platform blocks calls, and messages from an address if it is present in in the
+ * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or if the E164 version of the address
+ * matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
+ * </p>
+ *
+ * <h3> Operations </h3>
+ * <dl>
+ * <dt><b>Insert</b></dt>
+ * <dd>
+ * <p>
+ * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} is a required column that needs to be populated.
+ * Apps can optionally provide the {@link BlockedNumbers#COLUMN_E164_NUMBER} which is the phone
+ * number's E164 representation. The provider automatically populates this column if the app does
+ * not provide it. Note that this column is not populated if normalization fails or if the address
+ * is not a phone number (eg: email). The provider enforces uniqueness constraint on this column.
+ * Examples:
+ * <pre>
+ * ContentValues values = new ContentValues();
+ * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
+ * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
+ * </pre>
+ * <pre>
+ * ContentValues values = new ContentValues();
+ * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
+ * values.put(BlockedNumbers.COLUMN_E164_NUMBER, "+11234567890");
+ * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
+ * </pre>
+ * <pre>
+ * ContentValues values = new ContentValues();
+ * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "12345@abdcde.com");
+ * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
+ * </pre>
+ * </p>
+ * </dd>
+ * <dt><b>Update</b></dt>
+ * <dd>
+ * <p>
+ * Updates are not supported. Use Delete, and Insert instead.
+ * </p>
+ * </dd>
+ * <dt><b>Delete</b></dt>
+ * <dd>
+ * <p>
+ * Deletions can be performed as follows:
+ * <pre>
+ * ContentValues values = new ContentValues();
+ * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
+ * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
+ * getContentResolver().delete(uri, null, null);
+ * </pre>
+ * </p>
+ * </dd>
+ * <dt><b>Query</b></dt>
+ * <dd>
+ * <p>
+ * All blocked numbers can be enumerated as follows:
+ * <pre>
+ * Cursor c = getContentResolver().query(BlockedNumbers.CONTENT_URI,
+ * new String[]{BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
+ * BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);
+ * </pre>
+ * To check if a particular number is blocked, use the method
+ * {@link #isBlocked(Context, String)}.
+ * </p>
+ * </dd>
+ *
+ * <h3> Multi-user </h3>
+ * <p>
+ * Apps must use the method {@link #canCurrentUserBlockNumbers(Context)} before performing any
+ * operation on the blocked number provider. If {@link #canCurrentUserBlockNumbers(Context)} returns
+ * {@code false}, all operations on the provider will fail with an
+ * {@link UnsupportedOperationException}. The platform will block calls, and messages from numbers
+ * in the provider independent of the current user.
+ * </p>
*/
public class BlockedNumberContract {
private BlockedNumberContract() {
}
- /** The authority for the contacts provider */
+ /** The authority for the blocked number provider */
public static final String AUTHORITY = "com.android.blockednumber";
- /** A content:// style uri to the authority for the contacts provider */
+ /** A content:// style uri to the authority for the blocked number provider */
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
/**
- * TODO javadoc
- *
- * Constants to interact with the blocked phone number list.
+ * Constants to interact with the blocked numbers list.
*/
public static class BlockedNumbers {
private BlockedNumbers() {
}
/**
- * TODO javadoc
- *
* Content URI for the blocked numbers.
*
* Supported operations
@@ -117,8 +208,8 @@
* context {@code context}, this method will throw an {@link UnsupportedOperationException}.
*/
public static boolean isBlocked(Context context, String phoneNumber) {
- final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
- METHOD_IS_BLOCKED, phoneNumber, null);
+ final Bundle res = context.getContentResolver().call(
+ AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 330fcf6..dfdd36d 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2079,10 +2079,11 @@
if (preferHighres) {
final Uri displayPhotoUri = Uri.withAppendedPath(contactUri,
Contacts.Photo.DISPLAY_PHOTO);
- InputStream inputStream;
try {
AssetFileDescriptor fd = cr.openAssetFileDescriptor(displayPhotoUri, "r");
- return fd.createInputStream();
+ if (fd != null) {
+ return fd.createInputStream();
+ }
} catch (IOException e) {
// fallback to the thumbnail code
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index a56e030..fb58f4e 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -45,7 +45,9 @@
* <action android:name="android.service.notification.NotificationAssistantService" />
* </intent-filter>
* </service></pre>
+ * @hide
*/
+@SystemApi
public abstract class NotificationAssistantService extends NotificationListenerService {
private static final String TAG = "NotificationAssistant";
@@ -210,29 +212,6 @@
}
}
- /**
- * Add an annotation to a an existing notification. The delete intent will
- * be fired when the host notification is deleted, or when this annotation
- * is removed or replaced.
- *
- * @param key the key of the notification to be annotated
- * @param annotation the new annotation object
- */
- public final void setAnnotation(String key, Notification annotation)
- {
- // TODO: pack up the annotation and send it to the NotificationManager.
- }
-
- /**
- * Remove the annotation from a notification.
- *
- * @param key the key of the notification to be cleansed of annotatons
- */
- public final void clearAnnotation(String key)
- {
- // TODO: ask the NotificationManager to clear the annotation.
- }
-
private class NotificationAssistantWrapper extends NotificationListenerWrapper {
@Override
public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ed90e79..7ff883e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -695,7 +695,7 @@
/**
* Request that the listener be rebound, after a previous call to (@link requestUnbind).
*
- * <P>This method will fail for assistants that have
+ * <P>This method will fail for listeners that have
* not been granted the permission by the user.
*
* <P>The service should wait for the {@link #onListenerConnected()} event
@@ -1022,8 +1022,7 @@
}
/**
- * If the importance has been overriden by user preference, or by a
- * {@link NotificationAssistantService}, then this will be non-null,
+ * If the importance has been overriden by user preference, then this will be non-null,
* and should be displayed to the user.
*
* @return the explanation for the importance, or null if it is the natural importance
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 78d5bcd..29a72fd 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -104,6 +104,7 @@
}
super.finalize();
}
+
}
/**
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 3122c0b..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -22,6 +22,7 @@
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
/**
* <p>A display list records a series of graphics related operations and can replay
@@ -774,6 +775,14 @@
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
+ public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) {
+ if (mOwningView == null || mOwningView.mAttachInfo == null) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ nAddAnimator(mNativeRenderNode, animatorSet.getAnimatorNativePtr());
+ mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
+ }
+
public void endAllAnimators() {
nEndAllAnimators(mNativeRenderNode);
}
diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java
new file mode 100644
index 0000000..ba592d29
--- /dev/null
+++ b/core/java/android/view/RenderNodeAnimatorSetHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import android.animation.TimeInterpolator;
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet
+ * that are internal or package private to android.view package.
+ *
+ * @hide
+ */
+public class RenderNodeAnimatorSetHelper {
+
+ public static RenderNode getTarget(DisplayListCanvas recordingCanvas) {
+ return recordingCanvas.mNode;
+ }
+
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long
+ duration) {
+ if (interpolator == null) {
+ // create LinearInterpolator
+ return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator();
+ } else {
+ return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+ }
+ }
+
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ffa3fa6..1b6b53a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -51,6 +51,7 @@
android_database_SQLiteConnection.cpp \
android_database_SQLiteGlobal.cpp \
android_database_SQLiteDebug.cpp \
+ android_graphics_drawable_AnimatedVectorDrawable.cpp \
android_graphics_drawable_VectorDrawable.cpp \
android_view_DisplayEventReceiver.cpp \
android_view_DisplayListCanvas.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 2e45f8c..223fc1a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -131,6 +131,7 @@
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
extern int register_android_graphics_Xfermode(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
@@ -1321,6 +1322,7 @@
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
+ REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
REG_JNI(register_android_graphics_drawable_VectorDrawable),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
new file mode 100644
index 0000000..7a3c598
--- /dev/null
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "OpenGLRenderer"
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include "core_jni_helpers.h"
+#include "log/log.h"
+
+#include "Animator.h"
+#include "Interpolator.h"
+#include "PropertyValuesAnimatorSet.h"
+#include "PropertyValuesHolder.h"
+#include "VectorDrawable.h"
+
+namespace android {
+using namespace uirenderer;
+using namespace VectorDrawable;
+
+static struct {
+ jclass clazz;
+ jmethodID callOnFinished;
+} gVectorDrawableAnimatorClassInfo;
+
+static JNIEnv* getEnv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return 0;
+ }
+ return env;
+}
+
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
+ class AnimationListenerBridge : public AnimationListener {
+ public:
+ AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
+ mFinishListener = env->NewGlobalRef(finishListener);
+ env->GetJavaVM(&mJvm);
+ }
+
+ virtual ~AnimationListenerBridge() {
+ if (mFinishListener) {
+ onAnimationFinished(NULL);
+ }
+ }
+
+ virtual void onAnimationFinished(BaseRenderNodeAnimator*) {
+ LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?");
+ JNIEnv* env = getEnv(mJvm);
+ env->CallStaticVoidMethod(
+ gVectorDrawableAnimatorClassInfo.clazz,
+ gVectorDrawableAnimatorClassInfo.callOnFinished,
+ mFinishListener);
+ releaseJavaObject();
+ }
+
+ private:
+ void releaseJavaObject() {
+ JNIEnv* env = getEnv(mJvm);
+ env->DeleteGlobalRef(mFinishListener);
+ mFinishListener = NULL;
+ }
+
+ JavaVM* mJvm;
+ jobject mFinishListener;
+ };
+ return new AnimationListenerBridge(env, finishListener);
+}
+
+static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
+ jlong interpolatorPtr, jlong startDelay, jlong duration, jint repeatCount) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr);
+ Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr);
+ set->addPropertyAnimator(holder, interpolator, startDelay, duration, repeatCount);
+}
+
+static jlong createAnimatorSet(JNIEnv*, jobject) {
+ PropertyValuesAnimatorSet* animatorSet = new PropertyValuesAnimatorSet();
+ return reinterpret_cast<jlong>(animatorSet);
+}
+
+static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ jfloat startValue, jfloat endValue) {
+ VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr);
+ GroupPropertyValuesHolder* newHolder = new GroupPropertyValuesHolder(group, propertyId,
+ startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathDataPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jlong startValuePtr,
+ jlong endValuePtr) {
+ VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(nativePtr);
+ PathData* startData = reinterpret_cast<PathData*>(startValuePtr);
+ PathData* endData = reinterpret_cast<PathData*>(endValuePtr);
+ PathDataPropertyValuesHolder* newHolder = new PathDataPropertyValuesHolder(path,
+ startData, endData);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathColorPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ int startValue, jint endValue) {
+ VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr);
+ FullPathColorPropertyValuesHolder* newHolder = new FullPathColorPropertyValuesHolder(fullPath,
+ propertyId, startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ float startValue, jfloat endValue) {
+ VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr);
+ FullPathPropertyValuesHolder* newHolder = new FullPathPropertyValuesHolder(fullPath,
+ propertyId, startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createRootAlphaPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jfloat startValue,
+ float endValue) {
+ VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(nativePtr);
+ RootAlphaPropertyValuesHolder* newHolder = new RootAlphaPropertyValuesHolder(tree,
+ startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+static void setPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr,
+ jfloatArray srcData, jint length) {
+
+ jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr);
+ PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr);
+ holder->setPropertyDataSource(propertyData, length);
+ env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
+}
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ // TODO: keep a ref count in finish listener
+ AnimationListener* listener = createAnimationListener(env, finishListener);
+ set->start(listener);
+}
+
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+ // TODO: implement reverse
+}
+
+static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ set->end();
+}
+
+static void reset(JNIEnv*, jobject, jlong animatorSetPtr) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ set->reset();
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet},
+ {"nAddAnimator", "(JJJJJI)V", (void*)addAnimator},
+ {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder},
+ {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder},
+ {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder},
+ {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
+ {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
+ {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
+ {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+ {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
+ {"nEnd", "!(J)V", (void*)end},
+ {"nReset", "!(J)V", (void*)reset},
+};
+
+const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator";
+int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env) {
+ gVectorDrawableAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName);
+ gVectorDrawableAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env,
+ gVectorDrawableAnimatorClassInfo.clazz);
+
+ gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
+ env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
+ "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
+ return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp
index 563ec8b..7314fbc 100644
--- a/core/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -32,11 +32,6 @@
return reinterpret_cast<jlong>(tree);
}
-static void deleteTree(JNIEnv*, jobject, jlong treePtr) {
- VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
- delete tree;
-}
-
static void setTreeViewportSize(JNIEnv*, jobject, jlong treePtr,
jfloat viewportWidth, jfloat viewportHeight) {
VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
@@ -143,11 +138,6 @@
return reinterpret_cast<jlong>(newGroup);
}
-static void deleteNode(JNIEnv*, jobject, jlong nodePtr) {
- VectorDrawable::Node* node = reinterpret_cast<VectorDrawable::Node*>(nodePtr);
- delete node;
-}
-
static void setNodeName(JNIEnv* env, jobject, jlong nodePtr, jstring nameStr) {
VectorDrawable::Node* node = reinterpret_cast<VectorDrawable::Node*>(nodePtr);
const char* nodeName = env->GetStringUTFChars(nameStr, NULL);
@@ -333,7 +323,6 @@
static const JNINativeMethod gMethods[] = {
{"nCreateRenderer", "!(J)J", (void*)createTree},
- {"nDestroyRenderer", "!(J)V", (void*)deleteTree},
{"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize},
{"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha},
{"nGetRootAlpha", "!(J)F", (void*)getRootAlpha},
@@ -352,7 +341,6 @@
{"nCreateClipPath", "!(J)J", (void*)createClipPath},
{"nCreateGroup", "!()J", (void*)createEmptyGroup},
{"nCreateGroup", "!(J)J", (void*)createGroup},
- {"nDestroy", "!(J)V", (void*)deleteNode},
{"nSetName", "(JLjava/lang/String;)V", (void*)setNodeName},
{"nUpdateGroupProperties", "!(JFFFFFFF)V", (void*)updateGroupProperties},
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 4842e1b..e39bb1c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -205,8 +205,8 @@
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
Sensor const* const* sensorList;
- size_t count = mgr->getSensorList(&sensorList);
- if (size_t(index) >= count) {
+ ssize_t count = mgr->getSensorList(&sensorList);
+ if (ssize_t(index) >= count) {
return false;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cc48902..4cddb6c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2739,6 +2739,7 @@
android.service.notification.NotificationAssistantService},
to ensure that only the system can bind to it.
<p>Protection level: signature
+ @hide This is not a third-party API (intended for system apps). -->
-->
<permission android:name="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
android:protectionLevel="signature" />
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index dd18544..7399fa9 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -48,8 +48,6 @@
<color name="primary_text_default_material_light">#de000000</color>
<!-- 54% black -->
<color name="secondary_text_default_material_light">#8a000000</color>
- <!-- 38% black -->
- <color name="tertiary_text_default_material_light">#61000000</color>
<!-- 100% white -->
<color name="primary_text_default_material_dark">#ffffffff</color>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 0a52f41..9efcfda 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -444,7 +444,7 @@
</style>
<style name="TextAppearance.Material.Notification.Info">
- <item name="textColor">@color/tertiary_text_default_material_light</item>
+ <item name="textColor">@color/secondary_text_default_material_light</item>
<item name="textSize">@dimen/notification_subtext_size</item>
</style>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 961d0eb..dc302c7 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -344,6 +344,9 @@
<family lang="und-Zsye">
<font weight="400" style="normal">NotoColorEmoji.ttf</font>
</family>
+ <family>
+ <font weight="400" style="normal">DroidSansFallback.ttf</font>
+ </family>
<!--
Tai Le and Mongolian are intentionally kept last, to make sure they don't override
the East Asian punctuation for Chinese.
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index c48c371..af8ccf5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -19,8 +19,14 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.Animator.AnimatorListener;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ObjectAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.Application;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -31,17 +37,27 @@
import android.graphics.Outline;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.os.Build;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.LongArray;
+import android.util.PathParser;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.RenderNodeAnimatorSetHelper;
import android.view.View;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -138,7 +154,7 @@
private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
/** Local, mutable animator set. */
- private final AnimatorSet mAnimatorSet = new AnimatorSet();
+ private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator();
/**
* The resources against which this drawable was created. Used to attempt
@@ -187,6 +203,24 @@
mMutated = false;
}
+ /**
+ * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable
+ * animations * for apps targeting N and later. For older apps, we ignore (i.e. quietly skip)
+ * these animations.
+ *
+ * @return whether invalid animations for vector drawable should be ignored.
+ */
+ private static boolean shouldIgnoreInvalidAnimation() {
+ Application app = ActivityThread.currentApplication();
+ if (app == null || app.getApplicationInfo() == null) {
+ return true;
+ }
+ if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public ConstantState getConstantState() {
mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
@@ -200,6 +234,9 @@
@Override
public void draw(Canvas canvas) {
+ if (canvas.isHardwareAccelerated()) {
+ mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
+ }
mAnimatedVectorState.mVectorDrawable.draw(canvas);
if (isStarted()) {
invalidateSelf();
@@ -582,9 +619,8 @@
* Resets the AnimatedVectorDrawable to the start state as specified in the animators.
*/
public void reset() {
- // TODO: Use reverse or seek to implement reset, when AnimatorSet supports them.
- start();
- mAnimatorSet.cancel();
+ mAnimatorSet.reset();
+ invalidateSelf();
}
@Override
@@ -603,8 +639,12 @@
@NonNull
private void ensureAnimatorSet() {
if (!mHasAnimatorSet) {
- mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes);
+ // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
+ // with a list of LocalAnimators.
+ AnimatorSet set = new AnimatorSet();
+ mAnimatedVectorState.prepareLocalAnimators(set, mRes);
mHasAnimatorSet = true;
+ mAnimatorSet.initWithAnimatorSet(set);
mRes = null;
}
}
@@ -694,13 +734,13 @@
}
};
}
- mAnimatorSet.addListener(mAnimatorListener);
+ mAnimatorSet.setListener(mAnimatorListener);
}
// A helper function to clean up the animator listener in the mAnimatorSet.
private void removeAnimatorSetListener() {
if (mAnimatorListener != null) {
- mAnimatorSet.removeListener(mAnimatorListener);
+ mAnimatorSet.removeListener();
mAnimatorListener = null;
}
}
@@ -729,4 +769,427 @@
mAnimationCallbacks.clear();
}
+
+ /**
+ * @hide
+ */
+ public static class VectorDrawableAnimator {
+ private AnimatorListener mListener = null;
+ private final LongArray mStartDelays = new LongArray();
+ private PropertyValuesHolder.PropertyValues mTmpValues =
+ new PropertyValuesHolder.PropertyValues();
+ private long mSetPtr = 0;
+ private boolean mContainsSequentialAnimators = false;
+ private boolean mStarted = false;
+ private boolean mInitialized = false;
+ private boolean mAnimationPending = false;
+ private boolean mIsReversible = false;
+ // This needs to be set before parsing starts.
+ private boolean mShouldIgnoreInvalidAnim;
+ // TODO: Consider using NativeAllocationRegistery to track native allocation
+ private final VirtualRefBasePtr mSetRefBasePtr;
+ private WeakReference<RenderNode> mTarget = null;
+ private WeakReference<RenderNode> mLastSeenTarget = null;
+
+
+ VectorDrawableAnimator() {
+ mSetPtr = nCreateAnimatorSet();
+ // Increment ref count on native AnimatorSet, so it doesn't get released before Java
+ // side is done using it.
+ mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
+ }
+
+ private void initWithAnimatorSet(AnimatorSet set) {
+ if (mInitialized) {
+ // Already initialized
+ throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
+ "re-initialized");
+ }
+ mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
+ parseAnimatorSet(set, 0);
+ mInitialized = true;
+
+ // Check reversible.
+ if (mContainsSequentialAnimators) {
+ mIsReversible = false;
+ } else {
+ // Check if there's any start delay set on child
+ for (int i = 0; i < mStartDelays.size(); i++) {
+ if (mStartDelays.get(i) > 0) {
+ mIsReversible = false;
+ return;
+ }
+ }
+ }
+ mIsReversible = true;
+ }
+
+ private void parseAnimatorSet(AnimatorSet set, long startTime) {
+ ArrayList<Animator> animators = set.getChildAnimations();
+
+ boolean playTogether = set.shouldPlayTogether();
+ // Convert AnimatorSet to VectorDrawableAnimator
+ for (int i = 0; i < animators.size(); i++) {
+ Animator animator = animators.get(i);
+ // Here we only support ObjectAnimator
+ if (animator instanceof AnimatorSet) {
+ parseAnimatorSet((AnimatorSet) animator, startTime);
+ } else if (animator instanceof ObjectAnimator) {
+ createRTAnimator((ObjectAnimator) animator, startTime);
+ } // ignore ValueAnimators and others because they don't directly modify VD
+ // therefore will be useless to AVD.
+
+ if (!playTogether) {
+ // Assume not play together means play sequentially
+ startTime += animator.getTotalDuration();
+ mContainsSequentialAnimators = true;
+ }
+ }
+ }
+
+ // TODO: This method reads animation data from already parsed Animators. We need to move
+ // this step further up the chain in the parser to avoid the detour.
+ private void createRTAnimator(ObjectAnimator animator, long startTime) {
+ PropertyValuesHolder[] values = animator.getValues();
+ Object target = animator.getTarget();
+ if (target instanceof VectorDrawable.VGroup) {
+ createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
+ startTime);
+ } else if (target instanceof VectorDrawable.VPath) {
+ for (int i = 0; i < values.length; i++) {
+ values[i].getPropertyValues(mTmpValues);
+ if (mTmpValues.endValue instanceof PathParser.PathData &&
+ mTmpValues.propertyName.equals("pathData")) {
+ createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
+ startTime);
+ } else if (target instanceof VectorDrawable.VFullPath) {
+ createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
+ startTime);
+ } else if (!mShouldIgnoreInvalidAnim) {
+ throw new IllegalArgumentException("ClipPath only supports PathData " +
+ "property");
+ }
+
+ }
+ } else if (target instanceof VectorDrawable.VectorDrawableState) {
+ createRTAnimatorForRootGroup(values, animator,
+ (VectorDrawable.VectorDrawableState) target, startTime);
+ } else if (!mShouldIgnoreInvalidAnim) {
+ // Should never get here
+ throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
+ "or ConstantState, " + target == null ? "Null target" : target.getClass() +
+ " is not supported");
+ }
+ }
+
+ private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
+ ObjectAnimator animator, VectorDrawable.VGroup target,
+ long startTime) {
+
+ long nativePtr = target.getNativePtr();
+ int propertyId;
+ for (int i = 0; i < values.length; i++) {
+ // TODO: We need to support the rare case in AVD where no start value is provided
+ values[i].getPropertyValues(mTmpValues);
+ propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
+ if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.e(LOGTAG, "Unsupported type: " +
+ mTmpValues.type + ". Only float value is supported for Groups.");
+ }
+ continue;
+ }
+ if (propertyId < 0) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.e(LOGTAG, "Unsupported property: " +
+ mTmpValues.propertyName + " for Vector Drawable Group");
+ }
+ continue;
+ }
+ long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
+ (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+ if (mTmpValues.dataSource != null) {
+ float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
+ .getDuration());
+ nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+ }
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+ }
+ private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
+ long startTime) {
+
+ long nativePtr = target.getNativePtr();
+ long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
+ .getNativePtr();
+ long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
+ .getNativePtr();
+ long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
+ endPathDataPtr);
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ private void createRTAnimatorForFullPath(ObjectAnimator animator,
+ VectorDrawable.VFullPath target, long startTime) {
+
+ int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
+ long propertyPtr;
+ long nativePtr = target.getNativePtr();
+ if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
+ if (propertyId < 0) {
+ if (mShouldIgnoreInvalidAnim) {
+ return;
+ } else {
+ throw new IllegalArgumentException("Property: " + mTmpValues.propertyName
+ + " is not supported for FullPath");
+ }
+ }
+ propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
+ (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+
+ } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
+ propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
+ (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
+ } else {
+ if (mShouldIgnoreInvalidAnim) {
+ return;
+ } else {
+ throw new UnsupportedOperationException("Unsupported type: " +
+ mTmpValues.type + ". Only float, int or PathData value is " +
+ "supported for Paths.");
+ }
+ }
+ if (mTmpValues.dataSource != null) {
+ float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
+ .getDuration());
+ nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+ }
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
+ ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
+ long startTime) {
+ long nativePtr = target.getNativeRenderer();
+ if (!animator.getPropertyName().equals("alpha")) {
+ if (mShouldIgnoreInvalidAnim) {
+ return;
+ } else {
+ throw new UnsupportedOperationException("Only alpha is supported for root "
+ + "group");
+ }
+ }
+ Float startValue = null;
+ Float endValue = null;
+ for (int i = 0; i < values.length; i++) {
+ values[i].getPropertyValues(mTmpValues);
+ if (mTmpValues.propertyName.equals("alpha")) {
+ startValue = (Float) mTmpValues.startValue;
+ endValue = (Float) mTmpValues.endValue;
+ break;
+ }
+ }
+ if (startValue == null && endValue == null) {
+ if (mShouldIgnoreInvalidAnim) {
+ return;
+ } else {
+ throw new UnsupportedOperationException("No alpha values are specified");
+ }
+ }
+ long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ // These are the data points that define the value of the animating properties.
+ // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
+ // a point on the path corresponds to the values of translateX and translateY.
+ // TODO: (Optimization) We should pass the path down in native and chop it into segments
+ // in native.
+ private static float[] createDataPoints(
+ PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
+ long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+ int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+ int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+ float values[] = new float[numAnimFrames];
+ float lastFrame = numAnimFrames - 1;
+ for (int i = 0; i < numAnimFrames; i++) {
+ float fraction = i / lastFrame;
+ values[i] = (Float) dataSource.getValueAtFraction(fraction);
+ }
+ return values;
+ }
+
+ private void createNativeChildAnimator(long propertyPtr, long extraDelay,
+ ObjectAnimator animator) {
+ long duration = animator.getDuration();
+ int repeatCount = animator.getRepeatCount();
+ long startDelay = extraDelay + animator.getStartDelay();
+ TimeInterpolator interpolator = animator.getInterpolator();
+ long nativeInterpolator =
+ RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+
+ startDelay *= ValueAnimator.getDurationScale();
+ duration *= ValueAnimator.getDurationScale();
+
+ mStartDelays.add(startDelay);
+ nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
+ repeatCount);
+ }
+
+ /**
+ * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
+ * in the last draw call), so that when animator set needs to start, we can add the animator
+ * to the last seen RenderNode target and start right away.
+ */
+ protected void recordLastSeenTarget(DisplayListCanvas canvas) {
+ if (mAnimationPending) {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Target is set in the next frame");
+ }
+ mAnimationPending = false;
+ start();
+ } else {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
+ }
+
+ }
+
+ private boolean setTarget(RenderNode node) {
+ if (mTarget != null && mTarget.get() != null) {
+ // TODO: Maybe we want to support target change.
+ throw new IllegalStateException("Target already set!");
+ }
+
+ node.addAnimator(this);
+ mTarget = new WeakReference<RenderNode>(node);
+ return true;
+ }
+
+ private boolean useLastSeenTarget() {
+ if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
+ setTarget(mLastSeenTarget.get());
+ return true;
+ }
+ return false;
+ }
+
+ public void start() {
+ if (!mInitialized) {
+ return;
+ }
+
+ if (mStarted) {
+ return;
+ }
+
+ if (!useLastSeenTarget()) {
+ mAnimationPending = true;
+ return;
+ }
+
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
+ }
+
+ nStart(mSetPtr, this);
+ if (mListener != null) {
+ mListener.onAnimationStart(null);
+ }
+ mStarted = true;
+ }
+
+ public void end() {
+ if (mInitialized && mStarted) {
+ nEnd(mSetPtr);
+ onAnimationEnd();
+ }
+ }
+
+ void reset() {
+ if (!mInitialized) {
+ return;
+ }
+ // TODO: Need to implement reset.
+ Log.w(LOGTAG, "Reset is yet to be implemented");
+ nReset(mSetPtr);
+ }
+
+ // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
+ // animators or when the animator set has a start delay
+ void reverse() {
+ if (!mIsReversible) {
+ return;
+ }
+ // TODO: Need to support reverse (non-public API)
+ Log.w(LOGTAG, "Reverse is yet to be implemented");
+ nReverse(mSetPtr, this);
+ }
+
+ public long getAnimatorNativePtr() {
+ return mSetPtr;
+ }
+
+ boolean canReverse() {
+ return mIsReversible;
+ }
+
+ boolean isStarted() {
+ return mStarted;
+ }
+
+ boolean isRunning() {
+ if (!mInitialized) {
+ return false;
+ }
+ return mStarted;
+ }
+
+ void setListener(AnimatorListener listener) {
+ mListener = listener;
+ }
+
+ void removeListener() {
+ mListener = null;
+ }
+
+ private void onAnimationEnd() {
+ mStarted = false;
+ if (mListener != null) {
+ mListener.onAnimationEnd(null);
+ }
+ mTarget = null;
+ }
+
+ // onFinished: should be called from native
+ private static void callOnFinished(VectorDrawableAnimator set) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "on finished called from native");
+ }
+ set.onAnimationEnd();
+ }
+ }
+
+ private static native long nCreateAnimatorSet();
+ private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
+ long nativeInterpolator, long startDelay, long duration, int repeatCount);
+
+ private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+
+ private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+ long endValuePtr);
+ private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+ int startValue, int endValue);
+ private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+ private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+ float endValue);
+ private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
+ private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+ private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
+ private static native void nEnd(long animatorSetPtr);
+ private static native void nReset(long animatorSetPtr);
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 1fc1b83..bdbf3c0 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -39,6 +39,7 @@
import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -47,6 +48,7 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Stack;
/**
@@ -522,13 +524,13 @@
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererPtr != 0) {
+ if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) {
// This VD has been used to display other VD resource content, clean up.
mVectorState.mRootGroup = new VGroup();
- if (mVectorState.mNativeRendererPtr != 0) {
- nDestroyRenderer(mVectorState.mNativeRendererPtr);
+ if (mVectorState.mNativeRendererRefBase != null) {
+ mVectorState.mNativeRendererRefBase.release();
}
- mVectorState.mNativeRendererPtr = nCreateRenderer(mVectorState.mRootGroup.mNativePtr);
+ mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr);
}
final VectorDrawableState state = mVectorState;
state.setDensity(Drawable.resolveDensity(r, 0));
@@ -707,7 +709,7 @@
return mVectorState.mAutoMirrored;
}
- private static class VectorDrawableState extends ConstantState {
+ static class VectorDrawableState extends ConstantState {
// Variables below need to be copied (deep copy if applicable) for mutation.
int[] mThemeAttrs;
int mChangingConfigurations;
@@ -722,7 +724,7 @@
Insets mOpticalInsets = Insets.NONE;
String mRootName = null;
VGroup mRootGroup;
- long mNativeRendererPtr;
+ VirtualRefBasePtr mNativeRendererRefBase = null;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
@@ -743,7 +745,7 @@
mTintMode = copy.mTintMode;
mAutoMirrored = copy.mAutoMirrored;
mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
- mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
+ createNativeRenderer(mRootGroup.mNativePtr);
mBaseWidth = copy.mBaseWidth;
mBaseHeight = copy.mBaseHeight;
@@ -758,18 +760,15 @@
}
}
- @Override
- public void finalize() throws Throwable {
- if (mNativeRendererPtr != 0) {
- nDestroyRenderer(mNativeRendererPtr);
- mNativeRendererPtr = 0;
- }
- super.finalize();
+ private void createNativeRenderer(long rootGroupPtr) {
+ mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr));
}
-
long getNativeRenderer() {
- return mNativeRendererPtr;
+ if (mNativeRendererRefBase == null) {
+ return 0;
+ }
+ return mNativeRendererRefBase.get();
}
public boolean canReuseCache() {
@@ -808,7 +807,7 @@
public VectorDrawableState() {
mRootGroup = new VGroup();
- mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
+ createNativeRenderer(mRootGroup.mNativePtr);
}
@Override
@@ -872,16 +871,16 @@
* has changed.
*/
public boolean setAlpha(float alpha) {
- return nSetRootAlpha(mNativeRendererPtr, alpha);
+ return nSetRootAlpha(mNativeRendererRefBase.get(), alpha);
}
@SuppressWarnings("unused")
public float getAlpha() {
- return nGetRootAlpha(mNativeRendererPtr);
+ return nGetRootAlpha(mNativeRendererRefBase.get());
}
}
- private static class VGroup implements VObject {
+ static class VGroup implements VObject {
private static final int ROTATE_INDEX = 0;
private static final int PIVOT_X_INDEX = 1;
private static final int PIVOT_Y_INDEX = 2;
@@ -891,6 +890,28 @@
private static final int TRANSLATE_Y_INDEX = 6;
private static final int TRANSFORM_PROPERTY_COUNT = 7;
+ private static final HashMap<String, Integer> sPropertyMap =
+ new HashMap<String, Integer>() {
+ {
+ put("translateX", TRANSLATE_X_INDEX);
+ put("translateY", TRANSLATE_Y_INDEX);
+ put("scaleX", SCALE_X_INDEX);
+ put("scaleY", SCALE_Y_INDEX);
+ put("pivotX", PIVOT_X_INDEX);
+ put("pivotY", PIVOT_Y_INDEX);
+ put("rotation", ROTATE_INDEX);
+ }
+ };
+
+ static int getPropertyIndex(String propertyName) {
+ if (sPropertyMap.containsKey(propertyName)) {
+ return sPropertyMap.get(propertyName);
+ } else {
+ // property not found
+ return -1;
+ }
+ }
+
// Temp array to store transform values obtained from native.
private float[] mTransform;
/////////////////////////////////////////////////////
@@ -903,8 +924,11 @@
private int mChangingConfigurations;
private int[] mThemeAttrs;
private String mGroupName = null;
- private long mNativePtr = 0;
+ // The native object will be created in the constructor and will be destroyed in native
+ // when the neither java nor native has ref to the tree. This pointer should be valid
+ // throughout this VGroup Java object's life.
+ private final long mNativePtr;
public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
mIsStateful = copy.mIsStateful;
@@ -1044,16 +1068,6 @@
}
@Override
- protected void finalize() throws Throwable {
- if (mNativePtr != 0) {
- nDestroy(mNativePtr);
- mNativePtr = 0;
- }
- super.finalize();
- }
-
-
- @Override
public void applyTheme(Theme t) {
if (mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(mThemeAttrs,
@@ -1149,7 +1163,7 @@
/**
* Common Path information for clip path and normal path.
*/
- private static abstract class VPath implements VObject {
+ static abstract class VPath implements VObject {
protected PathParser.PathData mPathData = null;
String mPathName;
@@ -1187,10 +1201,10 @@
* Clip path, which only has name and pathData.
*/
private static class VClipPath extends VPath {
- long mNativePtr = 0;
+ private final long mNativePtr;
+
public VClipPath() {
mNativePtr = nCreateClipPath();
- // Empty constructor.
}
public VClipPath(VClipPath copy) {
@@ -1204,14 +1218,6 @@
}
@Override
- protected void finalize() throws Throwable {
- if (mNativePtr != 0) {
- nDestroy(mNativePtr);
- mNativePtr = 0;
- }
- super.finalize();
- }
- @Override
public void inflate(Resources r, AttributeSet attrs, Theme theme) {
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.VectorDrawableClipPath);
@@ -1260,7 +1266,7 @@
/**
* Normal path, which contains all the fill / paint information.
*/
- private static class VFullPath extends VPath {
+ static class VFullPath extends VPath {
private static final int STROKE_WIDTH_INDEX = 0;
private static final int STROKE_COLOR_INDEX = 1;
private static final int STROKE_ALPHA_INDEX = 2;
@@ -1274,6 +1280,20 @@
private static final int STROKE_MITER_LIMIT_INDEX = 10;
private static final int TOTAL_PROPERTY_COUNT = 11;
+ private final static HashMap<String, Integer> sPropertyMap
+ = new HashMap<String, Integer> () {
+ {
+ put("strokeWidth", STROKE_WIDTH_INDEX);
+ put("strokeColor", STROKE_COLOR_INDEX);
+ put("strokeAlpha", STROKE_ALPHA_INDEX);
+ put("fillColor", FILL_COLOR_INDEX);
+ put("fillAlpha", FILL_ALPHA_INDEX);
+ put("trimPathStart", TRIM_PATH_START_INDEX);
+ put("trimPathEnd", TRIM_PATH_END_INDEX);
+ put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
+ }
+ };
+
// Temp array to store property data obtained from native getter.
private byte[] mPropertyData;
/////////////////////////////////////////////////////
@@ -1282,10 +1302,9 @@
ComplexColor mStrokeColors = null;
ComplexColor mFillColors = null;
- private long mNativePtr = 0;
+ private final long mNativePtr;
public VFullPath() {
- // Empty constructor.
mNativePtr = nCreateFullPath();
}
@@ -1297,6 +1316,14 @@
mFillColors = copy.mFillColors;
}
+ int getPropertyIndex(String propertyName) {
+ if (!sPropertyMap.containsKey(propertyName)) {
+ return -1;
+ } else {
+ return sPropertyMap.get(propertyName);
+ }
+ }
+
@Override
public boolean onStateChange(int[] stateSet) {
boolean changed = false;
@@ -1341,15 +1368,6 @@
a.recycle();
}
- @Override
- protected void finalize() throws Throwable {
- if (mNativePtr != 0) {
- nDestroy(mNativePtr);
- mNativePtr = 0;
- }
- super.finalize();
- }
-
private void updateStateFromTypedArray(TypedArray a) {
int byteCount = TOTAL_PROPERTY_COUNT * 4;
if (mPropertyData == null) {
@@ -1595,7 +1613,6 @@
}
private static native long nCreateRenderer(long rootGroupPtr);
- private static native void nDestroyRenderer(long rendererPtr);
private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
float viewportHeight);
private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
@@ -1605,7 +1622,7 @@
private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
private static native long nCreateFullPath();
- private static native long nCreateFullPath(long mNativeFullPathPtr);
+ private static native long nCreateFullPath(long nativeFullPathPtr);
private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
int length);
@@ -1621,7 +1638,6 @@
private static native long nCreateGroup();
private static native long nCreateGroup(long groupPtr);
- private static native void nDestroy(long nodePtr);
private static native void nSetName(long nodePtr, String name);
private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
int length);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 939b9f6..7b43947 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -78,6 +78,8 @@
Program.cpp \
ProgramCache.cpp \
Properties.cpp \
+ PropertyValuesHolder.cpp \
+ PropertyValuesAnimatorSet.cpp \
RenderBufferCache.cpp \
RenderNode.cpp \
RenderProperties.cpp \
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 5ca2a2f..7bd2b24 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -90,6 +90,9 @@
doSetStartValue(getValue(mTarget));
}
if (mStagingPlayState > mPlayState) {
+ if (mStagingPlayState == PlayState::Restarted) {
+ mStagingPlayState = PlayState::Running;
+ }
mPlayState = mStagingPlayState;
// Oh boy, we're starting! Man the battle stations!
if (mPlayState == PlayState::Running) {
@@ -131,6 +134,11 @@
return true;
}
+ // This should be set before setValue() so animators can query this time when setValue
+ // is called.
+ nsecs_t currentFrameTime = context.frameTimeMs();
+ onPlayTimeChanged(currentFrameTime - mStartTime);
+
// If BaseRenderNodeAnimator is handling the delay (not typical), then
// because the staging properties reflect the final value, we always need
// to call setValue even if the animation isn't yet running or is still
@@ -141,8 +149,9 @@
}
float fraction = 1.0f;
+
if (mPlayState == PlayState::Running && mDuration > 0) {
- fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration;
+ fraction = (float)(currentFrameTime - mStartTime) / mDuration;
}
if (fraction >= 1.0f) {
fraction = 1.0f;
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index aea95bf..2c9c9c3 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -59,7 +59,13 @@
mMayRunAsync = mayRunAsync;
}
bool mayRunAsync() { return mMayRunAsync; }
- ANDROID_API void start() { mStagingPlayState = PlayState::Running; onStagingPlayStateChanged(); }
+ ANDROID_API void start() {
+ if (mStagingPlayState == PlayState::NotStarted) {
+ mStagingPlayState = PlayState::Running;
+ } else {
+ mStagingPlayState = PlayState::Restarted;
+ }
+ onStagingPlayStateChanged(); }
ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
void attach(RenderNode* target);
@@ -77,10 +83,27 @@
void forceEndNow(AnimationContext& context);
protected:
+ // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
+ // thread and Render Thread animation state, respectively.
+ // From the UI thread, mStagingPlayState transition looks like
+ // NotStarted -> Running -> Finished
+ // ^ |
+ // | |
+ // Restarted <------
+ // Note: For mStagingState, the Finished state (optional) is only set when the animation is
+ // terminated by user.
+ //
+ // On Render Thread, mPlayState transition:
+ // NotStart -> Running -> Finished
+ // ^ |
+ // | |
+ // -------------
+
enum class PlayState {
NotStarted,
Running,
Finished,
+ Restarted,
};
BaseRenderNodeAnimator(float finalValue);
@@ -93,6 +116,7 @@
void callOnFinishedListener(AnimationContext& context);
virtual void onStagingPlayStateChanged() {}
+ virtual void onPlayTimeChanged(nsecs_t playTime) {}
RenderNode* mTarget;
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index dbd502d..27facdf 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -52,6 +52,13 @@
} // namespace SaveFlags
+namespace uirenderer {
+namespace VectorDrawable {
+class Tree;
+};
+};
+typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+
class ANDROID_API Canvas {
public:
virtual ~Canvas() {};
@@ -217,6 +224,11 @@
*/
virtual bool drawTextAbsolutePos() const = 0;
+ /**
+ * Draws a VectorDrawable onto the canvas.
+ */
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree);
+
protected:
void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
};
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 7eaa785..3db14b5 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -16,11 +16,12 @@
#include "DisplayListCanvas.h"
-#include "ResourceCache.h"
#include "DeferredDisplayList.h"
#include "DeferredLayerUpdater.h"
#include "DisplayListOp.h"
+#include "ResourceCache.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
#include "utils/PaintUtils.h"
#include <SkCamera.h>
@@ -412,6 +413,16 @@
addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint)));
}
+void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->ref(tree);
+ const SkBitmap& bitmap = tree->getBitmapUpdateIfDirty();
+ SkPaint* paint = tree->getPaint();
+ const SkRect bounds = tree->getBounds();
+ addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap),
+ 0, 0, bitmap.width(), bitmap.height(),
+ bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), refPaint(paint)));
+}
+
void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count,
const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) {
if (!glyphs || count <= 0) return;
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index ad93960..06e72a0 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -206,6 +206,8 @@
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
+
// Text
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
@@ -214,7 +216,6 @@
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
-
private:
CanvasState mState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index c8910cb..185acce 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -19,6 +19,7 @@
#include "Canvas.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
#include "renderstate/OffscreenBufferPool.h"
#include "utils/FatVector.h"
#include "utils/PaintUtils.h"
@@ -545,6 +546,18 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
+void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
+ const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
+ SkPaint* paint = op.vectorDrawable->getPaint();
+ const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds,
+ op.localMatrix,
+ op.localClip,
+ paint,
+ &bitmap,
+ Rect(bitmap.width(), bitmap.height()));
+ deferBitmapRectOp(*resolvedOp);
+}
+
void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) {
// allocate a temporary oval op (with mAllocator, so it persists until render), so the
// renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
new file mode 100644
index 0000000..eca1afcc
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "PropertyValuesAnimatorSet.h"
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount) {
+
+ PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder,
+ interpolator, startDelay, duration, repeatCount);
+ mAnimators.emplace_back(animator);
+ setListener(new PropertyAnimatorSetListener(this));
+}
+
+PropertyValuesAnimatorSet::PropertyValuesAnimatorSet()
+ : BaseRenderNodeAnimator(1.0f) {
+ setStartValue(0);
+ mLastFraction = 0.0f;
+ setInterpolator(new LinearInterpolator());
+}
+
+void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) {
+ if (mOneShotListener.get()) {
+ mOneShotListener->onAnimationFinished(animator);
+ mOneShotListener = nullptr;
+ }
+}
+
+float PropertyValuesAnimatorSet::getValue(RenderNode* target) const {
+ return mLastFraction;
+}
+
+void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) {
+ mLastFraction = value;
+}
+
+void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ mAnimators[i]->setCurrentPlayTime(playTime);
+ }
+}
+
+void PropertyValuesAnimatorSet::reset() {
+ // TODO: implement reset through adding a play state because we need to support reset() even
+ // during an animation run.
+}
+
+void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
+ init();
+ mOneShotListener = listener;
+ BaseRenderNodeAnimator::start();
+}
+
+void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
+// TODO: implement reverse
+}
+
+void PropertyValuesAnimatorSet::init() {
+ if (mInitialized) {
+ return;
+ }
+ nsecs_t maxDuration = 0;
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ if (maxDuration < mAnimators[i]->getTotalDuration()) {
+ maxDuration = mAnimators[i]->getTotalDuration();
+ }
+ }
+ mDuration = maxDuration;
+ mInitialized = true;
+}
+
+uint32_t PropertyValuesAnimatorSet::dirtyMask() {
+ return RenderNode::DISPLAY_LIST;
+}
+
+PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator,
+ nsecs_t startDelay, nsecs_t duration, int repeatCount)
+ : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay),
+ mDuration(duration) {
+ if (repeatCount < 0) {
+ mRepeatCount = UINT32_MAX;
+ } else {
+ mRepeatCount = repeatCount;
+ }
+ mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay;
+}
+
+void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
+ if (playTime >= mStartDelay && playTime < mTotalDuration) {
+ nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
+ mLatestFraction = currentIterationPlayTime / (float) mDuration;
+ } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
+ mLatestFraction = 1.0f;
+ } else {
+ return;
+ }
+
+ setFraction(mLatestFraction);
+}
+
+void PropertyAnimator::setFraction(float fraction) {
+ float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
+ mPropertyValuesHolder->setFraction(interpolatedFraction);
+}
+
+void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) {
+ mSet->onFinished(animator);
+}
+
+}
+}
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
new file mode 100644
index 0000000..4c7ce52
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "Animator.h"
+#include "PropertyValuesHolder.h"
+#include "Interpolator.h"
+
+namespace android {
+namespace uirenderer {
+
+class PropertyAnimator {
+public:
+ PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount);
+ void setCurrentPlayTime(nsecs_t playTime);
+ nsecs_t getTotalDuration() {
+ return mTotalDuration;
+ }
+ void setFraction(float fraction);
+
+private:
+ std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder;
+ std::unique_ptr<Interpolator> mInterpolator;
+ nsecs_t mStartDelay;
+ nsecs_t mDuration;
+ uint32_t mRepeatCount;
+ nsecs_t mTotalDuration;
+ float mLatestFraction = 0.0f;
+};
+
+class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator {
+public:
+ friend class PropertyAnimatorSetListener;
+ PropertyValuesAnimatorSet();
+
+ void start(AnimationListener* listener);
+ void reverse(AnimationListener* listener);
+ void reset();
+
+ void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolators, int64_t startDelays,
+ nsecs_t durations, int repeatCount);
+ virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const override;
+ virtual void setValue(RenderNode* target, float value) override;
+ virtual void onPlayTimeChanged(nsecs_t playTime) override;
+
+private:
+ void init();
+ void onFinished(BaseRenderNodeAnimator* animator);
+ // Listener set from outside
+ sp<AnimationListener> mOneShotListener;
+ std::vector< std::unique_ptr<PropertyAnimator> > mAnimators;
+ float mLastFraction = 0.0f;
+ bool mInitialized = false;
+};
+
+class PropertyAnimatorSetListener : public AnimationListener {
+public:
+ PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {}
+ virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override;
+
+private:
+ PropertyValuesAnimatorSet* mSet;
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
new file mode 100644
index 0000000..8f837f6
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "PropertyValuesHolder.h"
+
+#include "utils/VectorDrawableUtils.h"
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+using namespace VectorDrawable;
+
+float PropertyValuesHolder::getValueFromData(float fraction) {
+ if (mDataSource.size() == 0) {
+ LOG_ALWAYS_FATAL("No data source is defined");
+ return 0;
+ }
+ if (fraction <= 0.0f) {
+ return mDataSource.front();
+ }
+ if (fraction >= 1.0f) {
+ return mDataSource.back();
+ }
+
+ fraction *= mDataSource.size() - 1;
+ int lowIndex = floor(fraction);
+ fraction -= lowIndex;
+
+ float value = mDataSource[lowIndex] * (1.0f - fraction)
+ + mDataSource[lowIndex + 1] * fraction;
+ return value;
+}
+
+void GroupPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mGroup->setPropertyValue(mPropertyId, animatedValue);
+}
+
+inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
+ return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+// TODO: Add a test for this
+SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor,
+ float fraction) {
+ U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
+ U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
+ U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
+ U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
+ return SkColorSetARGB(alpha, red, green, blue);
+}
+
+void FullPathColorPropertyValuesHolder::setFraction(float fraction) {
+ SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction);
+ mFullPath->setColorPropertyValue(mPropertyId, animatedValue);
+}
+
+void FullPathPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mFullPath->setPropertyValue(mPropertyId, animatedValue);
+}
+
+void PathDataPropertyValuesHolder::setFraction(float fraction) {
+ VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction);
+ mPath->setPathData(mPathData);
+}
+
+void RootAlphaPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ mTree->setRootAlpha(animatedValue);
+}
+
+} // namepace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h
new file mode 100644
index 0000000..b905fae
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "VectorDrawable.h"
+
+#include <SkColor.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * PropertyValues holder contains data needed to change a property of a Vector Drawable object.
+ * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based
+ * on its start and end value, and set the new value on the VectorDrawble's corresponding property.
+ */
+class ANDROID_API PropertyValuesHolder {
+public:
+ virtual void setFraction(float fraction) = 0;
+ void setPropertyDataSource(float* dataSource, int length) {
+ mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length);
+ }
+ float getValueFromData(float fraction);
+ virtual ~PropertyValuesHolder() {}
+protected:
+ std::vector<float> mDataSource;
+};
+
+class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue,
+ float endValue)
+ : mGroup(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue){
+ }
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Group* mGroup;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue,
+ int32_t endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+ static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction);
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ int32_t mStartValue;
+ int32_t mEndValue;
+};
+
+class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue,
+ float endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue,
+ PathData* endValue)
+ : mPath(ptr)
+ , mStartValue(*startValue)
+ , mEndValue(*endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Path* mPath;
+ PathData mPathData;
+ PathData mStartValue;
+ PathData mEndValue;
+};
+
+class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue)
+ : mTree(tree)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {}
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Tree* mTree;
+ float mStartValue;
+ float mEndValue;
+};
+}
+}
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 593d690..bb26e2e 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
+#include "RecordedOp.h"
#include "font/FontUtil.h"
#include "Matrix.h"
#include "Rect.h"
@@ -39,6 +40,10 @@
class RenderNode;
struct Vertex;
+namespace VectorDrawable {
+class Tree;
+}
+
/**
* Authoritative op list, used for generating the op ID enum, ID based LUTS, and
* the functions to which they dispatch. Parameter macros are executed for each op,
@@ -75,6 +80,7 @@
PRE_RENDER_OP_FN(EndLayerOp) \
PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \
PRE_RENDER_OP_FN(EndUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(VectorDrawableOp) \
\
RENDER_ONLY_OP_FN(ShadowOp) \
RENDER_ONLY_OP_FN(LayerOp) \
@@ -325,6 +331,13 @@
const float* ry;
};
+struct VectorDrawableOp : RecordedOp {
+ VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS)
+ : SUPER_PAINTLESS(VectorDrawableOp)
+ , vectorDrawable(tree) {}
+ VectorDrawable::Tree* vectorDrawable;
+};
+
/**
* Real-time, dynamic-lit shadow.
*
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 2387962..16929b8 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -19,6 +19,7 @@
#include "DeferredLayerUpdater.h"
#include "RecordedOp.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
namespace android {
namespace uirenderer {
@@ -395,7 +396,6 @@
&x->value, &y->value, &radius->value));
}
-
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) OvalOp(
Rect(left, top, right, bottom),
@@ -422,6 +422,15 @@
refPaint(&paint), refPath(&path)));
}
+void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->ref(tree);
+ addOp(new (alloc()) VectorDrawableOp(
+ tree,
+ Rect(tree->getBounds()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip()));
+}
+
// Bitmap-based
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
save(SaveFlags::Matrix);
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 375760f..cc14e61 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -175,6 +175,8 @@
const uint16_t* indices, int indexCount, const SkPaint& paint) override
{ /* RecordingCanvas does not support drawVertices(); ignore */ }
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
+
// Bitmap-based
virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d320a41..bd4442d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -32,6 +32,8 @@
#include <SkTLazy.h>
#include <SkTemplates.h>
+#include "VectorDrawable.h"
+
#include <memory>
namespace android {
@@ -153,6 +155,7 @@
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return true; }
+ virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
@@ -742,6 +745,14 @@
NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr);
}
+void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
+ const SkBitmap& bitmap = vectorDrawable->getBitmapUpdateIfDirty();
+ SkRect bounds = vectorDrawable->getBounds();
+ drawBitmap(bitmap, 0, 0, bitmap.width(), bitmap.height(),
+ bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom,
+ vectorDrawable->getPaint());
+}
+
// ----------------------------------------------------------------------------
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 1cf15ac..2e3856f 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -138,18 +138,7 @@
}
FullPath::FullPath(const FullPath& path) : Path(path) {
- mStrokeWidth = path.mStrokeWidth;
- mStrokeColor = path.mStrokeColor;
- mStrokeAlpha = path.mStrokeAlpha;
- mFillColor = path.mFillColor;
- mFillAlpha = path.mFillAlpha;
- mTrimPathStart = path.mTrimPathStart;
- mTrimPathEnd = path.mTrimPathEnd;
- mTrimPathOffset = path.mTrimPathOffset;
- mStrokeMiterLimit = path.mStrokeMiterLimit;
- mStrokeLineCap = path.mStrokeLineCap;
- mStrokeLineJoin = path.mStrokeLineJoin;
-
+ mProperties = path.mProperties;
SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient);
SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient);
}
@@ -159,7 +148,7 @@
return mTrimmedSkPath;
}
Path::getUpdatedPath();
- if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) {
+ if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) {
applyTrim();
return mTrimmedSkPath;
} else {
@@ -170,14 +159,14 @@
void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) {
- mStrokeWidth = strokeWidth;
- mStrokeColor = strokeColor;
- mStrokeAlpha = strokeAlpha;
- mFillColor = fillColor;
- mFillAlpha = fillAlpha;
- mStrokeMiterLimit = strokeMiterLimit;
- mStrokeLineCap = SkPaint::Cap(strokeLineCap);
- mStrokeLineJoin = SkPaint::Join(strokeLineJoin);
+ mProperties.strokeWidth = strokeWidth;
+ mProperties.strokeColor = strokeColor;
+ mProperties.strokeAlpha = strokeAlpha;
+ mProperties.fillColor = fillColor;
+ mProperties.fillAlpha = fillAlpha;
+ mProperties.strokeMiterLimit = strokeMiterLimit;
+ mProperties.strokeLineCap = strokeLineCap;
+ mProperties.strokeLineJoin = strokeLineJoin;
// If any trim property changes, mark trim dirty and update the trim path
setTrimPathStart(trimPathStart);
@@ -195,12 +184,12 @@
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
if (mFillGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mFillAlpha));
+ mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha));
SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix);
mPaint.setShader(newShader);
needsFill = true;
- } else if (mFillColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mFillColor, mFillAlpha));
+ } else if (mProperties.fillColor != SK_ColorTRANSPARENT) {
+ mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha));
needsFill = true;
}
@@ -213,21 +202,21 @@
// Draw path's stroke, if stroke color or gradient is valid
bool needsStroke = false;
if (mStrokeGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mStrokeAlpha));
+ mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha));
SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix);
mPaint.setShader(newShader);
needsStroke = true;
- } else if (mStrokeColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha));
+ } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) {
+ mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha));
needsStroke = true;
}
if (needsStroke) {
mPaint.setStyle(SkPaint::Style::kStroke_Style);
mPaint.setAntiAlias(true);
- mPaint.setStrokeJoin(mStrokeLineJoin);
- mPaint.setStrokeCap(mStrokeLineCap);
- mPaint.setStrokeMiter(mStrokeMiterLimit);
- mPaint.setStrokeWidth(mStrokeWidth * strokeScale);
+ mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin));
+ mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap));
+ mPaint.setStrokeMiter(mProperties.strokeMiterLimit);
+ mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale);
outCanvas->drawPath(renderPath, mPaint);
}
}
@@ -236,14 +225,14 @@
* Applies trimming to the specified path.
*/
void FullPath::applyTrim() {
- if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) {
+ if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) {
// No trimming necessary.
return;
}
SkPathMeasure measure(mSkPath, false);
float len = SkScalarToFloat(measure.getLength());
- float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f);
- float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f);
+ float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f);
+ float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f);
mTrimmedSkPath.reset();
if (start > end) {
@@ -255,76 +244,69 @@
mTrimDirty = false;
}
-inline int putData(int8_t* outBytes, int startIndex, float value) {
- int size = sizeof(float);
- memcpy(&outBytes[startIndex], &value, size);
- return size;
-}
-
-inline int putData(int8_t* outBytes, int startIndex, int value) {
- int size = sizeof(int);
- memcpy(&outBytes[startIndex], &value, size);
- return size;
-}
-
-struct FullPathProperties {
- // TODO: Consider storing full path properties in this struct instead of the fields.
- float strokeWidth;
- SkColor strokeColor;
- float strokeAlpha;
- SkColor fillColor;
- float fillAlpha;
- float trimPathStart;
- float trimPathEnd;
- float trimPathOffset;
- int32_t strokeLineCap;
- int32_t strokeLineJoin;
- float strokeMiterLimit;
-};
-
-REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties);
+REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties);
static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
bool FullPath::getProperties(int8_t* outProperties, int length) {
- int propertyDataSize = sizeof(FullPathProperties);
+ int propertyDataSize = sizeof(Properties);
if (length != propertyDataSize) {
LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
propertyDataSize, length);
return false;
}
- // TODO: consider replacing the property fields with a FullPathProperties struct.
- FullPathProperties properties;
- properties.strokeWidth = mStrokeWidth;
- properties.strokeColor = mStrokeColor;
- properties.strokeAlpha = mStrokeAlpha;
- properties.fillColor = mFillColor;
- properties.fillAlpha = mFillAlpha;
- properties.trimPathStart = mTrimPathStart;
- properties.trimPathEnd = mTrimPathEnd;
- properties.trimPathOffset = mTrimPathOffset;
- properties.strokeLineCap = mStrokeLineCap;
- properties.strokeLineJoin = mStrokeLineJoin;
- properties.strokeMiterLimit = mStrokeMiterLimit;
-
- memcpy(outProperties, &properties, length);
+ Properties* out = reinterpret_cast<Properties*>(outProperties);
+ *out = mProperties;
return true;
}
+void FullPath::setColorPropertyValue(int propertyId, int32_t value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ if (currentProperty == Property::StrokeColor) {
+ mProperties.strokeColor = value;
+ } else if (currentProperty == Property::FillColor) {
+ mProperties.fillColor = value;
+ } else {
+ LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d",
+ propertyId);
+ }
+}
+
+void FullPath::setPropertyValue(int propertyId, float value) {
+ Property property = static_cast<Property>(propertyId);
+ switch (property) {
+ case Property::StrokeWidth:
+ setStrokeWidth(value);
+ break;
+ case Property::StrokeAlpha:
+ setStrokeAlpha(value);
+ break;
+ case Property::FillAlpha:
+ setFillAlpha(value);
+ break;
+ case Property::TrimPathStart:
+ setTrimPathStart(value);
+ break;
+ case Property::TrimPathEnd:
+ setTrimPathEnd(value);
+ break;
+ case Property::TrimPathOffset:
+ setTrimPathOffset(value);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId);
+ break;
+ }
+}
+
void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
float strokeScale, const SkMatrix& matrix){
outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
}
Group::Group(const Group& group) : Node(group) {
- mRotate = group.mRotate;
- mPivotX = group.mPivotX;
- mPivotY = group.mPivotY;
- mScaleX = group.mScaleX;
- mScaleY = group.mScaleY;
- mTranslateX = group.mTranslateX;
- mTranslateY = group.mTranslateY;
+ mProperties = group.mProperties;
}
void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
@@ -342,7 +324,7 @@
// Save the current clip information, which is local to this group.
outCanvas->save();
// Draw the group tree in the same order as the XML file.
- for (Node* child : mChildren) {
+ for (auto& child : mChildren) {
child->draw(outCanvas, stackedMatrix, scaleX, scaleY);
}
// Restore the previous clip information.
@@ -371,14 +353,15 @@
outMatrix->reset();
// TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
// translating to pivot for rotating and scaling, then translating back.
- outMatrix->postTranslate(-mPivotX, -mPivotY);
- outMatrix->postScale(mScaleX, mScaleY);
- outMatrix->postRotate(mRotate, 0, 0);
- outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+ outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY);
+ outMatrix->postScale(mProperties.scaleX, mProperties.scaleY);
+ outMatrix->postRotate(mProperties.rotate, 0, 0);
+ outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX,
+ mProperties.translateY + mProperties.pivotY);
}
void Group::addChild(Node* child) {
- mChildren.push_back(child);
+ mChildren.emplace_back(child);
}
bool Group::getProperties(float* outProperties, int length) {
@@ -388,38 +371,68 @@
propertyCount, length);
return false;
}
- for (int i = 0; i < propertyCount; i++) {
- Property currentProperty = static_cast<Property>(i);
- switch (currentProperty) {
- case Property::Rotate_Property:
- outProperties[i] = mRotate;
- break;
- case Property::PivotX_Property:
- outProperties[i] = mPivotX;
- break;
- case Property::PivotY_Property:
- outProperties[i] = mPivotY;
- break;
- case Property::ScaleX_Property:
- outProperties[i] = mScaleX;
- break;
- case Property::ScaleY_Property:
- outProperties[i] = mScaleY;
- break;
- case Property::TranslateX_Property:
- outProperties[i] = mTranslateX;
- break;
- case Property::TranslateY_Property:
- outProperties[i] = mTranslateY;
- break;
- default:
- LOG_ALWAYS_FATAL("Invalid input index: %d", i);
- return false;
- }
- }
+ Properties* out = reinterpret_cast<Properties*>(outProperties);
+ *out = mProperties;
return true;
}
+// TODO: Consider animating the properties as float pointers
+float Group::getPropertyValue(int propertyId) const {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::Rotate:
+ return mProperties.rotate;
+ case Property::PivotX:
+ return mProperties.pivotX;
+ case Property::PivotY:
+ return mProperties.pivotY;
+ case Property::ScaleX:
+ return mProperties.scaleX;
+ case Property::ScaleY:
+ return mProperties.scaleY;
+ case Property::TranslateX:
+ return mProperties.translateX;
+ case Property::TranslateY:
+ return mProperties.translateY;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ return 0;
+ }
+}
+
+void Group::setPropertyValue(int propertyId, float value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::Rotate:
+ mProperties.rotate = value;
+ break;
+ case Property::PivotX:
+ mProperties.pivotX = value;
+ break;
+ case Property::PivotY:
+ mProperties.pivotY = value;
+ break;
+ case Property::ScaleX:
+ mProperties.scaleX = value;
+ break;
+ case Property::ScaleY:
+ mProperties.scaleY = value;
+ break;
+ case Property::TranslateX:
+ mProperties.translateX = value;
+ break;
+ case Property::TranslateY:
+ mProperties.translateY = value;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ }
+}
+
+bool Group::isValidProperty(int propertyId) {
+ return propertyId >= 0 && propertyId < static_cast<int>(Property::Count);
+}
+
void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
// The imageView can scale the canvas in different ways, in order to
@@ -445,6 +458,8 @@
return;
}
+ mPaint.setColorFilter(colorFilter);
+
int saveCount = outCanvas->save(SaveFlags::MatrixClip);
outCanvas->translate(mBounds.fLeft, mBounds.fTop);
@@ -458,43 +473,33 @@
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
mBounds.offsetTo(0, 0);
-
createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
- if (!mAllowCaching) {
- updateCachedBitmap(scaledWidth, scaledHeight);
- } else {
- if (!canReuseCache || mCacheDirty) {
- updateCachedBitmap(scaledWidth, scaledHeight);
- }
- }
- drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds);
+
+ outCanvas->drawVectorDrawable(this);
outCanvas->restoreToCount(saveCount);
}
-void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
- const SkRect& originalBounds) {
+SkPaint* Tree::getPaint() {
SkPaint* paint;
- if (mRootAlpha == 1.0f && filter == NULL) {
+ if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) {
paint = NULL;
} else {
mPaint.setFilterQuality(kLow_SkFilterQuality);
mPaint.setAlpha(mRootAlpha * 255);
- mPaint.setColorFilter(filter);
paint = &mPaint;
}
- outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(),
- originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight,
- originalBounds.fBottom, paint);
+ return paint;
}
-void Tree::updateCachedBitmap(int width, int height) {
+const SkBitmap& Tree::getBitmapUpdateIfDirty() {
mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
SkCanvas outCanvas(mCachedBitmap);
- float scaleX = width / mViewportWidth;
- float scaleY = height / mViewportHeight;
+ float scaleX = (float) mCachedBitmap.width() / mViewportWidth;
+ float scaleY = (float) mCachedBitmap.height() / mViewportHeight;
mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY);
mCacheDirty = false;
+ return mCachedBitmap;
}
void Tree::createCachedBitmapIfNeeded(int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 09bdce5..36a8aeb 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -18,6 +18,7 @@
#define ANDROID_HWUI_VPATH_H
#include "Canvas.h"
+
#include <SkBitmap.h>
#include <SkColor.h>
#include <SkCanvas.h>
@@ -104,6 +105,21 @@
class ANDROID_API FullPath: public Path {
public:
+
+struct Properties {
+ float strokeWidth = 0;
+ SkColor strokeColor = SK_ColorTRANSPARENT;
+ float strokeAlpha = 1;
+ SkColor fillColor = SK_ColorTRANSPARENT;
+ float fillAlpha = 1;
+ float trimPathStart = 0;
+ float trimPathEnd = 1;
+ float trimPathOffset = 0;
+ int32_t strokeLineCap = SkPaint::Cap::kButt_Cap;
+ int32_t strokeLineJoin = SkPaint::Join::kMiter_Join;
+ float strokeMiterLimit = 4;
+};
+
FullPath(const FullPath& path); // for cloning
FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
FullPath() : Path() {}
@@ -118,55 +134,58 @@
float strokeAlpha, SkColor fillColor, float fillAlpha,
float trimPathStart, float trimPathEnd, float trimPathOffset,
float strokeMiterLimit, int strokeLineCap, int strokeLineJoin);
+ // TODO: Cleanup: Remove the setter and getters below, and their counterparts in java and JNI
float getStrokeWidth() {
- return mStrokeWidth;
+ return mProperties.strokeWidth;
}
void setStrokeWidth(float strokeWidth) {
- mStrokeWidth = strokeWidth;
+ mProperties.strokeWidth = strokeWidth;
}
SkColor getStrokeColor() {
- return mStrokeColor;
+ return mProperties.strokeColor;
}
void setStrokeColor(SkColor strokeColor) {
- mStrokeColor = strokeColor;
+ mProperties.strokeColor = strokeColor;
}
float getStrokeAlpha() {
- return mStrokeAlpha;
+ return mProperties.strokeAlpha;
}
void setStrokeAlpha(float strokeAlpha) {
- mStrokeAlpha = strokeAlpha;
+ mProperties.strokeAlpha = strokeAlpha;
}
SkColor getFillColor() {
- return mFillColor;
+ return mProperties.fillColor;
}
void setFillColor(SkColor fillColor) {
- mFillColor = fillColor;
+ mProperties.fillColor = fillColor;
}
float getFillAlpha() {
- return mFillAlpha;
+ return mProperties.fillAlpha;
}
void setFillAlpha(float fillAlpha) {
- mFillAlpha = fillAlpha;
+ mProperties.fillAlpha = fillAlpha;
}
float getTrimPathStart() {
- return mTrimPathStart;
+ return mProperties.trimPathStart;
}
void setTrimPathStart(float trimPathStart) {
- VD_SET_PROP_WITH_FLAG(mTrimPathStart, trimPathStart, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathStart, trimPathStart, mTrimDirty);
}
float getTrimPathEnd() {
- return mTrimPathEnd;
+ return mProperties.trimPathEnd;
}
void setTrimPathEnd(float trimPathEnd) {
- VD_SET_PROP_WITH_FLAG(mTrimPathEnd, trimPathEnd, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathEnd, trimPathEnd, mTrimDirty);
}
float getTrimPathOffset() {
- return mTrimPathOffset;
+ return mProperties.trimPathOffset;
}
void setTrimPathOffset(float trimPathOffset) {
- VD_SET_PROP_WITH_FLAG(mTrimPathOffset, trimPathOffset, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathOffset, trimPathOffset, mTrimDirty);
}
bool getProperties(int8_t* outProperties, int length);
+ void setColorPropertyValue(int propertyId, int32_t value);
+ void setPropertyValue(int propertyId, float value);
void setFillGradient(SkShader* fillGradient) {
SkRefCnt_SafeAssign(mFillGradient, fillGradient);
@@ -182,24 +201,28 @@
float strokeScale, const SkMatrix& matrix) override;
private:
+ enum class Property {
+ StrokeWidth = 0,
+ StrokeColor,
+ StrokeAlpha,
+ FillColor,
+ FillAlpha,
+ TrimPathStart,
+ TrimPathEnd,
+ TrimPathOffset,
+ StrokeLineCap,
+ StrokeLineJoin,
+ StrokeMiterLimit,
+ Count,
+ };
// Applies trimming to the specified path.
void applyTrim();
- float mStrokeWidth = 0;
- SkColor mStrokeColor = SK_ColorTRANSPARENT;
- float mStrokeAlpha = 1;
- SkColor mFillColor = SK_ColorTRANSPARENT;
- SkShader* mStrokeGradient = nullptr;
- SkShader* mFillGradient = nullptr;
- float mFillAlpha = 1;
- float mTrimPathStart = 0;
- float mTrimPathEnd = 1;
- float mTrimPathOffset = 0;
+ Properties mProperties;
bool mTrimDirty = true;
- SkPaint::Cap mStrokeLineCap = SkPaint::Cap::kButt_Cap;
- SkPaint::Join mStrokeLineJoin = SkPaint::Join::kMiter_Join;
- float mStrokeMiterLimit = 4;
SkPath mTrimmedSkPath;
SkPaint mPaint;
+ SkShader* mStrokeGradient = nullptr;
+ SkShader* mFillGradient = nullptr;
};
class ANDROID_API ClipPath: public Path {
@@ -216,49 +239,58 @@
class ANDROID_API Group: public Node {
public:
+ struct Properties {
+ float rotate = 0;
+ float pivotX = 0;
+ float pivotY = 0;
+ float scaleX = 1;
+ float scaleY = 1;
+ float translateX = 0;
+ float translateY = 0;
+ };
Group(const Group& group);
Group() {}
float getRotation() {
- return mRotate;
+ return mProperties.rotate;
}
void setRotation(float rotation) {
- mRotate = rotation;
+ mProperties.rotate = rotation;
}
float getPivotX() {
- return mPivotX;
+ return mProperties.pivotX;
}
void setPivotX(float pivotX) {
- mPivotX = pivotX;
+ mProperties.pivotX = pivotX;
}
float getPivotY() {
- return mPivotY;
+ return mProperties.pivotY;
}
void setPivotY(float pivotY) {
- mPivotY = pivotY;
+ mProperties.pivotY = pivotY;
}
float getScaleX() {
- return mScaleX;
+ return mProperties.scaleX;
}
void setScaleX(float scaleX) {
- mScaleX = scaleX;
+ mProperties.scaleX = scaleX;
}
float getScaleY() {
- return mScaleY;
+ return mProperties.scaleY;
}
void setScaleY(float scaleY) {
- mScaleY = scaleY;
+ mProperties.scaleY = scaleY;
}
float getTranslateX() {
- return mTranslateX;
+ return mProperties.translateX;
}
void setTranslateX(float translateX) {
- mTranslateX = translateX;
+ mProperties.translateX = translateX;
}
float getTranslateY() {
- return mTranslateY;
+ return mProperties.translateY;
}
void setTranslateY(float translateY) {
- mTranslateY = translateY;
+ mProperties.translateY = translateY;
}
virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
float scaleX, float scaleY) override;
@@ -268,38 +300,33 @@
void addChild(Node* child);
void dump() override;
bool getProperties(float* outProperties, int length);
+ float getPropertyValue(int propertyId) const;
+ void setPropertyValue(int propertyId, float value);
+ static bool isValidProperty(int propertyId);
private:
enum class Property {
- Rotate_Property = 0,
- PivotX_Property,
- PivotY_Property,
- ScaleX_Property,
- ScaleY_Property,
- TranslateX_Property,
- TranslateY_Property,
+ Rotate = 0,
+ PivotX,
+ PivotY,
+ ScaleX,
+ ScaleY,
+ TranslateX,
+ TranslateY,
// Count of the properties, must be at the end.
Count,
};
- float mRotate = 0;
- float mPivotX = 0;
- float mPivotY = 0;
- float mScaleX = 1;
- float mScaleY = 1;
- float mTranslateX = 0;
- float mTranslateY = 0;
- std::vector<Node*> mChildren;
+ std::vector< std::unique_ptr<Node> > mChildren;
+ Properties mProperties;
};
-class ANDROID_API Tree {
+class ANDROID_API Tree : public VirtualLightRefBase {
public:
Tree(Group* rootNode) : mRootNode(rootNode) {}
void draw(Canvas* outCanvas, SkColorFilter* colorFilter,
const SkRect& bounds, bool needsMirroring, bool canReuseCache);
- void drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
- const SkRect& originalBounds);
- void updateCachedBitmap(int width, int height);
+ const SkBitmap& getBitmapUpdateIfDirty();
void createCachedBitmapIfNeeded(int width, int height);
bool canReuseBitmap(int width, int height);
void setAllowCaching(bool allowCaching) {
@@ -316,6 +343,10 @@
mViewportWidth = viewportWidth;
mViewportHeight = viewportHeight;
}
+ SkPaint* getPaint();
+ const SkRect& getBounds() const {
+ return mBounds;
+ }
private:
// Cap the bitmap size, such that it won't hurt the performance too much
@@ -329,7 +360,7 @@
float mViewportHeight = 0;
float mRootAlpha = 1.0f;
- Group* mRootNode;
+ std::unique_ptr<Group> mRootNode;
SkRect mBounds;
SkMatrix mCanvasMatrix;
SkPaint mPaint;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3007d86..b26b310 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1784,9 +1784,9 @@
* Note that the actual playback of this data might occur after this function returns.
*
* @param audioData the array that holds the data to play.
- * @param offsetInBytes the offset expressed in bytes in audioData where the data to play
+ * @param offsetInBytes the offset expressed in bytes in audioData where the data to write
* starts.
- * @param sizeInBytes the number of bytes to read in audioData after the offset.
+ * @param sizeInBytes the number of bytes to write in audioData after the offset.
* @return zero or the positive number of bytes that were written, or
* {@link #ERROR_INVALID_OPERATION}
* if the track isn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1821,9 +1821,9 @@
* Note that the actual playback of this data might occur after this function returns.
*
* @param audioData the array that holds the data to play.
- * @param offsetInBytes the offset expressed in bytes in audioData where the data to play
+ * @param offsetInBytes the offset expressed in bytes in audioData where the data to write
* starts.
- * @param sizeInBytes the number of bytes to read in audioData after the offset.
+ * @param sizeInBytes the number of bytes to write in audioData after the offset.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
* <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -1920,8 +1920,8 @@
* In static buffer mode, copies the data to the buffer starting at offset 0.
* Note that the actual playback of this data might occur after this function returns.
*
- * @param audioData the array that holds the data to play.
- * @param offsetInShorts the offset expressed in shorts in audioData where the data to play
+ * @param audioData the array that holds the data to write.
+ * @param offsetInShorts the offset expressed in shorts in audioData where the data to write
* starts.
* @param sizeInShorts the number of shorts to read in audioData after the offset.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
@@ -1987,7 +1987,7 @@
* and the write mode is ignored.
* Note that the actual playback of this data might occur after this function returns.
*
- * @param audioData the array that holds the data to play.
+ * @param audioData the array that holds the data to write.
* The implementation does not clip for sample values within the nominal range
* [-1.0f, 1.0f], provided that all gains in the audio pipeline are
* less than or equal to unity (1.0f), and in the absence of post-processing effects
@@ -1998,8 +1998,8 @@
* and later processing in the audio path. Therefore applications are encouraged
* to provide samples values within the nominal range.
* @param offsetInFloats the offset, expressed as a number of floats,
- * in audioData where the data to play starts.
- * @param sizeInFloats the number of floats to read in audioData after the offset.
+ * in audioData where the data to write starts.
+ * @param sizeInFloats the number of floats to write in audioData after the offset.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
* <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -2070,7 +2070,7 @@
* and the write mode is ignored.
* Note that the actual playback of this data might occur after this function returns.
*
- * @param audioData the buffer that holds the data to play, starting at the position reported
+ * @param audioData the buffer that holds the data to write, starting at the position reported
* by <code>audioData.position()</code>.
* <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
* have been advanced to reflect the amount of data that was successfully written to
@@ -2137,7 +2137,7 @@
/**
* Writes the audio data to the audio sink for playback in streaming mode on a HW_AV_SYNC track.
* The blocking behavior will depend on the write mode.
- * @param audioData the buffer that holds the data to play, starting at the position reported
+ * @param audioData the buffer that holds the data to write, starting at the position reported
* by <code>audioData.position()</code>.
* <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
* have been advanced to reflect the amount of data that was successfully written to
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index b791ef1..73571af 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -32,23 +32,6 @@
android:imeOptions="actionSearch"
android:visible="false" />
<item
- android:id="@+id/menu_sort"
- android:title="@string/menu_sort"
- android:icon="@drawable/ic_menu_sortby"
- android:showAsAction="always">
- <menu>
- <item
- android:id="@+id/menu_sort_name"
- android:title="@string/sort_name" />
- <item
- android:id="@+id/menu_sort_date"
- android:title="@string/sort_date" />
- <item
- android:id="@+id/menu_sort_size"
- android:title="@string/sort_size" />
- </menu>
- </item>
- <item
android:id="@+id/menu_grid"
android:title="@string/menu_grid"
android:icon="@drawable/ic_menu_view_grid"
@@ -70,7 +53,7 @@
android:title="@string/menu_create_dir"
android:icon="@drawable/ic_menu_new_folder"
android:alphabeticShortcut="e"
- android:showAsAction="always"
+ android:showAsAction="never"
android:visible="false" />
<item
android:id="@+id/menu_paste_from_clipboard"
@@ -80,6 +63,23 @@
android:visible="false" />
<!-- Copy action is defined in mode_directory.xml -->
<item
+ android:id="@+id/menu_sort"
+ android:title="@string/menu_sort"
+ android:icon="@drawable/ic_menu_sortby"
+ android:showAsAction="never">
+ <menu>
+ <item
+ android:id="@+id/menu_sort_name"
+ android:title="@string/sort_name" />
+ <item
+ android:id="@+id/menu_sort_date"
+ android:title="@string/sort_date" />
+ <item
+ android:id="@+id/menu_sort_size"
+ android:title="@string/sort_size" />
+ </menu>
+ </item>
+ <item
android:id="@+id/menu_file_size"
android:showAsAction="never"
android:visible="false" />
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 1e11b4e..3c21a21 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -65,6 +65,9 @@
static final String EXTRA_STATE = "state";
+ // See comments where this const is referenced for details.
+ private static final int DRAWER_NO_FIDDLE_DELAY = 1500;
+
State mState;
RootsCache mRoots;
SearchManager mSearchManager;
@@ -72,9 +75,14 @@
NavigationView mNavigator;
private final String mTag;
+
@LayoutRes
private int mLayoutId;
+ // Track the time we opened the drawer in response to back being pressed.
+ // We use the time gap to figure out whether to close app or reopen the drawer.
+ private long mDrawerLastFiddled;
+
public abstract void onDocumentPicked(DocumentInfo doc, @Nullable SiblingProvider siblings);
public abstract void onDocumentsPicked(List<DocumentInfo> docs);
@@ -232,21 +240,6 @@
}
}
- void expandMenus(Menu menu) {
- for (int i = 0; i < menu.size(); i++) {
- final MenuItem item = menu.getItem(i);
- switch (item.getItemId()) {
- case R.id.menu_advanced:
- case R.id.menu_file_size:
- case R.id.menu_new_window:
- case R.id.menu_search:
- break;
- default:
- item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- }
- }
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -541,16 +534,35 @@
return;
}
- final int size = mState.stack.size();
+ int size = mState.stack.size();
- if (mDrawer.isOpen()) {
- mDrawer.setOpen(false);
- } else if (size > 1) {
+ // Do some "do what a I want" drawer fiddling, but don't
+ // do it if user already hit back recently and we recently
+ // did some fiddling.
+ if ((System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
+ // Close drawer if it is open.
+ if (mDrawer.isOpen()) {
+ mDrawer.setOpen(false);
+ mDrawerLastFiddled = System.currentTimeMillis();
+ return;
+ }
+
+ // Open the Close drawer if it is closed and we're at the top of a root.
+ if (size == 1) {
+ mDrawer.setOpen(true);
+ // Remember so we don't just close it again if back is pressed again.
+ mDrawerLastFiddled = System.currentTimeMillis();
+ return;
+ }
+ }
+
+ if (size > 1) {
mState.stack.pop();
refreshCurrentRootAndDirectory(ANIM_LEAVE);
- } else {
- super.onBackPressed();
+ return;
}
+
+ super.onBackPressed();
}
public void onStackPicked(DocumentStack stack) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 5a092db..3485fe4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -221,14 +221,6 @@
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- boolean showMenu = super.onCreateOptionsMenu(menu);
-
- expandMenus(menu);
- return showMenu;
- }
-
- @Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index dae4bf7..d589d5e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -100,14 +100,12 @@
final MenuItem advanced = menu.findItem(R.id.menu_advanced);
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
- final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
advanced.setVisible(false);
createDir.setVisible(false);
pasteFromCb.setEnabled(false);
- newWindow.setEnabled(false);
fileSize.setVisible(false);
Menus.disableHiddenItems(menu);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index c55f814..c81f342 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -185,14 +185,6 @@
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- boolean showMenu = super.onCreateOptionsMenu(menu);
-
- expandMenus(menu);
- return showMenu;
- }
-
- @Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
@@ -201,15 +193,13 @@
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
final MenuItem settings = menu.findItem(R.id.menu_settings);
+ final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
createDir.setVisible(true);
createDir.setEnabled(canCreateDirectory());
pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
settings.setVisible(root.hasSettings());
-
- // TODO: For some reason settings menu item is not
- // honoring the "showAsAction=never" setting in activity.xml.
- settings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ newWindow.setVisible(true);
Menus.disableHiddenItems(menu, pasteFromCb);
return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index f5a2aae..d368de9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -15,40 +15,19 @@
*/
package com.android.settingslib;
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.SystemProperties;
-import android.os.UserManager;
-import android.provider.Settings;
import android.telephony.CarrierConfigManager;
public class TetherUtil {
- // Extras used for communicating with the TetherService.
- public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
- public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
- public static final String EXTRA_SET_ALARM = "extraSetAlarm";
- /**
- * Tells the service to run a provision check now.
- */
- public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
-
public static boolean setWifiTethering(boolean enable, Context context) {
final WifiManager wifiManager =
(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
return wifiManager.setWifiApEnabled(null, enable);
}
- public static boolean isWifiTetherEnabled(Context context) {
- WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- return wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED;
- }
-
private static boolean isEntitlementCheckRequired(Context context) {
final CarrierConfigManager configManager = (CarrierConfigManager) context
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -71,13 +50,4 @@
}
return (provisionApp.length == 2);
}
-
- public static boolean isTetheringSupported(Context context) {
- final ConnectivityManager cm =
- (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- final boolean isAdminUser =
- UserManager.get(context).isUserAdmin(ActivityManager.getCurrentUser());
- return isAdminUser && cm.isTetheringSupported();
- }
-
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 083707a..a6ba8b5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -103,7 +103,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast,work
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index e770b5d..454d1ce 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -182,6 +182,7 @@
mListening = true;
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
+ updateShowPercent();
if (mDemoMode) return;
mBatteryController.addStateChangedCallback(this);
}
@@ -193,6 +194,11 @@
mBatteryController.removeStateChangedCallback(this);
}
+ public void disableShowPercent() {
+ mShowPercent = false;
+ postInvalidate();
+ }
+
private void postInvalidate() {
mHandler.post(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 6840e08..29f8af2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -42,6 +42,7 @@
private static final String TAG = "TileQueryHelper";
private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+ private final ArrayList<String> mSpecs = new ArrayList<>();
private final Context mContext;
private TileStateListener mListener;
@@ -76,7 +77,7 @@
mainHandler.post(new Runnable() {
@Override
public void run() {
- addTile(spec, state, mTiles);
+ addTile(spec, state);
mListener.onTilesChanged(mTiles);
}
});
@@ -95,20 +96,23 @@
mListener = listener;
}
- private static void addTile(String spec, QSTile.State state, List<TileInfo> tiles) {
+ private void addTile(String spec, QSTile.State state) {
+ if (mSpecs.contains(spec)) {
+ return;
+ }
TileInfo info = new TileInfo();
info.state = state;
info.spec = spec;
- tiles.add(info);
+ mTiles.add(info);
+ mSpecs.add(spec);
}
- private static void addTile(String spec, Drawable drawable, CharSequence label, Context context,
- List<TileInfo> tiles) {
+ private void addTile(String spec, Drawable drawable, CharSequence label, Context context) {
QSTile.State state = new QSTile.State();
state.label = label;
state.contentDescription = label;
state.icon = new DrawableIcon(drawable);
- addTile(spec, state, tiles);
+ addTile(spec, state);
}
public static class TileInfo {
@@ -133,7 +137,7 @@
icon.setTint(mContext.getColor(android.R.color.white));
}
CharSequence label = info.serviceInfo.loadLabel(pm);
- addTile(spec, icon, label != null ? label.toString() : "null", mContext, tiles);
+ addTile(spec, icon, label != null ? label.toString() : "null", mContext);
}
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index cd3e3b2..72cdf18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -153,6 +153,7 @@
private void bindView() {
mDrawable.onBatteryLevelChanged(100, false, false);
mDrawable.onPowerSaveChanged(true);
+ mDrawable.disableShowPercent();
((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
checkbox.setChecked(mPowerSave);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 9359301..d625fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -27,7 +27,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Space;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
@@ -58,8 +57,9 @@
public static final String KEY_IMAGE_DELIM = ":";
public static final String KEY_CODE_END = ")";
- protected final LayoutInflater mLayoutInflater;
- protected final LayoutInflater mLandscapeInflater;
+ protected LayoutInflater mLayoutInflater;
+ protected LayoutInflater mLandscapeInflater;
+ private int mDensity;
protected FrameLayout mRot0;
protected FrameLayout mRot90;
@@ -69,11 +69,27 @@
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
- mLayoutInflater = LayoutInflater.from(context);
+ mDensity = context.getResources().getConfiguration().densityDpi;
+ createInflaters();
+ }
+
+ private void createInflaters() {
+ mLayoutInflater = LayoutInflater.from(mContext);
Configuration landscape = new Configuration();
- landscape.setTo(context.getResources().getConfiguration());
+ landscape.setTo(mContext.getResources().getConfiguration());
landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
- mLandscapeInflater = LayoutInflater.from(context.createConfigurationContext(landscape));
+ mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mDensity != newConfig.densityDpi) {
+ mDensity = newConfig.densityDpi;
+ createInflaters();
+ clearViews();
+ inflateLayout(mCurrentLayout);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 9996b75..45aae2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -30,12 +30,12 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.FontSizeUtils;
@@ -226,12 +226,25 @@
public void setExternalIcon(String slot) {
int viewIndex = getViewIndex(getSlotIndex(slot));
+ int height = mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_icon_drawing_size);
ImageView imageView = (ImageView) mStatusIcons.getChildAt(viewIndex);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setAdjustViewBounds(true);
+ setHeightAndCenter(imageView, height);
imageView = (ImageView) mStatusIconsKeyguard.getChildAt(viewIndex);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setAdjustViewBounds(true);
+ setHeightAndCenter(imageView, height);
+ }
+
+ private void setHeightAndCenter(ImageView imageView, int height) {
+ ViewGroup.LayoutParams params = imageView.getLayoutParams();
+ params.height = height;
+ if (params instanceof LinearLayout.LayoutParams) {
+ ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
+ }
+ imageView.setLayoutParams(params);
}
public void setIcon(String slot, StatusBarIcon icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index b036936..500d603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -20,7 +20,6 @@
void addCallback(Callback callback);
void removeCallback(Callback callback);
boolean isHotspotEnabled();
- boolean isHotspotSupported();
void setHotspotEnabled(boolean enabled);
boolean isTetheringAllowed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 61d26c7..07b7409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -27,8 +27,6 @@
import android.os.UserManager;
import android.util.Log;
-import com.android.settingslib.TetherUtil;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -98,11 +96,6 @@
}
@Override
- public boolean isHotspotSupported() {
- return TetherUtil.isTetheringSupported(mContext);
- }
-
- @Override
public boolean isTetheringAllowed() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING,
UserHandle.of(mCurrentUser));
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3fd8b40..4300920 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2670,18 +2670,21 @@
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
+ @Override
public boolean isTetheringSupported() {
enforceTetherAccessPermission();
int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
- return tetherEnabledInSettings && ((mTethering.getTetherableUsbRegexs().length != 0 ||
+ return tetherEnabledInSettings && mUserManager.isAdminUser() &&
+ ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
}
+ @Override
public void startTethering(int type, ResultReceiver receiver,
boolean showProvisioningUi) {
ConnectivityManager.enforceTetherChangePermission(mContext);
@@ -2692,6 +2695,7 @@
mTethering.startTethering(type, receiver, showProvisioningUi);
}
+ @Override
public void stopTethering(int type) {
ConnectivityManager.enforceTetherChangePermission(mContext);
mTethering.stopTethering(type);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 3ce4452..5120e1b 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2800,14 +2800,13 @@
}
@Override
- public void prepareUserStorage(
- String volumeUuid, int userId, int serialNumber, boolean ephemeral) {
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
try {
mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
- userId, serialNumber, ephemeral ? 1 : 0);
+ userId, serialNumber, flags);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d646696..c021f4c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8770,6 +8770,13 @@
continue;
}
+ if (!tr.mUserSetupComplete) {
+ // Don't include task launched while user is not done setting-up.
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, user setup not complete: " + tr);
+ continue;
+ }
+
ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
if (!detailed) {
rti.baseIntent.replaceExtras((Bundle)null);
@@ -12425,9 +12432,8 @@
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- // Make sure we have the current profile info, since it is needed for
- // security checks.
- mUserController.updateCurrentProfileIdsLocked();
+ // Make sure we have the current profile info, since it is needed for security checks.
+ mUserController.onSystemReady();
mRecentTasks.onSystemReady();
// Check to see if there are any update receivers to run.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 33112c6..0b2ff65 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2601,9 +2601,11 @@
stack.setVisibleBehindActivity(visible ? r : null);
if (!visible) {
- // Make the activity immediately above r opaque.
+ // If there is a translucent home activity, we need to force it stop being translucent,
+ // because we can't depend on the application to necessarily perform that operation.
+ // Check out b/14469711 for details.
final ActivityRecord next = stack.findNextTranslucentActivity(r);
- if (next != null) {
+ if (next != null && next.isHomeActivity()) {
mService.convertFromTranslucent(next.appToken);
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 703dfca..10f0977 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -108,6 +108,7 @@
private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
+ private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete";
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
private static final String ATTR_TASKTYPE = "task_type";
private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
@@ -157,6 +158,8 @@
String stringName; // caching of toString() result.
int userId; // user for which this task was created
+ boolean mUserSetupComplete; // The user set-up is complete as of the last time the task activity
+ // was changed.
int numFullscreen; // Number of fullscreen activities.
@@ -318,7 +321,8 @@
boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
- int resizeMode, boolean privileged, boolean _realActivitySuspended) {
+ int resizeMode, boolean privileged, boolean _realActivitySuspended,
+ boolean userSetupComplete) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -341,6 +345,7 @@
taskType = _taskType;
mTaskToReturnTo = HOME_ACTIVITY_TYPE;
userId = _userId;
+ mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
firstActiveTime = _firstActiveTime;
lastActiveTime = _lastActiveTime;
@@ -441,6 +446,7 @@
}
userId = UserHandle.getUserId(info.applicationInfo.uid);
+ mUserSetupComplete = mService.mUserController.isUserSetupCompleteLocked(userId);
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
@@ -1071,6 +1077,7 @@
out.attribute(null, ATTR_AUTOREMOVERECENTS, String.valueOf(autoRemoveRecents));
out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
out.attribute(null, ATTR_USERID, String.valueOf(userId));
+ out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete));
out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
@@ -1140,6 +1147,7 @@
boolean askedCompatMode = false;
int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
int userId = 0;
+ boolean userSetupComplete = true;
int effectiveUid = -1;
String lastDescription = null;
long firstActiveTime = -1;
@@ -1186,6 +1194,8 @@
askedCompatMode = Boolean.valueOf(attrValue);
} else if (ATTR_USERID.equals(attrName)) {
userId = Integer.valueOf(attrValue);
+ } else if (ATTR_USER_SETUP_COMPLETE.equals(attrName)) {
+ userSetupComplete = Boolean.valueOf(attrValue);
} else if (ATTR_EFFECTIVE_UID.equals(attrName)) {
effectiveUid = Integer.valueOf(attrValue);
} else if (ATTR_TASKTYPE.equals(attrName)) {
@@ -1285,7 +1295,7 @@
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
taskAffiliationColor, callingUid, callingPackage, resizeMode, privileged,
- realActivitySuspended);
+ realActivitySuspended, userSetupComplete);
task.updateOverrideConfiguration(bounds);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 551f332..2f63b2d3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -24,6 +24,7 @@
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -45,11 +46,14 @@
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -66,10 +70,12 @@
import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.R;
@@ -145,6 +151,34 @@
private final LockPatternUtils mLockPatternUtils;
+ // Set of users who have completed the set-up process.
+ private final SparseBooleanArray mSetupCompletedUsers = new SparseBooleanArray();
+ private final UserSetupCompleteContentObserver mUserSetupCompleteContentObserver;
+
+ private class UserSetupCompleteContentObserver extends ContentObserver {
+ private final Uri mUserSetupComplete = Settings.Secure.getUriFor(USER_SETUP_COMPLETE);
+
+ public UserSetupCompleteContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ void register(ContentResolver resolver) {
+ resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
+ synchronized (mService) {
+ updateCurrentUserSetupCompleteLocked();
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mUserSetupComplete.equals(uri)) {
+ synchronized (mService) {
+ updateCurrentUserSetupCompleteLocked();
+ }
+ }
+ }
+ }
+
UserController(ActivityManagerService service) {
mService = service;
mHandler = mService.mHandler;
@@ -154,6 +188,7 @@
mUserLru.add(UserHandle.USER_SYSTEM);
mLockPatternUtils = new LockPatternUtils(mService.mContext);
updateStartedUserArrayLocked();
+ mUserSetupCompleteContentObserver = new UserSetupCompleteContentObserver(mHandler);
}
void finishUserSwitch(UserState uss) {
@@ -424,6 +459,7 @@
mStartedUsers.remove(userId);
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLocked();
+ mSetupCompletedUsers.delete(userId);
mService.onUserStoppedLocked(userId);
// Clean up all state and processes associated with the user.
@@ -619,6 +655,7 @@
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
+ updateCurrentUserSetupCompleteLocked();
if (foreground) {
mCurrentUserId = userId;
@@ -833,6 +870,17 @@
mUserSwitchObservers.finishBroadcast();
}
+ void updateCurrentUserSetupCompleteLocked() {
+ final ContentResolver cr = mService.mContext.getContentResolver();
+ final boolean setupComplete =
+ Settings.Secure.getIntForUser(cr, USER_SETUP_COMPLETE, 0, mCurrentUserId) != 0;
+ mSetupCompletedUsers.put(mCurrentUserId, setupComplete);
+ }
+
+ boolean isUserSetupCompleteLocked(int userId) {
+ return mSetupCompletedUsers.get(userId);
+ }
+
private void stopBackgroundUsersIfEnforced(int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -1141,12 +1189,17 @@
}
}
+ void onSystemReady() {
+ updateCurrentProfileIdsLocked();
+ mUserSetupCompleteContentObserver.register(mService.mContext.getContentResolver());
+ }
+
/**
* Refreshes the list of users related to the current user when either a
* user switch happens or when a new related user is started in the
* background.
*/
- void updateCurrentProfileIdsLocked() {
+ private void updateCurrentProfileIdsLocked() {
final List<UserInfo> profiles = getUserManager().getProfiles(mCurrentUserId,
false /* enabledOnly */);
int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index a73a67a..760b218 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -451,7 +451,7 @@
((BluetoothPan) proxy).setBluetoothTethering(enable);
// TODO: Enabling bluetooth tethering can fail asynchronously here.
// We should figure out a way to bubble up that failure instead of sending success.
- int result = ((BluetoothPan) proxy).isTetheringOn() ?
+ int result = ((BluetoothPan) proxy).isTetheringOn() == enable ?
ConnectivityManager.TETHER_ERROR_NO_ERROR :
ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
sendTetherResult(receiver, result);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f5da52e..29d52c1 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -250,6 +250,12 @@
rebindServices();
}
+ public void onUserUnlocked(int user) {
+ if (DEBUG) Slog.d(TAG, "onUserUnlocked u=" + user);
+ rebuildRestoredPackages();
+ rebindServices();
+ }
+
public ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
if (service == null) {
return null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 078094c..bcb2c59 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -816,6 +816,12 @@
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
mZenModeHelper.onUserRemoved(user);
+ } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
+ final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ mConditionProviders.onUserUnlocked(user);
+ mListeners.onUserUnlocked(user);
+ mAssistant.onUserUnlocked(user);
+ mZenModeHelper.onUserUnlocked(user);
}
}
};
@@ -994,6 +1000,7 @@
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b7abce21..7518c6e 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,7 +16,6 @@
package com.android.server.notification;
-import static android.media.AudioAttributes.USAGE_ALARM;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.media.AudioAttributes.USAGE_UNKNOWN;
@@ -195,19 +194,7 @@
}
public void onUserSwitched(int user) {
- if (mUser == user || user < UserHandle.USER_SYSTEM) return;
- mUser = user;
- if (DEBUG) Log.d(TAG, "onUserSwitched u=" + user);
- ZenModeConfig config = mConfigs.get(user);
- if (config == null) {
- if (DEBUG) Log.d(TAG, "onUserSwitched: generating default config for user " + user);
- config = mDefaultConfig.copy();
- config.user = user;
- }
- synchronized (mConfig) {
- setConfigLocked(config, "onUserSwitched");
- }
- cleanUpZenRules();
+ loadConfigForUser(user, "onUserSwitched");
}
public void onUserRemoved(int user) {
@@ -216,6 +203,26 @@
mConfigs.remove(user);
}
+ public void onUserUnlocked(int user) {
+ loadConfigForUser(user, "onUserUnlocked");
+ }
+
+ private void loadConfigForUser(int user, String reason) {
+ if (mUser == user || user < UserHandle.USER_SYSTEM) return;
+ mUser = user;
+ if (DEBUG) Log.d(TAG, reason + " u=" + user);
+ ZenModeConfig config = mConfigs.get(user);
+ if (config == null) {
+ if (DEBUG) Log.d(TAG, reason + " generating default config for user " + user);
+ config = mDefaultConfig.copy();
+ config.user = user;
+ }
+ synchronized (mConfig) {
+ setConfigLocked(config, reason);
+ }
+ cleanUpZenRules();
+ }
+
public int getZenModeListenerInterruptionFilter() {
return NotificationManager.zenModeToInterruptionFilter(mZenMode);
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index dac89ec..c08f713 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,11 +16,11 @@
package com.android.server.pm;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.os.storage.StorageManager;
import android.util.Slog;
import com.android.internal.os.InstallerConnection;
@@ -29,9 +29,6 @@
import dalvik.system.VMRuntime;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
public final class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -52,19 +49,9 @@
/** This is an OTA update dexopt */
public static final int DEXOPT_OTA = 1 << 6;
- /** @hide */
- @IntDef(flag = true, value = {
- FLAG_DE_STORAGE,
- FLAG_CE_STORAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface StorageFlags {}
-
- public static final int FLAG_DE_STORAGE = 1 << 0;
- public static final int FLAG_CE_STORAGE = 1 << 1;
-
- public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 2;
- public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 3;
+ // NOTE: keep in sync with installd
+ public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
+ public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
private final InstallerConnection mInstaller;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 553a9a2..504ce31 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -235,7 +235,6 @@
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
-import com.android.server.pm.Installer.StorageFlags;
import com.android.server.pm.PermissionsState.PermissionState;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
@@ -2388,9 +2387,9 @@
// can't wait for user to start
final int flags;
if (StorageManager.isFileBasedEncryptionEnabled()) {
- flags = Installer.FLAG_DE_STORAGE;
+ flags = StorageManager.FLAG_STORAGE_DE;
} else {
- flags = Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE;
+ flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
}
reconcileAppsData(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM, flags);
@@ -6860,7 +6859,7 @@
private boolean removeDataDirsLI(String volumeUuid, String packageName) {
// TODO: triage flags as part of 26466827
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
boolean res = true;
final int[] users = sUserManager.getUserIds();
@@ -6906,7 +6905,7 @@
private void deleteCodeCacheDirsLI(String volumeUuid, String packageName) {
// TODO: triage flags as part of 26466827
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
final int[] users = sUserManager.getUserIds();
for (int user : users) {
@@ -13947,7 +13946,8 @@
outInfo.removedUsers = new int[] {removeUser};
}
// TODO: triage flags as part of 26466827
- final int installerFlags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int installerFlags = StorageManager.FLAG_STORAGE_CE
+ | StorageManager.FLAG_STORAGE_DE;
try {
mInstaller.destroyAppData(ps.volumeUuid, packageName, removeUser, installerFlags);
} catch (InstallerException e) {
@@ -14127,7 +14127,7 @@
// record of app. This helps users recover from UID mismatches without
// resorting to a full data wipe.
// TODO: triage flags as part of 26466827
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
mInstaller.clearAppData(pkg.volumeUuid, packageName, userId, flags);
} catch (InstallerException e) {
@@ -14362,7 +14362,7 @@
return false;
}
// TODO: triage flags as part of 26466827
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
mInstaller.clearAppData(p.volumeUuid, packageName, userId,
flags | Installer.FLAG_CLEAR_CACHE_ONLY);
@@ -14463,7 +14463,7 @@
}
// TODO: triage flags as part of 26466827
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
mInstaller.getAppSize(p.volumeUuid, packageName, userHandle, flags, apkPath,
libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
@@ -16787,16 +16787,20 @@
}
// Reconcile app data for all started/unlocked users
+ final StorageManager sm = mContext.getSystemService(StorageManager.class);
final UserManager um = mContext.getSystemService(UserManager.class);
for (UserInfo user : um.getUsers()) {
+ final int flags;
if (um.isUserUnlocked(user.id)) {
- reconcileAppsData(volumeUuid, user.id,
- Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE);
+ flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (um.isUserRunning(user.id)) {
- reconcileAppsData(volumeUuid, user.id, Installer.FLAG_DE_STORAGE);
+ flags = StorageManager.FLAG_STORAGE_DE;
} else {
continue;
}
+
+ sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
+ reconcileAppsData(volumeUuid, user.id, flags);
}
synchronized (mPackages) {
@@ -16905,20 +16909,6 @@
}
}
}
-
- final StorageManager sm = mContext.getSystemService(StorageManager.class);
- final UserManager um = mContext.getSystemService(UserManager.class);
- for (UserInfo user : um.getUsers()) {
- final File userDir = Environment.getDataUserDirectory(volumeUuid, user.id);
- if (userDir.exists()) continue;
-
- try {
- sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, user.isEphemeral());
- UserManagerService.enforceSerialNumber(userDir, user.serialNumber);
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e);
- }
- }
}
private void assertPackageKnown(String volumeUuid, String packageName)
@@ -16988,7 +16978,7 @@
* Verifies that directories exist and that ownership and labeling is
* correct for all installed apps on all mounted volumes.
*/
- void reconcileAppsData(int userId, @StorageFlags int flags) {
+ void reconcileAppsData(int userId, int flags) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
@@ -17005,7 +16995,7 @@
* Verifies that directories exist and that ownership and labeling is
* correct for all installed apps.
*/
- private void reconcileAppsData(String volumeUuid, int userId, @StorageFlags int flags) {
+ private void reconcileAppsData(String volumeUuid, int userId, int flags) {
Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
+ Integer.toHexString(flags));
@@ -17016,7 +17006,7 @@
// First look for stale data that doesn't belong, and check if things
// have changed since we did our last restorecon
- if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
if (!isUserKeyUnlocked(userId)) {
throw new RuntimeException(
"Yikes, someone asked us to reconcile CE storage while " + userId
@@ -17034,12 +17024,12 @@
logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
synchronized (mInstallLock) {
destroyAppDataLI(volumeUuid, packageName, userId,
- Installer.FLAG_CE_STORAGE);
+ StorageManager.FLAG_STORAGE_CE);
}
}
}
}
- if ((flags & Installer.FLAG_DE_STORAGE) != 0) {
+ if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
restoreconNeeded |= SELinuxMMAC.isRestoreconNeeded(deDir);
final File[] files = FileUtils.listFilesOrEmpty(deDir);
@@ -17051,7 +17041,7 @@
logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
synchronized (mInstallLock) {
destroyAppDataLI(volumeUuid, packageName, userId,
- Installer.FLAG_DE_STORAGE);
+ StorageManager.FLAG_STORAGE_DE);
}
}
}
@@ -17080,10 +17070,10 @@
}
if (restoreconNeeded) {
- if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
SELinuxMMAC.setRestoreconDone(ceDir);
}
- if ((flags & Installer.FLAG_DE_STORAGE) != 0) {
+ if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
SELinuxMMAC.setRestoreconDone(deDir);
}
}
@@ -17112,9 +17102,9 @@
for (UserInfo user : um.getUsers()) {
final int flags;
if (um.isUserUnlocked(user.id)) {
- flags = Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE;
+ flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (um.isUserRunning(user.id)) {
- flags = Installer.FLAG_DE_STORAGE;
+ flags = StorageManager.FLAG_STORAGE_DE;
} else {
continue;
}
@@ -17135,7 +17125,7 @@
* will try recovering system apps by wiping data; third-party app data is
* left intact.
*/
- private void prepareAppData(String volumeUuid, int userId, @StorageFlags int flags,
+ private void prepareAppData(String volumeUuid, int userId, int flags,
PackageParser.Package pkg, boolean restoreconNeeded) {
if (DEBUG_APP_DATA) {
Slog.v(TAG, "prepareAppData for " + pkg.packageName + " u" + userId + " 0x"
@@ -17173,7 +17163,7 @@
restoreconAppDataLI(volumeUuid, packageName, userId, flags, appId, app.seinfo);
}
- if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
// Create a native library symlink only if we have native libraries
// and if the native libraries are 32 bit libraries. We do not provide
// this symlink for 64 bit libraries.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bbbe693..fcb777b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3787,7 +3787,7 @@
continue;
}
// TODO: triage flags!
- final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
+ final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
installer.createAppData(volumeUuids[i], names[i], userHandle, flags, appIds[i],
seinfos[i], targetSdkVersions[i]);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f46567..3cc7b10 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,6 @@
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -78,7 +77,9 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
-import com.android.server.pm.Installer.StorageFlags;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -96,9 +97,6 @@
import java.util.ArrayList;
import java.util.List;
-import libcore.io.IoUtils;
-import libcore.util.Objects;
-
/**
* Service for {@link UserManager}.
*
@@ -1893,17 +1891,8 @@
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- try {
- final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
- storage.prepareUserStorage(
- volumeUuid, userId, userInfo.serialNumber, userInfo.isEphemeral());
- enforceSerialNumber(userDir, userInfo.serialNumber);
- } catch (IOException e) {
- Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
- }
- }
+ prepareUserStorage(userId, userInfo.serialNumber,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
mPm.createNewUser(userId);
userInfo.partial = false;
synchronized (mPackagesLock) {
@@ -2466,11 +2455,24 @@
}
/**
+ * Prepare storage areas for given user on all mounted devices.
+ */
+ private void prepareUserStorage(int userId, int userSerial, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+ }
+ }
+
+ /**
* Called right before a user is started. This gives us a chance to prepare
* app storage and apply any user restrictions.
*/
public void onBeforeStartUser(int userId) {
- mPm.reconcileAppsData(userId, Installer.FLAG_DE_STORAGE);
+ final int userSerial = getUserSerialNumber(userId);
+ prepareUserStorage(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
+ mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE);
if (userId != UserHandle.USER_SYSTEM) {
synchronized (mRestrictionsLock) {
@@ -2484,7 +2486,9 @@
* app storage.
*/
public void onBeforeUnlockUser(int userId) {
- mPm.reconcileAppsData(userId, Installer.FLAG_CE_STORAGE);
+ final int userSerial = getUserSerialNumber(userId);
+ prepareUserStorage(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+ mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE);
}
/**
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 7d7be07..071ec1b0 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -15,6 +15,7 @@
services.core \
services.devicepolicy \
services.net \
+ services.usage \
easymocklib \
guava \
android-support-test \
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
new file mode 100644
index 0000000..9ccb1a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 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.usage;
+
+import android.os.FileUtils;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class AppIdleHistoryTests extends AndroidTestCase {
+
+ File mStorageDir;
+
+ final static String PACKAGE_1 = "com.android.testpackage1";
+ final static String PACKAGE_2 = "com.android.testpackage2";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mStorageDir = new File(getContext().getFilesDir(), "appidle");
+ mStorageDir.mkdirs();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ FileUtils.deleteContents(mStorageDir);
+ super.tearDown();
+ }
+
+ public void testFilesCreation() {
+ final int userId = 0;
+ AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0);
+
+ aih.updateDisplayLocked(true, /* elapsedRealtime= */ 1000);
+ aih.updateDisplayLocked(false, /* elapsedRealtime= */ 2000);
+ // Screen On time file should be written right away
+ assertTrue(aih.getScreenOnTimeFile().exists());
+
+ aih.writeAppIdleTimesLocked(userId);
+ // stats file should be written now
+ assertTrue(new File(new File(mStorageDir, "users/" + userId),
+ AppIdleHistory.APP_IDLE_FILENAME).exists());
+ }
+
+ public void testScreenOnTime() {
+ AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+ aih.updateDisplayLocked(false, 2000);
+ assertEquals(aih.getScreenOnTimeLocked(2000), 0);
+ aih.updateDisplayLocked(true, 3000);
+ assertEquals(aih.getScreenOnTimeLocked(4000), 1000);
+ assertEquals(aih.getScreenOnTimeLocked(5000), 2000);
+ aih.updateDisplayLocked(false, 6000);
+ // Screen on time should not keep progressing with screen is off
+ assertEquals(aih.getScreenOnTimeLocked(7000), 3000);
+ assertEquals(aih.getScreenOnTimeLocked(8000), 3000);
+ aih.writeElapsedTimeLocked();
+
+ // Check if the screen on time is persisted across instantiations
+ AppIdleHistory aih2 = new AppIdleHistory(mStorageDir, 0);
+ assertEquals(aih2.getScreenOnTimeLocked(11000), 3000);
+ aih2.updateDisplayLocked(true, 4000);
+ aih2.updateDisplayLocked(false, 5000);
+ assertEquals(aih2.getScreenOnTimeLocked(13000), 4000);
+ }
+
+ public void testPackageEvents() {
+ AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+ aih.setThresholds(4000, 1000);
+ aih.updateDisplayLocked(true, 1000);
+ // App is not-idle by default
+ assertFalse(aih.isIdleLocked(PACKAGE_1, 0, 1500));
+ // Still not idle
+ assertFalse(aih.isIdleLocked(PACKAGE_1, 0, 3000));
+ // Idle now
+ assertTrue(aih.isIdleLocked(PACKAGE_1, 0, 8000));
+ // Not idle
+ assertFalse(aih.isIdleLocked(PACKAGE_2, 0, 9000));
+
+ // Screen off
+ aih.updateDisplayLocked(false, 9100);
+ // Still idle after 10 seconds because screen hasn't been on long enough
+ assertFalse(aih.isIdleLocked(PACKAGE_2, 0, 20000));
+ aih.updateDisplayLocked(true, 21000);
+ assertTrue(aih.isIdleLocked(PACKAGE_2, 0, 23000));
+ }
+}
\ No newline at end of file
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index e3c0868..3e2b43d 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,19 +16,45 @@
package com.android.server.usage;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
/**
* Keeps track of recent active state changes in apps.
* Access should be guarded by a lock by the caller.
*/
public class AppIdleHistory {
- private SparseArray<ArrayMap<String,byte[]>> mIdleHistory = new SparseArray<>();
- private long lastPeriod = 0;
+ private static final String TAG = "AppIdleHistory";
+
+ // History for all users and all packages
+ private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
+ private long mLastPeriod = 0;
private static final long ONE_MINUTE = 60 * 1000;
private static final int HISTORY_SIZE = 100;
private static final int FLAG_LAST_STATE = 2;
@@ -36,77 +62,353 @@
private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
: 60 * ONE_MINUTE;
- public void addEntry(String packageName, int userId, boolean idle, long timeNow) {
- ArrayMap<String, byte[]> userHistory = getUserHistory(userId);
- byte[] packageHistory = getPackageHistory(userHistory, packageName);
+ @VisibleForTesting
+ static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
+ private static final String TAG_PACKAGES = "packages";
+ private static final String TAG_PACKAGE = "package";
+ private static final String ATTR_NAME = "name";
+ // Screen on timebase time when app was last used
+ private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
+ // Elapsed timebase time when app was last used
+ private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
- long thisPeriod = timeNow / PERIOD_DURATION;
+ // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
+ private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
+ private long mElapsedDuration; // Total device on duration since device was "born"
+
+ // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot)
+ private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
+ private long mScreenOnDuration; // Total screen on duration since device was "born"
+
+ private long mElapsedTimeThreshold;
+ private long mScreenOnTimeThreshold;
+ private final File mStorageDir;
+
+ private boolean mScreenOn;
+
+ private static class PackageHistory {
+ final byte[] recent = new byte[HISTORY_SIZE];
+ long lastUsedElapsedTime;
+ long lastUsedScreenTime;
+ }
+
+ AppIdleHistory(long elapsedRealtime) {
+ this(Environment.getDataSystemDirectory(), elapsedRealtime);
+ }
+
+ @VisibleForTesting
+ AppIdleHistory(File storageDir, long elapsedRealtime) {
+ mElapsedSnapshot = elapsedRealtime;
+ mScreenOnSnapshot = elapsedRealtime;
+ mStorageDir = storageDir;
+ readScreenOnTimeLocked();
+ }
+
+ public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
+ mElapsedTimeThreshold = elapsedTimeThreshold;
+ mScreenOnTimeThreshold = screenOnTimeThreshold;
+ }
+
+ public void updateDisplayLocked(boolean screenOn, long elapsedRealtime) {
+ if (screenOn == mScreenOn) return;
+
+ mScreenOn = screenOn;
+ if (mScreenOn) {
+ mScreenOnSnapshot = elapsedRealtime;
+ } else {
+ mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot;
+ mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
+ writeScreenOnTimeLocked();
+ mElapsedSnapshot = elapsedRealtime;
+ }
+ }
+
+ public long getScreenOnTimeLocked(long elapsedRealtime) {
+ long screenOnTime = mScreenOnDuration;
+ if (mScreenOn) {
+ screenOnTime += elapsedRealtime - mScreenOnSnapshot;
+ }
+ return screenOnTime;
+ }
+
+ @VisibleForTesting
+ File getScreenOnTimeFile() {
+ return new File(mStorageDir, "screen_on_time");
+ }
+
+ private void readScreenOnTimeLocked() {
+ File screenOnTimeFile = getScreenOnTimeFile();
+ if (screenOnTimeFile.exists()) {
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
+ mScreenOnDuration = Long.parseLong(reader.readLine());
+ mElapsedDuration = Long.parseLong(reader.readLine());
+ reader.close();
+ } catch (IOException | NumberFormatException e) {
+ }
+ } else {
+ writeScreenOnTimeLocked();
+ }
+ }
+
+ private void writeScreenOnTimeLocked() {
+ AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
+ FileOutputStream fos = null;
+ try {
+ fos = screenOnTimeFile.startWrite();
+ fos.write((Long.toString(mScreenOnDuration) + "\n"
+ + Long.toString(mElapsedDuration) + "\n").getBytes());
+ screenOnTimeFile.finishWrite(fos);
+ } catch (IOException ioe) {
+ screenOnTimeFile.failWrite(fos);
+ }
+ }
+
+ /**
+ * To be called periodically to keep track of elapsed time when app idle times are written
+ */
+ public void writeElapsedTimeLocked() {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ // Only bump up and snapshot the elapsed time. Don't change screen on duration.
+ mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
+ mElapsedSnapshot = elapsedRealtime;
+ writeScreenOnTimeLocked();
+ }
+
+ public void reportUsageLocked(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
+ PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
+ elapsedRealtime);
+
+ shiftHistoryToNow(userHistory, elapsedRealtime);
+
+ packageHistory.lastUsedElapsedTime = mElapsedDuration
+ + (elapsedRealtime - mElapsedSnapshot);
+ packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime);
+ packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+ }
+
+ public void setIdle(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
+ PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
+ elapsedRealtime);
+
+ shiftHistoryToNow(userHistory, elapsedRealtime);
+
+ packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
+ }
+
+ private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
+ long elapsedRealtime) {
+ long thisPeriod = elapsedRealtime / PERIOD_DURATION;
// Has the period switched over? Slide all users' package histories
- if (lastPeriod != 0 && lastPeriod < thisPeriod
- && (thisPeriod - lastPeriod) < HISTORY_SIZE - 1) {
- int diff = (int) (thisPeriod - lastPeriod);
+ if (mLastPeriod != 0 && mLastPeriod < thisPeriod
+ && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
+ int diff = (int) (thisPeriod - mLastPeriod);
final int NUSERS = mIdleHistory.size();
for (int u = 0; u < NUSERS; u++) {
userHistory = mIdleHistory.valueAt(u);
- for (byte[] history : userHistory.values()) {
+ for (PackageHistory idleState : userHistory.values()) {
// Shift left
- System.arraycopy(history, diff, history, 0, HISTORY_SIZE - diff);
+ System.arraycopy(idleState.recent, diff, idleState.recent, 0,
+ HISTORY_SIZE - diff);
// Replicate last state across the diff
for (int i = 0; i < diff; i++) {
- history[HISTORY_SIZE - i - 1] =
- (byte) (history[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
+ idleState.recent[HISTORY_SIZE - i - 1] =
+ (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
}
}
}
}
- lastPeriod = thisPeriod;
- if (!idle) {
- packageHistory[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
- } else {
- packageHistory[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
- }
+ mLastPeriod = thisPeriod;
}
- private ArrayMap<String, byte[]> getUserHistory(int userId) {
- ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
+ private ArrayMap<String, PackageHistory> getUserHistoryLocked(int userId) {
+ ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
if (userHistory == null) {
userHistory = new ArrayMap<>();
mIdleHistory.put(userId, userHistory);
+ readAppIdleTimesLocked(userId, userHistory);
}
return userHistory;
}
- private byte[] getPackageHistory(ArrayMap<String, byte[]> userHistory, String packageName) {
- byte[] packageHistory = userHistory.get(packageName);
+ private PackageHistory getPackageHistoryLocked(ArrayMap<String, PackageHistory> userHistory,
+ String packageName, long elapsedRealtime) {
+ PackageHistory packageHistory = userHistory.get(packageName);
if (packageHistory == null) {
- packageHistory = new byte[HISTORY_SIZE];
+ packageHistory = new PackageHistory();
+ packageHistory.lastUsedElapsedTime = getElapsedTimeLocked(elapsedRealtime);
+ packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime);
userHistory.put(packageName, packageHistory);
}
return packageHistory;
}
- public void removeUser(int userId) {
+ public void onUserRemoved(int userId) {
mIdleHistory.remove(userId);
}
- public boolean isIdle(int userId, String packageName) {
- ArrayMap<String, byte[]> userHistory = getUserHistory(userId);
- byte[] packageHistory = getPackageHistory(userHistory, packageName);
- return (packageHistory[HISTORY_SIZE - 1] & FLAG_LAST_STATE) == 0;
+ public boolean isIdleLocked(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
+ PackageHistory packageHistory =
+ getPackageHistoryLocked(userHistory, packageName, elapsedRealtime);
+ if (packageHistory == null) {
+ return false; // Default to not idle
+ } else {
+ return hasPassedThresholdsLocked(packageHistory, elapsedRealtime);
+ }
+ }
+
+ private long getElapsedTimeLocked(long elapsedRealtime) {
+ return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
+ }
+
+ public void setIdleLocked(String packageName, int userId, boolean idle, long elapsedRealtime) {
+ ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
+ PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
+ elapsedRealtime);
+ packageHistory.lastUsedElapsedTime = getElapsedTimeLocked(elapsedRealtime)
+ - mElapsedTimeThreshold;
+ packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime)
+ - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
+ }
+
+ private boolean hasPassedThresholdsLocked(PackageHistory packageHistory, long elapsedRealtime) {
+ return (packageHistory.lastUsedScreenTime
+ <= getScreenOnTimeLocked(elapsedRealtime) - mScreenOnTimeThreshold)
+ && (packageHistory.lastUsedElapsedTime
+ <= getElapsedTimeLocked(elapsedRealtime) - mElapsedTimeThreshold);
+ }
+
+ private File getUserFile(int userId) {
+ return new File(new File(new File(mStorageDir, "users"),
+ Integer.toString(userId)), APP_IDLE_FILENAME);
+ }
+
+ private void readAppIdleTimesLocked(int userId, ArrayMap<String, PackageHistory> userHistory) {
+ FileInputStream fis = null;
+ try {
+ AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
+ fis = appIdleFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Skip
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(TAG, "Unable to read app idle file for user " + userId);
+ return;
+ }
+ if (!parser.getName().equals(TAG_PACKAGES)) {
+ return;
+ }
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ final String name = parser.getName();
+ if (name.equals(TAG_PACKAGE)) {
+ final String packageName = parser.getAttributeValue(null, ATTR_NAME);
+ PackageHistory packageHistory = new PackageHistory();
+ packageHistory.lastUsedElapsedTime =
+ Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
+ packageHistory.lastUsedScreenTime =
+ Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
+ userHistory.put(packageName, packageHistory);
+ }
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Unable to read app idle file for user " + userId);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ public void writeAppIdleTimesLocked(int userId) {
+ FileOutputStream fos = null;
+ AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
+ try {
+ fos = appIdleFile.startWrite();
+ final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+ FastXmlSerializer xml = new FastXmlSerializer();
+ xml.setOutput(bos, StandardCharsets.UTF_8.name());
+ xml.startDocument(null, true);
+ xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ xml.startTag(null, TAG_PACKAGES);
+
+ ArrayMap<String,PackageHistory> userHistory = getUserHistoryLocked(userId);
+ final int N = userHistory.size();
+ for (int i = 0; i < N; i++) {
+ String packageName = userHistory.keyAt(i);
+ PackageHistory history = userHistory.valueAt(i);
+ xml.startTag(null, TAG_PACKAGE);
+ xml.attribute(null, ATTR_NAME, packageName);
+ xml.attribute(null, ATTR_ELAPSED_IDLE,
+ Long.toString(history.lastUsedElapsedTime));
+ xml.attribute(null, ATTR_SCREEN_IDLE,
+ Long.toString(history.lastUsedScreenTime));
+ xml.endTag(null, TAG_PACKAGE);
+ }
+
+ xml.endTag(null, TAG_PACKAGES);
+ xml.endDocument();
+ appIdleFile.finishWrite(fos);
+ } catch (Exception e) {
+ appIdleFile.failWrite(fos);
+ Slog.e(TAG, "Error writing app idle file for user " + userId);
+ }
}
public void dump(IndentingPrintWriter idpw, int userId) {
- ArrayMap<String, byte[]> userHistory = mIdleHistory.get(userId);
+ idpw.println("Package idle stats:");
+ idpw.increaseIndent();
+ ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long totalElapsedTime = getElapsedTimeLocked(elapsedRealtime);
+ final long screenOnTime = getScreenOnTimeLocked(elapsedRealtime);
if (userHistory == null) return;
final int P = userHistory.size();
for (int p = 0; p < P; p++) {
final String packageName = userHistory.keyAt(p);
- final byte[] history = userHistory.valueAt(p);
+ final PackageHistory packageHistory = userHistory.valueAt(p);
+ idpw.print("package=" + packageName);
+ idpw.print(" lastUsedElapsed=");
+ TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
+ idpw.print(" lastUsedScreenOn=");
+ TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
+ idpw.print(" idle=" + (isIdleLocked(packageName, userId, elapsedRealtime) ? "y" : "n"));
+ idpw.println();
+ }
+ idpw.println();
+ idpw.print("totalElapsedTime=");
+ TimeUtils.formatDuration(getElapsedTimeLocked(elapsedRealtime), idpw);
+ idpw.println();
+ idpw.print("totalScreenOnTime=");
+ TimeUtils.formatDuration(getScreenOnTimeLocked(elapsedRealtime), idpw);
+ idpw.println();
+ idpw.decreaseIndent();
+ }
+
+ public void dumpHistory(IndentingPrintWriter idpw, int userId) {
+ ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (userHistory == null) return;
+ final int P = userHistory.size();
+ for (int p = 0; p < P; p++) {
+ final String packageName = userHistory.keyAt(p);
+ final byte[] history = userHistory.valueAt(p).recent;
for (int i = 0; i < HISTORY_SIZE; i++) {
idpw.print(history[i] == 0 ? '.' : 'A');
}
+ idpw.print(" idle=" + (isIdleLocked(packageName, userId, elapsedRealtime) ? "y" : "n"));
idpw.print(" " + packageName);
idpw.println();
}
}
-}
\ No newline at end of file
+}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 7f379fe..f541f70 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -48,7 +48,6 @@
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
- usageStats.mBeginIdleTime = 0;
packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
@@ -113,7 +112,6 @@
if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
usageStats.mLastTimeUsed = timeStamp;
}
- usageStats.mLastTimeSystemUsed = timeStamp;
usageStats.mEndTimeStamp = timeStamp;
if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
@@ -123,22 +121,6 @@
endTime = timeStamp;
}
- /**
- * Updates the last active time for the package. The timestamp uses a timebase that
- * tracks the device usage time.
- * @param packageName
- * @param timeStamp
- */
- void updateBeginIdleTime(String packageName, long timeStamp) {
- UsageStats usageStats = getOrCreateUsageStats(packageName);
- usageStats.mBeginIdleTime = timeStamp;
- }
-
- void updateSystemLastUsedTime(String packageName, long lastUsedTime) {
- UsageStats usageStats = getOrCreateUsageStats(packageName);
- usageStats.mLastTimeSystemUsed = lastUsedTime;
- }
-
void updateConfigurationStats(Configuration config, long timeStamp) {
if (activeConfiguration != null) {
ConfigurationStats activeStats = configurations.get(activeConfiguration);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 77740387..46ad8a1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -40,6 +40,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
@@ -62,7 +63,6 @@
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.Slog;
import android.util.SparseArray;
@@ -77,12 +77,8 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -106,7 +102,7 @@
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
- long mAppIdleDurationMillis;
+ long mAppIdleScreenThresholdMillis;
long mCheckIdleIntervalMillis;
long mAppIdleWallclockThresholdMillis;
long mAppIdleParoleIntervalMillis;
@@ -147,11 +143,8 @@
private volatile boolean mPendingOneTimeCheckIdleStates;
- long mScreenOnTime;
- long mLastScreenOnEventRealtime;
-
@GuardedBy("mLock")
- private AppIdleHistory mAppIdleHistory = new AppIdleHistory();
+ private AppIdleHistory mAppIdleHistory;
private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
mPackageAccessListeners = new ArrayList<>();
@@ -191,8 +184,7 @@
synchronized (mLock) {
cleanUpRemovedUsersLocked();
- mLastScreenOnEventRealtime = SystemClock.elapsedRealtime();
- mScreenOnTime = readScreenOnTimeLocked();
+ mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
}
mRealTimeSnapshot = SystemClock.elapsedRealtime();
@@ -221,7 +213,7 @@
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mLock) {
- updateDisplayLocked();
+ mAppIdleHistory.updateDisplayLocked(isDisplayOn(), SystemClock.elapsedRealtime());
}
if (mPendingOneTimeCheckIdleStates) {
@@ -232,6 +224,11 @@
}
}
+ private boolean isDisplayOn() {
+ return mDisplayManager
+ .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+ }
+
private class UserActionsReceiver extends BroadcastReceiver {
@Override
@@ -274,7 +271,8 @@
@Override public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
synchronized (UsageStatsService.this.mLock) {
- updateDisplayLocked();
+ mAppIdleHistory.updateDisplayLocked(isDisplayOn(),
+ SystemClock.elapsedRealtime());
}
}
}
@@ -291,8 +289,25 @@
}
@Override
- public long getAppIdleRollingWindowDurationMillis() {
- return mAppIdleWallclockThresholdMillis * 2;
+ public void onNewUpdate(int userId) {
+ initializeDefaultsForSystemApps(userId);
+ }
+
+ private void initializeDefaultsForSystemApps(int userId) {
+ Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackagesAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+ userId);
+ final int packageCount = packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final PackageInfo pi = packages.get(i);
+ String packageName = pi.packageName;
+ if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
+ mAppIdleHistory.reportUsageLocked(packageName, userId, elapsedRealtime);
+ }
+ }
}
private void cleanUpRemovedUsersLocked() {
@@ -350,7 +365,7 @@
if (timeLeft < 0) {
timeLeft = 0;
}
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft / 10);
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
}
private void postParoleEndTimeout() {
@@ -400,28 +415,27 @@
return;
}
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
for (int i = 0; i < userIds.length; i++) {
final int userId = userIds[i];
List<PackageInfo> packages =
getContext().getPackageManager().getInstalledPackagesAsUser(
- PackageManager.GET_DISABLED_COMPONENTS
- | PackageManager.GET_UNINSTALLED_PACKAGES,
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES,
userId);
synchronized (mLock) {
- final long timeNow = checkAndGetTimeLocked();
- final long screenOnTime = getScreenOnTimeLocked();
- UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId,
- timeNow);
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
final PackageInfo pi = packages.get(p);
final String packageName = pi.packageName;
final boolean isIdle = isAppIdleFiltered(packageName,
UserHandle.getAppId(pi.applicationInfo.uid),
- userId, service, timeNow, screenOnTime);
+ userId, elapsedRealtime);
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
userId, isIdle ? 1 : 0, packageName));
- mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
+ if (isIdle) {
+ mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+ }
}
}
}
@@ -458,62 +472,6 @@
}
}
- void updateDisplayLocked() {
- boolean screenOn = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState()
- == Display.STATE_ON;
-
- if (screenOn == mScreenOn) return;
-
- mScreenOn = screenOn;
- long now = SystemClock.elapsedRealtime();
- if (mScreenOn) {
- mLastScreenOnEventRealtime = now;
- } else {
- mScreenOnTime += now - mLastScreenOnEventRealtime;
- writeScreenOnTimeLocked(mScreenOnTime);
- }
- }
-
- long getScreenOnTimeLocked() {
- long screenOnTime = mScreenOnTime;
- if (mScreenOn) {
- screenOnTime += SystemClock.elapsedRealtime() - mLastScreenOnEventRealtime;
- }
- return screenOnTime;
- }
-
- private File getScreenOnTimeFile() {
- return new File(mUsageStatsDir, UserHandle.USER_SYSTEM + "/screen_on_time");
- }
-
- private long readScreenOnTimeLocked() {
- long screenOnTime = 0;
- File screenOnTimeFile = getScreenOnTimeFile();
- if (screenOnTimeFile.exists()) {
- try {
- BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
- screenOnTime = Long.parseLong(reader.readLine());
- reader.close();
- } catch (IOException | NumberFormatException e) {
- }
- } else {
- writeScreenOnTimeLocked(screenOnTime);
- }
- return screenOnTime;
- }
-
- private void writeScreenOnTimeLocked(long screenOnTime) {
- AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
- FileOutputStream fos = null;
- try {
- fos = screenOnTimeFile.startWrite();
- fos.write(Long.toString(screenOnTime).getBytes());
- screenOnTimeFile.finishWrite(fos);
- } catch (IOException ioe) {
- screenOnTimeFile.failWrite(fos);
- }
- }
-
void onDeviceIdleModeChanged() {
final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
@@ -549,7 +507,7 @@
if (service == null) {
service = new UserUsageStatsService(getContext(), userId,
new File(mUsageStatsDir, Integer.toString(userId)), this);
- service.init(currentTimeMillis, getScreenOnTimeLocked());
+ service.init(currentTimeMillis);
mUserState.put(userId, service);
}
return service;
@@ -569,8 +527,7 @@
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
final UserUsageStatsService service = mUserState.valueAt(i);
- service.onTimeChanged(expectedSystemTime, actualSystemTime, getScreenOnTimeLocked(),
- false);
+ service.onTimeChanged(expectedSystemTime, actualSystemTime);
}
mRealTimeSnapshot = actualRealtime;
mSystemTimeSnapshot = actualSystemTime;
@@ -602,26 +559,26 @@
void reportEvent(UsageEvents.Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
- final long screenOnTime = getScreenOnTimeLocked();
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
convertToSystemTimeLocked(event);
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- final long beginIdleTime = service.getBeginIdleTime(event.mPackage);
- final long lastUsedTime = service.getSystemLastUsedTime(event.mPackage);
- final boolean previouslyIdle = hasPassedIdleTimeoutLocked(beginIdleTime,
- lastUsedTime, screenOnTime, timeNow);
- service.reportEvent(event, screenOnTime);
+ // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
+ // about apps that are on some kind of whitelist anyway.
+ final boolean previouslyIdle = mAppIdleHistory.isIdleLocked(
+ event.mPackage, userId, elapsedRealtime);
+ service.reportEvent(event);
// Inform listeners if necessary
if ((event.mEventType == Event.MOVE_TO_FOREGROUND
|| event.mEventType == Event.MOVE_TO_BACKGROUND
|| event.mEventType == Event.SYSTEM_INTERACTION
|| event.mEventType == Event.USER_INTERACTION)) {
+ mAppIdleHistory.reportUsageLocked(event.mPackage, userId, elapsedRealtime);
if (previouslyIdle) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
/* idle = */ 0, event.mPackage));
notifyBatteryStats(event.mPackage, userId, false);
- mAppIdleHistory.addEntry(event.mPackage, userId, false, timeNow);
}
}
}
@@ -655,28 +612,23 @@
* the threshold for idle.
*/
void forceIdleState(String packageName, int userId, boolean idle) {
+ final int appId = getAppId(packageName);
+ if (appId < 0) return;
synchronized (mLock) {
- final long timeNow = checkAndGetTimeLocked();
- final long screenOnTime = getScreenOnTimeLocked();
- final long deviceUsageTime = screenOnTime - (idle ? mAppIdleDurationMillis : 0) - 5000;
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
- final UserUsageStatsService service =
- getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- final long beginIdleTime = service.getBeginIdleTime(packageName);
- final long lastUsedTime = service.getSystemLastUsedTime(packageName);
- final boolean previouslyIdle = hasPassedIdleTimeoutLocked(beginIdleTime,
- lastUsedTime, screenOnTime, timeNow);
- service.setBeginIdleTime(packageName, deviceUsageTime);
- service.setSystemLastUsedTime(packageName,
- timeNow - (idle ? mAppIdleWallclockThresholdMillis : 0) - 5000);
+ final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
+ userId, elapsedRealtime);
+ mAppIdleHistory.setIdleLocked(packageName, userId, idle, elapsedRealtime);
+ final boolean stillIdle = isAppIdleFiltered(packageName, appId,
+ userId, elapsedRealtime);
// Inform listeners if necessary
- if (previouslyIdle != idle) {
+ if (previouslyIdle != stillIdle) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ idle ? 1 : 0, packageName));
- if (!idle) {
+ /* idle = */ stillIdle ? 1 : 0, packageName));
+ if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
- mAppIdleHistory.addEntry(packageName, userId, idle, timeNow);
}
}
}
@@ -693,10 +645,11 @@
/**
* Called by the Binder stub.
*/
- void removeUser(int userId) {
+ void onUserRemoved(int userId) {
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
+ mAppIdleHistory.onUserRemoved(userId);
cleanUpRemovedUsersLocked();
}
}
@@ -750,29 +703,12 @@
}
}
- private boolean isAppIdleUnfiltered(String packageName, UserUsageStatsService userService,
- long timeNow, long screenOnTime) {
+ private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
synchronized (mLock) {
- long beginIdleTime = userService.getBeginIdleTime(packageName);
- long lastUsedTime = userService.getSystemLastUsedTime(packageName);
- return hasPassedIdleTimeoutLocked(beginIdleTime, lastUsedTime, screenOnTime,
- timeNow);
+ return mAppIdleHistory.isIdleLocked(packageName, userId, elapsedRealtime);
}
}
- /**
- * @param beginIdleTime when the app was last used in device usage timebase
- * @param lastUsedTime wallclock time of when the app was last used
- * @param screenOnTime screen-on timebase time
- * @param currentTime current time in device usage timebase
- * @return whether it's been used far enough in the past to be considered inactive
- */
- boolean hasPassedIdleTimeoutLocked(long beginIdleTime, long lastUsedTime,
- long screenOnTime, long currentTime) {
- return (beginIdleTime <= screenOnTime - mAppIdleDurationMillis)
- && (lastUsedTime <= currentTime - mAppIdleWallclockThresholdMillis);
- }
-
void addListener(AppIdleStateChangeListener listener) {
synchronized (mLock) {
if (!mPackageAccessListeners.contains(listener)) {
@@ -787,32 +723,22 @@
}
}
- boolean isAppIdleFilteredOrParoled(String packageName, int userId, long timeNow) {
+ int getAppId(String packageName) {
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ return ai.uid;
+ } catch (NameNotFoundException re) {
+ return -1;
+ }
+ }
+
+ boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime) {
if (mAppIdleParoled) {
return false;
}
- try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS);
- return isAppIdleFiltered(packageName, ai.uid, userId, timeNow);
- } catch (PackageManager.NameNotFoundException e) {
- }
- return false;
- }
-
- boolean isAppIdleFiltered(String packageName, int uidForAppId, int userId, long timeNow) {
- final UserUsageStatsService userService;
- final long screenOnTime;
- synchronized (mLock) {
- if (timeNow == -1) {
- timeNow = checkAndGetTimeLocked();
- }
- userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- screenOnTime = getScreenOnTimeLocked();
- }
- return isAppIdleFiltered(packageName, UserHandle.getAppId(uidForAppId), userId,
- userService, timeNow, screenOnTime);
+ return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
}
/**
@@ -822,7 +748,7 @@
* Called by interface impls.
*/
private boolean isAppIdleFiltered(String packageName, int appId, int userId,
- UserUsageStatsService userService, long timeNow, long screenOnTime) {
+ long elapsedRealtime) {
if (packageName == null) return false;
// If not enabled at all, of course nobody is ever idle.
if (!mAppIdleEnabled) {
@@ -864,7 +790,7 @@
return false;
}
- return isAppIdleUnfiltered(packageName, userService, timeNow, screenOnTime);
+ return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
int[] getIdleUidsForUser(int userId) {
@@ -872,14 +798,7 @@
return new int[0];
}
- final long timeNow;
- final UserUsageStatsService userService;
- final long screenOnTime;
- synchronized (mLock) {
- timeNow = checkAndGetTimeLocked();
- userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- screenOnTime = getScreenOnTimeLocked();
- }
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
List<ApplicationInfo> apps;
try {
@@ -899,12 +818,12 @@
// Now resolve all app state. Iterating over all apps, keeping track of how many
// we find for each uid and how many of those are idle.
- for (int i = apps.size()-1; i >= 0; i--) {
+ for (int i = apps.size() - 1; i >= 0; i--) {
ApplicationInfo ai = apps.get(i);
// Check whether this app is idle.
boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
- userId, userService, timeNow, screenOnTime);
+ userId, elapsedRealtime);
int index = uidStates.indexOfKey(ai.uid);
if (index < 0) {
@@ -990,8 +909,11 @@
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
+ mAppIdleHistory.writeAppIdleTimesLocked(mUserState.keyAt(i));
}
-
+ // Persist elapsed time periodically, in case screen doesn't get toggled
+ // until the next boot
+ mAppIdleHistory.writeElapsedTimeLocked();
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
@@ -1000,7 +922,6 @@
*/
void dump(String[] args, PrintWriter pw) {
synchronized (mLock) {
- final long screenOnTime = getScreenOnTimeLocked();
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
ArraySet<String> argSet = new ArraySet<>();
argSet.addAll(Arrays.asList(args));
@@ -1011,27 +932,28 @@
idpw.println();
idpw.increaseIndent();
if (argSet.contains("--checkin")) {
- mUserState.valueAt(i).checkin(idpw, screenOnTime);
+ mUserState.valueAt(i).checkin(idpw);
} else {
- mUserState.valueAt(i).dump(idpw, screenOnTime);
+ mUserState.valueAt(i).dump(idpw);
idpw.println();
- if (args.length > 0 && "history".equals(args[0])) {
- mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
+ if (args.length > 0) {
+ if ("history".equals(args[0])) {
+ mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
+ } else if ("flush".equals(args[0])) {
+ UsageStatsService.this.flushToDiskLocked();
+ pw.println("Flushed stats to disk");
+ }
}
}
+ mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
idpw.decreaseIndent();
}
- pw.print("Screen On Timebase: ");
- pw.print(screenOnTime);
- pw.print(" (");
- TimeUtils.formatDuration(screenOnTime, pw);
- pw.println(")");
pw.println();
pw.println("Settings:");
pw.print(" mAppIdleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleDurationMillis, pw);
+ TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
pw.println();
pw.print(" mAppIdleWallclockThresholdMillis=");
@@ -1057,11 +979,6 @@
pw.print("mLastAppIdleParoledTime=");
TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
pw.println();
- pw.print("mScreenOnTime="); TimeUtils.formatDuration(mScreenOnTime, pw);
- pw.println();
- pw.print("mLastScreenOnEventRealtime=");
- TimeUtils.formatDuration(mLastScreenOnEventRealtime, pw);
- pw.println();
}
}
@@ -1082,7 +999,7 @@
break;
case MSG_REMOVE_USER:
- removeUser(msg.arg1);
+ onUserRemoved(msg.arg1);
break;
case MSG_INFORM_LISTENERS:
@@ -1179,13 +1096,13 @@
}
// Default: 12 hours of screen-on time sans dream-time
- mAppIdleDurationMillis = mParser.getLong(KEY_IDLE_DURATION,
+ mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
- mCheckIdleIntervalMillis = Math.min(mAppIdleDurationMillis / 4,
+ mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
// Default: 24 hours between paroles
@@ -1194,6 +1111,8 @@
mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+ mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+ mAppIdleScreenThresholdMillis);
}
}
}
@@ -1284,7 +1203,8 @@
}
final long token = Binder.clearCallingIdentity();
try {
- return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId, -1);
+ return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
+ SystemClock.elapsedRealtime());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1304,11 +1224,9 @@
"No permission to change app idle state");
final long token = Binder.clearCallingIdentity();
try {
- PackageInfo pi = AppGlobals.getPackageManager().getPackageInfo(packageName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (pi == null) return;
+ final int appId = getAppId(packageName);
+ if (appId < 0) return;
UsageStatsService.this.setAppIdle(packageName, idle, userId);
- } catch (RemoteException re) {
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1335,8 +1253,6 @@
}
UsageStatsService.this.dump(args, pw);
}
-
-
}
/**
@@ -1411,7 +1327,8 @@
@Override
public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
- return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId, -1);
+ return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
+ SystemClock.elapsedRealtime());
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index f2ca3a4..c95ff23 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -26,7 +26,6 @@
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
-import android.text.TextUtils;
import java.io.IOException;
import java.net.ProtocolException;
@@ -55,13 +54,11 @@
// Time attributes stored as an offset of the beginTime.
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
- private static final String LAST_TIME_ACTIVE_SYSTEM_ATTR = "lastTimeActiveSystem";
- private static final String BEGIN_IDLE_TIME_ATTR = "beginIdleTime";
private static final String END_TIME_ATTR = "endTime";
private static final String TIME_ATTR = "time";
private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
- throws XmlPullParserException, IOException {
+ throws IOException {
final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
if (pkg == null) {
throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
@@ -72,20 +69,6 @@
// Apply the offset to the beginTime to find the absolute time.
stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
parser, LAST_TIME_ACTIVE_ATTR);
-
- final String lastTimeUsedSystem = parser.getAttributeValue(null,
- LAST_TIME_ACTIVE_SYSTEM_ATTR);
- if (TextUtils.isEmpty(lastTimeUsedSystem)) {
- // If the field isn't present, use the old one.
- stats.mLastTimeSystemUsed = stats.mLastTimeUsed;
- } else {
- stats.mLastTimeSystemUsed = statsOut.beginTime + Long.parseLong(lastTimeUsedSystem);
- }
-
- final String beginIdleTime = parser.getAttributeValue(null, BEGIN_IDLE_TIME_ATTR);
- if (!TextUtils.isEmpty(beginIdleTime)) {
- stats.mBeginIdleTime = Long.parseLong(beginIdleTime);
- }
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
}
@@ -141,13 +124,10 @@
// Write the time offset.
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
usageStats.mLastTimeUsed - stats.beginTime);
- XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_SYSTEM_ATTR,
- usageStats.mLastTimeSystemUsed - stats.beginTime);
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
- XmlUtils.writeLongAttribute(xml, BEGIN_IDLE_TIME_ATTR, usageStats.mBeginIdleTime);
xml.endTag(null, PACKAGE_TAG);
}
@@ -255,7 +235,6 @@
}
xml.endTag(null, PACKAGES_TAG);
-
xml.startTag(null, CONFIGURATIONS_TAG);
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index f2045d3..7d003f3 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -59,7 +59,6 @@
private final Context mContext;
private final UsageStatsDatabase mDatabase;
private final IntervalStats[] mCurrentStats;
- private IntervalStats mAppIdleRollingWindow;
private boolean mStatsChanged = false;
private final UnixCalendar mDailyExpiryDate;
private final StatsUpdatedListener mListener;
@@ -74,7 +73,11 @@
interface StatsUpdatedListener {
void onStatsUpdated();
void onStatsReloaded();
- long getAppIdleRollingWindowDurationMillis();
+ /**
+ * Callback that a system update was detected
+ * @param mUserId user that needs to be initialized
+ */
+ void onNewUpdate(int mUserId);
}
UserUsageStatsService(Context context, int userId, File usageStatsDir,
@@ -88,7 +91,7 @@
mUserId = userId;
}
- void init(final long currentTimeMillis, final long deviceUsageTime) {
+ void init(final long currentTimeMillis) {
mDatabase.init(currentTimeMillis);
int nullCount = 0;
@@ -112,7 +115,7 @@
// By calling loadActiveStats, we will
// generate new stats for each bucket.
- loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
+ loadActiveStats(currentTimeMillis);
} else {
// Set up the expiry date to be one day from the latest daily stat.
// This may actually be today and we will rollover on the first event
@@ -136,54 +139,18 @@
stat.updateConfigurationStats(null, stat.lastTimeSaved);
}
- refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
-
if (mDatabase.isNewUpdate()) {
- initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
- mDatabase.isFirstUpdate());
+ notifyNewUpdate();
}
}
- /**
- * If any of the apps don't have a last-used entry, add one now.
- * @param currentTimeMillis the current time
- * @param firstUpdate if it is the first update, touch all installed apps, otherwise only
- * touch the system apps
- */
- private void initializeDefaultsForApps(long currentTimeMillis, long deviceUsageTime,
- boolean firstUpdate) {
- PackageManager pm = mContext.getPackageManager();
- List<PackageInfo> packages = pm.getInstalledPackagesAsUser(0, mUserId);
- final int packageCount = packages.size();
- for (int i = 0; i < packageCount; i++) {
- final PackageInfo pi = packages.get(i);
- String packageName = pi.packageName;
- if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
- && getBeginIdleTime(packageName) == -1) {
- for (IntervalStats stats : mCurrentStats) {
- stats.update(packageName, currentTimeMillis, Event.SYSTEM_INTERACTION);
- stats.updateBeginIdleTime(packageName, deviceUsageTime);
- }
-
- mAppIdleRollingWindow.update(packageName, currentTimeMillis,
- Event.SYSTEM_INTERACTION);
- mAppIdleRollingWindow.updateBeginIdleTime(packageName, deviceUsageTime);
- mStatsChanged = true;
- }
- }
- // Persist the new OTA-related access stats.
- persistActiveStats();
- }
-
- void onTimeChanged(long oldTime, long newTime, long deviceUsageTime,
- boolean resetBeginIdleTime) {
+ void onTimeChanged(long oldTime, long newTime) {
persistActiveStats();
mDatabase.onTimeChanged(newTime - oldTime);
- loadActiveStats(newTime, resetBeginIdleTime);
- refreshAppIdleRollingWindow(newTime, deviceUsageTime);
+ loadActiveStats(newTime);
}
- void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
+ void reportEvent(UsageEvents.Event event) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
+ "[" + event.mTimeStamp + "]: "
@@ -192,7 +159,7 @@
if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
// Need to rollover
- rolloverStats(event.mTimeStamp, deviceUsageTime);
+ rolloverStats(event.mTimeStamp);
}
final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
@@ -218,35 +185,9 @@
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
} else {
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
- stats.updateBeginIdleTime(event.mPackage, deviceUsageTime);
}
}
- if (event.mEventType != Event.CONFIGURATION_CHANGE) {
- mAppIdleRollingWindow.update(event.mPackage, event.mTimeStamp, event.mEventType);
- mAppIdleRollingWindow.updateBeginIdleTime(event.mPackage, deviceUsageTime);
- }
-
- notifyStatsChanged();
- }
-
- /**
- * Sets the beginIdleTime for each of the intervals.
- * @param beginIdleTime
- */
- void setBeginIdleTime(String packageName, long beginIdleTime) {
- for (IntervalStats stats : mCurrentStats) {
- stats.updateBeginIdleTime(packageName, beginIdleTime);
- }
- mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime);
- notifyStatsChanged();
- }
-
- void setSystemLastUsedTime(String packageName, long lastUsedTime) {
- for (IntervalStats stats : mCurrentStats) {
- stats.updateSystemLastUsedTime(packageName, lastUsedTime);
- }
- mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime);
notifyStatsChanged();
}
@@ -404,24 +345,6 @@
return new UsageEvents(results, table);
}
- long getBeginIdleTime(String packageName) {
- UsageStats packageUsage;
- if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
- return -1;
- } else {
- return packageUsage.getBeginIdleTime();
- }
- }
-
- long getSystemLastUsedTime(String packageName) {
- UsageStats packageUsage;
- if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
- return -1;
- } else {
- return packageUsage.getLastTimeSystemUsed();
- }
- }
-
void persistActiveStats() {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
@@ -436,7 +359,7 @@
}
}
- private void rolloverStats(final long currentTimeMillis, final long deviceUsageTime) {
+ private void rolloverStats(final long currentTimeMillis) {
final long startTime = SystemClock.elapsedRealtime();
Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
@@ -463,7 +386,7 @@
persistActiveStats();
mDatabase.prune(currentTimeMillis);
- loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
+ loadActiveStats(currentTimeMillis);
final int continueCount = continuePreviousDay.size();
for (int i = 0; i < continueCount; i++) {
@@ -477,8 +400,6 @@
}
persistActiveStats();
- refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
-
final long totalTime = SystemClock.elapsedRealtime() - startTime;
Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
+ " milliseconds");
@@ -491,7 +412,11 @@
}
}
- private void loadActiveStats(final long currentTimeMillis, boolean resetBeginIdleTime) {
+ private void notifyNewUpdate() {
+ mListener.onNewUpdate(mUserId);
+ }
+
+ private void loadActiveStats(final long currentTimeMillis) {
for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
@@ -514,12 +439,6 @@
mCurrentStats[intervalType].beginTime = currentTimeMillis;
mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
}
-
- if (resetBeginIdleTime) {
- for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) {
- usageStats.mBeginIdleTime = 0;
- }
- }
}
mStatsChanged = false;
@@ -538,96 +457,28 @@
mDailyExpiryDate.getTimeInMillis() + ")");
}
- private static void mergePackageStats(IntervalStats dst, IntervalStats src,
- final long deviceUsageTime) {
- dst.endTime = Math.max(dst.endTime, src.endTime);
-
- final int srcPackageCount = src.packageStats.size();
- for (int i = 0; i < srcPackageCount; i++) {
- final String packageName = src.packageStats.keyAt(i);
- final UsageStats srcStats = src.packageStats.valueAt(i);
- UsageStats dstStats = dst.packageStats.get(packageName);
- if (dstStats == null) {
- dstStats = new UsageStats(srcStats);
- dst.packageStats.put(packageName, dstStats);
- } else {
- dstStats.add(src.packageStats.valueAt(i));
- }
-
- // App idle times can not begin in the future. This happens if we had a time change.
- if (dstStats.mBeginIdleTime > deviceUsageTime) {
- dstStats.mBeginIdleTime = deviceUsageTime;
- }
- }
- }
-
- /**
- * App idle operates on a rolling window of time. When we roll over time, we end up with a
- * period of time where in-memory stats are empty and we don't hit the disk for older stats
- * for performance reasons. Suddenly all apps will become idle.
- *
- * Instead, at times we do a deep query to find all the apps that have run in the past few
- * days and keep the cached data up to date.
- *
- * @param currentTimeMillis
- */
- void refreshAppIdleRollingWindow(final long currentTimeMillis, final long deviceUsageTime) {
- // Start the rolling window for AppIdle requests.
- final long startRangeMillis = currentTimeMillis -
- mListener.getAppIdleRollingWindowDurationMillis();
-
- List<IntervalStats> stats = mDatabase.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
- startRangeMillis, currentTimeMillis, new StatCombiner<IntervalStats>() {
- @Override
- public void combine(IntervalStats stats, boolean mutable,
- List<IntervalStats> accumulatedResult) {
- IntervalStats accum;
- if (accumulatedResult.isEmpty()) {
- accum = new IntervalStats();
- accum.beginTime = stats.beginTime;
- accumulatedResult.add(accum);
- } else {
- accum = accumulatedResult.get(0);
- }
-
- mergePackageStats(accum, stats, deviceUsageTime);
- }
- });
-
- if (stats == null || stats.isEmpty()) {
- mAppIdleRollingWindow = new IntervalStats();
- mergePackageStats(mAppIdleRollingWindow,
- mCurrentStats[UsageStatsManager.INTERVAL_YEARLY], deviceUsageTime);
- } else {
- mAppIdleRollingWindow = stats.get(0);
- }
- }
-
//
// -- DUMP related methods --
//
- void checkin(final IndentingPrintWriter pw, final long screenOnTime) {
+ void checkin(final IndentingPrintWriter pw) {
mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
@Override
public boolean checkin(IntervalStats stats) {
- printIntervalStats(pw, stats, screenOnTime, false);
+ printIntervalStats(pw, stats, false);
return true;
}
});
}
- void dump(IndentingPrintWriter pw, final long screenOnTime) {
+ void dump(IndentingPrintWriter pw) {
// This is not a check-in, only dump in-memory stats.
for (int interval = 0; interval < mCurrentStats.length; interval++) {
pw.print("In-memory ");
pw.print(intervalToString(interval));
pw.println(" stats");
- printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
+ printIntervalStats(pw, mCurrentStats[interval], true);
}
-
- pw.println("AppIdleRollingWindow cache");
- printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true);
}
private String formatDateTime(long dateTime, boolean pretty) {
@@ -644,7 +495,7 @@
return Long.toString(elapsedTime);
}
- void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, long screenOnTime,
+ void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
boolean prettyDates) {
if (prettyDates) {
pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
@@ -665,10 +516,6 @@
pw.printPair("totalTime",
formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
- pw.printPair("lastTimeSystem",
- formatDateTime(usageStats.mLastTimeSystemUsed, prettyDates));
- pw.printPair("inactiveTime",
- formatElapsedTime(screenOnTime - usageStats.mBeginIdleTime, prettyDates));
pw.println();
}
pw.decreaseIndent();
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
index 13efc36..fa666af 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright (C) 2016 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
+ * 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,
@@ -26,7 +26,7 @@
oneway interface IWifiNanEventListener
{
void onConfigCompleted(in ConfigRequest completedConfig);
- void onConfigFailed(int reason);
+ void onConfigFailed(in ConfigRequest failedConfig, int reason);
void onNanDown(int reason);
void onIdentityChanged();
}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
index ec9e462..f382d97 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright (C) 2016 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
+ * 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,
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
index 50c34d9..d60d8ca 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright (C) 2016 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
+ * 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,
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
index eae0a55..5c18bd7 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
@@ -47,7 +47,8 @@
/**
* Configuration failed callback event registration flag. Corresponding
- * callback is {@link WifiNanEventListener#onConfigFailed(int)}.
+ * callback is
+ * {@link WifiNanEventListener#onConfigFailed(ConfigRequest, int)}.
*/
public static final int LISTEN_CONFIG_FAILED = 0x1 << 1;
@@ -93,7 +94,7 @@
WifiNanEventListener.this.onConfigCompleted((ConfigRequest) msg.obj);
break;
case LISTEN_CONFIG_FAILED:
- WifiNanEventListener.this.onConfigFailed(msg.arg1);
+ WifiNanEventListener.this.onConfigFailed((ConfigRequest) msg.obj, msg.arg1);
break;
case LISTEN_NAN_DOWN:
WifiNanEventListener.this.onNanDown(msg.arg1);
@@ -129,7 +130,7 @@
*
* @param reason Failure reason code, see {@code NanSessionListener.FAIL_*}.
*/
- public void onConfigFailed(int reason) {
+ public void onConfigFailed(ConfigRequest failedConfig, int reason) {
Log.w(TAG, "onConfigFailed: called in stub - override if interested or disable");
}
@@ -173,11 +174,14 @@
}
@Override
- public void onConfigFailed(int reason) {
- if (VDBG) Log.v(TAG, "onConfigFailed: reason=" + reason);
+ public void onConfigFailed(ConfigRequest failedConfig, int reason) {
+ if (VDBG) {
+ Log.v(TAG, "onConfigFailed: failedConfig=" + failedConfig + ", reason=" + reason);
+ }
Message msg = mHandler.obtainMessage(LISTEN_CONFIG_FAILED);
msg.arg1 = reason;
+ msg.obj = failedConfig;
mHandler.sendMessage(msg);
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
index d5e59f0..0925087 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
@@ -303,8 +303,8 @@
* message). Override to implement your custom response.
* <p>
* Note that either this callback or
- * {@link WifiNanSessionListener#onMessageSendFail(int)} will be received -
- * never both.
+ * {@link WifiNanSessionListener#onMessageSendFail(int, int)} will be
+ * received - never both.
*/
public void onMessageSendSuccess(int messageId) {
if (VDBG) Log.v(TAG, "onMessageSendSuccess: called in stub - override if interested");
@@ -319,8 +319,8 @@
* message). Override to implement your custom response.
* <p>
* Note that either this callback or
- * {@link WifiNanSessionListener#onMessageSendSuccess()} will be received -
- * never both
+ * {@link WifiNanSessionListener#onMessageSendSuccess(int)} will be received
+ * - never both
*
* @param reason The failure reason using {@code NanSessionListener.FAIL_*}
* codes.