Merge "Enable checkstyle for all frameworks/base projects."
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index e224fa3..35d3802 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -245,10 +245,11 @@
state.pauseTiming();
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -261,10 +262,11 @@
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -277,10 +279,11 @@
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -293,11 +296,12 @@
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -310,11 +314,12 @@
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -328,10 +333,11 @@
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -345,10 +351,11 @@
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -362,11 +369,12 @@
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -380,11 +388,12 @@
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
diff --git a/api/current.txt b/api/current.txt
index cc38630..f27e83c 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -42607,6 +42607,7 @@
field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
+ field public static final java.lang.String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final java.lang.String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -51428,6 +51429,99 @@
package android.view.textclassifier {
+ public final class ConversationActions implements android.os.Parcelable {
+ ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>);
+ method public int describeContents();
+ method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR;
+ field public static final java.lang.String HINT_FOR_IN_APP = "in_app";
+ field public static final java.lang.String HINT_FOR_NOTIFICATION = "notification";
+ field public static final java.lang.String TYPE_CALL_PHONE = "call_phone";
+ field public static final java.lang.String TYPE_CREATE_REMINDER = "create_reminder";
+ field public static final java.lang.String TYPE_OPEN_URL = "open_url";
+ field public static final java.lang.String TYPE_SEND_EMAIL = "send_email";
+ field public static final java.lang.String TYPE_SEND_SMS = "send_sms";
+ field public static final java.lang.String TYPE_SHARE_LOCATION = "share_location";
+ field public static final java.lang.String TYPE_TEXT_REPLY = "text_reply";
+ field public static final java.lang.String TYPE_TRACK_FLIGHT = "track_flight";
+ field public static final java.lang.String TYPE_VIEW_CALENDAR = "view_calendar";
+ field public static final java.lang.String TYPE_VIEW_MAP = "view_map";
+ }
+
+ public static final class ConversationActions.ConversationAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.app.RemoteAction getAction();
+ method public float getConfidenceScore();
+ method public android.os.Bundle getExtras();
+ method public java.lang.CharSequence getTextReply();
+ method public java.lang.String getType();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.ConversationAction> CREATOR;
+ }
+
+ public static final class ConversationActions.ConversationAction.Builder {
+ ctor public ConversationActions.ConversationAction.Builder(java.lang.String);
+ method public android.view.textclassifier.ConversationActions.ConversationAction build();
+ method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setAction(android.app.RemoteAction);
+ method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setConfidenceScore(float);
+ method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setExtras(android.os.Bundle);
+ method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setTextReply(java.lang.CharSequence);
+ }
+
+ public static final class ConversationActions.Message implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.app.Person getAuthor();
+ method public android.os.Bundle getExtras();
+ method public java.lang.CharSequence getText();
+ method public java.time.ZonedDateTime getTime();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
+ }
+
+ public static final class ConversationActions.Message.Builder {
+ ctor public ConversationActions.Message.Builder();
+ method public android.view.textclassifier.ConversationActions.Message build();
+ method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person);
+ method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime);
+ method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle);
+ method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence);
+ }
+
+ public static final class ConversationActions.Request implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation();
+ method public java.util.List<java.lang.String> getHints();
+ method public int getMaxSuggestions();
+ method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Request> CREATOR;
+ }
+
+ public static final class ConversationActions.Request.Builder {
+ ctor public ConversationActions.Request.Builder(java.util.List<android.view.textclassifier.ConversationActions.Message>);
+ method public android.view.textclassifier.ConversationActions.Request build();
+ method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>);
+ method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int);
+ method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig);
+ }
+
+ public static final class ConversationActions.TypeConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.Collection<java.lang.String> resolveTypes(java.util.Collection<java.lang.String>);
+ method public boolean shouldIncludeTypesFromTextClassifier();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.TypeConfig> CREATOR;
+ }
+
+ public static final class ConversationActions.TypeConfig.Builder {
+ ctor public ConversationActions.TypeConfig.Builder();
+ method public android.view.textclassifier.ConversationActions.TypeConfig build();
+ method public android.view.textclassifier.ConversationActions.TypeConfig.Builder includeTypesFromTextClassifier(boolean);
+ method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setExcludedTypes(java.util.Collection<java.lang.String>);
+ method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setIncludedTypes(java.util.Collection<java.lang.String>);
+ }
+
public final class SelectionEvent implements android.os.Parcelable {
method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int);
method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
@@ -51482,6 +51576,7 @@
method public float getConfidenceScore(java.lang.String);
method public java.lang.String getEntity(int);
method public int getEntityCount();
+ method public android.os.Bundle getExtras();
method public deprecated android.graphics.drawable.Drawable getIcon();
method public java.lang.String getId();
method public deprecated android.content.Intent getIntent();
@@ -51497,6 +51592,7 @@
method public android.view.textclassifier.TextClassification.Builder addAction(android.app.RemoteAction);
method public android.view.textclassifier.TextClassification build();
method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassification.Builder setExtras(android.os.Bundle);
method public deprecated android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
method public android.view.textclassifier.TextClassification.Builder setId(java.lang.String);
method public deprecated android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
@@ -51509,6 +51605,7 @@
method public int describeContents();
method public android.os.LocaleList getDefaultLocales();
method public int getEndIndex();
+ method public android.os.Bundle getExtras();
method public java.time.ZonedDateTime getReferenceTime();
method public int getStartIndex();
method public java.lang.CharSequence getText();
@@ -51520,6 +51617,7 @@
ctor public TextClassification.Request.Builder(java.lang.CharSequence, int, int);
method public android.view.textclassifier.TextClassification.Request build();
method public android.view.textclassifier.TextClassification.Request.Builder setDefaultLocales(android.os.LocaleList);
+ method public android.view.textclassifier.TextClassification.Request.Builder setExtras(android.os.Bundle);
method public android.view.textclassifier.TextClassification.Request.Builder setReferenceTime(java.time.ZonedDateTime);
}
@@ -51564,6 +51662,7 @@
method public default int getMaxGenerateLinksTextLength();
method public default boolean isDestroyed();
method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent);
+ method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request);
method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
diff --git a/api/system-current.txt b/api/system-current.txt
index 0d7cad12..b1cbfa6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -871,9 +871,11 @@
method public int getAppStandbyBucket(java.lang.String);
method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
method public void registerAppUsageObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, android.app.PendingIntent);
+ method public void registerUsageSessionObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, android.app.PendingIntent, android.app.PendingIntent);
method public void setAppStandbyBucket(java.lang.String, int);
method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>);
method public void unregisterAppUsageObserver(int);
+ method public void unregisterUsageSessionObserver(int);
method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle);
field public static final java.lang.String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID";
field public static final java.lang.String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 51d05bb..ca049b0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -2540,10 +2540,17 @@
* Binder stats will be reset every time the data is pulled. It means it can only be pulled by one
* config on the device.
*
- * Next tag: 14
+ * Next tag: 15
*/
message BinderCalls {
+ // UID of the process responsible for the binder transaction. It will be set if the process
+ // executing the binder transaction attribute the transaction to another uid using
+ // Binder.setThreadWorkSource().
+ //
+ // If not set, the value will be -1.
optional int32 uid = 1 [(is_uid) = true];
+ // UID of the process executing the binder transaction.
+ optional int32 direct_caller_uid = 14;
// Fully qualified class name of the API call.
//
// This is a system server class name.
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 9713527..4d52263 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -51,4 +51,8 @@
void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
in PendingIntent callback, String callingPackage);
void unregisterAppUsageObserver(int observerId, String callingPackage);
+ void registerUsageSessionObserver(int sessionObserverId, in String[] observed, long timeLimitMs,
+ long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
+ in PendingIntent sessionEndCallbackIntent, String callingPackage);
+ void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index dbb00eb..6d7400e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -595,7 +596,7 @@
* exceeded by the group of apps. The delivered Intent will also contain
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
* {@link #EXTRA_TIME_USED}. Cannot be null.
- * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
* is not the profile owner of this user.
*/
@SystemApi
@@ -616,7 +617,7 @@
* to any observer registered by this application. Unregistering an observer that was already
* unregistered or never registered will have no effect.
* @param observerId The id of the observer that was previously registered.
- * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and is
* not the profile owner of this user.
*/
@SystemApi
@@ -629,6 +630,81 @@
}
}
+ /**
+ * Register a usage session observer that receives a callback on the provided {@code
+ * limitReachedCallbackIntent} when the sum of usages of apps in the packages array exceeds
+ * the {@code timeLimit} specified within a usage session. After the {@code timeLimit} has
+ * been reached, the usage session observer will receive a callback on the provided {@code
+ * sessionEndCallbackIntent} when the usage session ends. Registering another session
+ * observer against a {@code sessionObserverId} that has already been registered will
+ * override the previous session observer.
+ *
+ * @param sessionObserverId A unique id associated with the group of apps to be
+ * monitored. There can be multiple groups with common
+ * packages and different time limits.
+ * @param packages The list of packages to observe for foreground activity time. Cannot be null
+ * and must include at least one package.
+ * @param timeLimit The total time the set of apps can be used continuously before the {@code
+ * limitReachedCallbackIntent} is delivered. Must be at least one minute.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param sessionThresholdTime The time that can take place between usage sessions before the
+ * next session is considered a new session. Must be non-negative.
+ * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
+ * Cannot be null.
+ * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
+ * time limit is exceeded by the group of apps. The delivered
+ * Intent will also contain the extras {@link
+ * #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
+ * #EXTRA_TIME_USED}. Cannot be null.
+ * @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the
+ * session has ended after the time limit has been exceeded. The
+ * session is considered at its end after the {@code observed}
+ * usage has stopped and an additional {@code
+ * sessionThresholdTime} has passed. The delivered Intent will
+ * also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
+ * #EXTRA_TIME_USED}. Can be null.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void registerUsageSessionObserver(int sessionObserverId, @NonNull String[] packages,
+ long timeLimit, @NonNull TimeUnit timeUnit, long sessionThresholdTime,
+ @NonNull TimeUnit sessionThresholdTimeUnit,
+ @NonNull PendingIntent limitReachedCallbackIntent,
+ @Nullable PendingIntent sessionEndCallbackIntent) {
+ try {
+ mService.registerUsageSessionObserver(sessionObserverId, packages,
+ timeUnit.toMillis(timeLimit),
+ sessionThresholdTimeUnit.toMillis(sessionThresholdTime),
+ limitReachedCallbackIntent, sessionEndCallbackIntent,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister the usage session observer specified by the {@code sessionObserverId}. This will
+ * only apply to any app session observer registered by this application. Unregistering an
+ * observer that was already unregistered or never registered will have no effect.
+ *
+ * @param sessionObserverId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void unregisterUsageSessionObserver(int sessionObserverId) {
+ try {
+ mService.unregisterUsageSessionObserver(sessionObserverId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index 7559cd5..b539414 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -31,8 +31,8 @@
void close();
// Registers a PendingIntent with the client
- boolean registerIntent(in PendingIntent intent, long nanoAppId);
+ boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);
// Unregisters a PendingIntent from the client
- boolean unregisterIntent(in PendingIntent intent);
+ boolean unregisterIntent(in PendingIntent pendingIntent);
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e270fc2..5447f59 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -811,14 +811,19 @@
* packet needs to be subtracted from the root UID on the base interface both for tx
* and rx traffic (http://b/12249687, http:/b/33681750).
*
+ * As for eBPF, the per uid stats is collected by different hook, the rx packets on base
+ * interface will not be counted. Thus, the adjustment on root uid is only needed in tx
+ * direction.
+ *
* <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
* {@code ConcurrentHashMap}
* @param baseTraffic Traffic on the base interfaces. Will be mutated.
* @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @param useBpfStats True if eBPF is in use.
*/
public static void apply464xlatAdjustments(NetworkStats baseTraffic,
- NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
+ NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
// Total 464xlat traffic to subtract from uid 0 on all base interfaces.
// stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
@@ -837,15 +842,20 @@
continue;
}
// Subtract any 464lat traffic seen for the root UID on the current base interface.
+ // However, for eBPF, the per uid stats is collected by different hook, the rx packets
+ // on base interface will not be counted. Thus, the adjustment on root uid is only
+ // needed in tx direction.
adjust.iface = baseIface;
- adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ if (!useBpfStats) {
+ adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ adjust.rxPackets = -entry.rxPackets;
+ }
adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
- adjust.rxPackets = -entry.rxPackets;
adjust.txPackets = -entry.txPackets;
adjustments.combineValues(adjust);
- // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
- // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+ // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
+ // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after
// unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
// difference for all packets (http://b/12249687, http:/b/33681750).
entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
@@ -864,8 +874,8 @@
* base and stacked traffic.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
*/
- public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
- apply464xlatAdjustments(this, this, stackedIfaces);
+ public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
+ apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
}
/**
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 7af9db8..7f1082d 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -17,7 +17,6 @@
package android.service.textclassifier;
import android.Manifest;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -98,8 +97,7 @@
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onSuggestSelection(
- request.getText(), request.getStartIndex(), request.getEndIndex(),
- TextSelection.Options.from(sessionId, request), mCancellationSignal,
+ sessionId, request, mCancellationSignal,
new Callback<TextSelection>() {
@Override
public void onSuccess(TextSelection result) {
@@ -132,8 +130,7 @@
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onClassifyText(
- request.getText(), request.getStartIndex(), request.getEndIndex(),
- TextClassification.Options.from(sessionId, request), mCancellationSignal,
+ sessionId, request, mCancellationSignal,
new Callback<TextClassification>() {
@Override
public void onSuccess(TextClassification result) {
@@ -164,7 +161,7 @@
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onGenerateLinks(
- request.getText(), TextLinks.Options.from(sessionId, request),
+ sessionId, request,
mCancellationSignal,
new Callback<TextLinks>() {
@Override
@@ -238,25 +235,6 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<TextSelection> callback);
- // TODO: Remove once apps can build against the latest sdk.
- /** @hide */
- public void onSuggestSelection(
- @NonNull CharSequence text,
- @IntRange(from = 0) int selectionStartIndex,
- @IntRange(from = 0) int selectionEndIndex,
- @Nullable TextSelection.Options options,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull Callback<TextSelection> callback) {
- final TextClassificationSessionId sessionId = options.getSessionId();
- final TextSelection.Request request = options.getRequest() != null
- ? options.getRequest()
- : new TextSelection.Request.Builder(
- text, selectionStartIndex, selectionEndIndex)
- .setDefaultLocales(options.getDefaultLocales())
- .build();
- onSuggestSelection(sessionId, request, cancellationSignal, callback);
- }
-
/**
* Classifies the specified text and returns a {@link TextClassification} object that can be
* used to generate a widget for handling the classified text.
@@ -272,26 +250,6 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<TextClassification> callback);
- // TODO: Remove once apps can build against the latest sdk.
- /** @hide */
- public void onClassifyText(
- @NonNull CharSequence text,
- @IntRange(from = 0) int startIndex,
- @IntRange(from = 0) int endIndex,
- @Nullable TextClassification.Options options,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull Callback<TextClassification> callback) {
- final TextClassificationSessionId sessionId = options.getSessionId();
- final TextClassification.Request request = options.getRequest() != null
- ? options.getRequest()
- : new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(options.getDefaultLocales())
- .setReferenceTime(options.getReferenceTime())
- .build();
- onClassifyText(sessionId, request, cancellationSignal, callback);
- }
-
/**
* Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
* links information.
@@ -307,23 +265,6 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<TextLinks> callback);
- // TODO: Remove once apps can build against the latest sdk.
- /** @hide */
- public void onGenerateLinks(
- @NonNull CharSequence text,
- @Nullable TextLinks.Options options,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull Callback<TextLinks> callback) {
- final TextClassificationSessionId sessionId = options.getSessionId();
- final TextLinks.Request request = options.getRequest() != null
- ? options.getRequest()
- : new TextLinks.Request.Builder(text)
- .setDefaultLocales(options.getDefaultLocales())
- .setEntityConfig(options.getEntityConfig())
- .build();
- onGenerateLinks(sessionId, request, cancellationSignal, callback);
- }
-
/**
* Writes the selection event.
* This is called when a selection event occurs. e.g. user changed selection; or smart selection
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
new file mode 100644
index 0000000..5fcf227
--- /dev/null
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -0,0 +1,779 @@
+/*
+ * Copyright (C) 2018 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.textclassifier;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.Person;
+import android.app.RemoteAction;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannedString;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
+ *
+ * @see TextClassifier#suggestConversationActions(Request)
+ */
+public final class ConversationActions implements Parcelable {
+
+ public static final Creator<ConversationActions> CREATOR =
+ new Creator<ConversationActions>() {
+ @Override
+ public ConversationActions createFromParcel(Parcel in) {
+ return new ConversationActions(in);
+ }
+
+ @Override
+ public ConversationActions[] newArray(int size) {
+ return new ConversationActions[size];
+ }
+ };
+
+ /** @hide */
+ @Retention(SOURCE)
+ @StringDef(
+ value = {
+ TYPE_VIEW_CALENDAR,
+ TYPE_VIEW_MAP,
+ TYPE_TRACK_FLIGHT,
+ TYPE_OPEN_URL,
+ TYPE_SEND_SMS,
+ TYPE_CALL_PHONE,
+ TYPE_SEND_EMAIL,
+ TYPE_TEXT_REPLY,
+ TYPE_CREATE_REMINDER,
+ TYPE_SHARE_LOCATION
+ },
+ prefix = "TYPE_")
+ public @interface ActionType {}
+
+ /**
+ * Indicates an action to view a calendar at a specified time.
+ */
+ public static final String TYPE_VIEW_CALENDAR = "view_calendar";
+ /**
+ * Indicates an action to view the map at a specified location.
+ */
+ public static final String TYPE_VIEW_MAP = "view_map";
+ /**
+ * Indicates an action to track a flight.
+ */
+ public static final String TYPE_TRACK_FLIGHT = "track_flight";
+ /**
+ * Indicates an action to open an URL.
+ */
+ public static final String TYPE_OPEN_URL = "open_url";
+ /**
+ * Indicates an action to send a SMS.
+ */
+ public static final String TYPE_SEND_SMS = "send_sms";
+ /**
+ * Indicates an action to call a phone number.
+ */
+ public static final String TYPE_CALL_PHONE = "call_phone";
+ /**
+ * Indicates an action to send an email.
+ */
+ public static final String TYPE_SEND_EMAIL = "send_email";
+ /**
+ * Indicates an action to reply with a text message.
+ */
+ public static final String TYPE_TEXT_REPLY = "text_reply";
+ /**
+ * Indicates an action to create a reminder.
+ */
+ public static final String TYPE_CREATE_REMINDER = "create_reminder";
+ /**
+ * Indicates an action to reply with a location.
+ */
+ public static final String TYPE_SHARE_LOCATION = "share_location";
+
+ /** @hide */
+ @Retention(SOURCE)
+ @StringDef(
+ value = {
+ HINT_FOR_NOTIFICATION,
+ HINT_FOR_IN_APP,
+ },
+ prefix = "HINT_")
+ public @interface Hint {}
+ /**
+ * To indicate the generated actions will be used within the app.
+ */
+ public static final String HINT_FOR_IN_APP = "in_app";
+ /**
+ * To indicate the generated actions will be used for notification.
+ */
+ public static final String HINT_FOR_NOTIFICATION = "notification";
+
+ private List<ConversationAction> mConversationActions;
+
+ /** Constructs a {@link ConversationActions} object. */
+ public ConversationActions(@NonNull List<ConversationAction> conversationActions) {
+ mConversationActions =
+ Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions));
+ }
+
+ private ConversationActions(Parcel in) {
+ mConversationActions =
+ Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeTypedList(mConversationActions);
+ }
+
+ /** Returns an immutable list of {@link ConversationAction} objects. */
+ @NonNull
+ public List<ConversationAction> getConversationActions() {
+ return mConversationActions;
+ }
+
+ /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
+ public static final class ConversationAction implements Parcelable {
+
+ public static final Creator<ConversationAction> CREATOR =
+ new Creator<ConversationAction>() {
+ @Override
+ public ConversationAction createFromParcel(Parcel in) {
+ return new ConversationAction(in);
+ }
+
+ @Override
+ public ConversationAction[] newArray(int size) {
+ return new ConversationAction[size];
+ }
+ };
+
+ @NonNull
+ @ActionType
+ private final String mType;
+ @NonNull
+ private final CharSequence mTextReply;
+ @Nullable
+ private final RemoteAction mAction;
+
+ @FloatRange(from = 0, to = 1)
+ private final float mScore;
+
+ @NonNull
+ private final Bundle mExtras;
+
+ private ConversationAction(
+ @NonNull String type,
+ @Nullable RemoteAction action,
+ @Nullable CharSequence textReply,
+ float score,
+ @NonNull Bundle extras) {
+ mType = Preconditions.checkNotNull(type);
+ mAction = action;
+ mTextReply = textReply;
+ mScore = score;
+ mExtras = Preconditions.checkNotNull(extras);
+ }
+
+ private ConversationAction(Parcel in) {
+ mType = in.readString();
+ mAction = in.readParcelable(null);
+ mTextReply = in.readCharSequence();
+ mScore = in.readFloat();
+ mExtras = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mType);
+ parcel.writeParcelable(mAction, flags);
+ parcel.writeCharSequence(mTextReply);
+ parcel.writeFloat(mScore);
+ parcel.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @ActionType
+ /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */
+ public String getType() {
+ return mType;
+ }
+
+ @Nullable
+ /**
+ * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for
+ * the specified action type.
+ */
+ public RemoteAction getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns the confidence score for the specified action. The value ranges from 0 (low
+ * confidence) to 1 (high confidence).
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getConfidenceScore() {
+ return mScore;
+ }
+
+ /**
+ * Returns the text reply that could be sent as a reply to the given conversation.
+ * <p>
+ * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}.
+ */
+ @Nullable
+ public CharSequence getTextReply() {
+ return mTextReply;
+ }
+
+ /**
+ * Returns the extended data related to this conversation action.
+ *
+ * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+ * prefer to hold a reference to the returned bundle rather than frequently calling this
+ * method.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras.deepCopy();
+ }
+
+ /** Builder class to construct {@link ConversationAction}. */
+ public static final class Builder {
+ @Nullable
+ @ActionType
+ private String mType;
+ @Nullable
+ private RemoteAction mAction;
+ @Nullable
+ private CharSequence mTextReply;
+ private float mScore;
+ @Nullable
+ private Bundle mExtras;
+
+ public Builder(@NonNull @ActionType String actionType) {
+ mType = Preconditions.checkNotNull(actionType);
+ }
+
+ /**
+ * Sets an action that may be performed on the given conversation.
+ */
+ @NonNull
+ public Builder setAction(@Nullable RemoteAction action) {
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Sets a text reply that may be performed on the given conversation.
+ */
+ @NonNull
+ public Builder setTextReply(@Nullable CharSequence textReply) {
+ mTextReply = textReply;
+ return this;
+ }
+
+ /** Sets the confident score. */
+ @NonNull
+ public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) {
+ mScore = score;
+ return this;
+ }
+
+ /**
+ * Sets the extended data for the conversation action object.
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Builds the {@link ConversationAction} object. */
+ @NonNull
+ public ConversationAction build() {
+ return new ConversationAction(
+ mType,
+ mAction,
+ mTextReply,
+ mScore,
+ mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
+ }
+ }
+ }
+
+ /** Represents a message in the conversation. */
+ public static final class Message implements Parcelable {
+ @Nullable
+ private final Person mAuthor;
+ @Nullable
+ private final ZonedDateTime mComposeTime;
+ @Nullable
+ private final CharSequence mText;
+ @NonNull
+ private final Bundle mExtras;
+
+ private Message(
+ @Nullable Person author,
+ @Nullable ZonedDateTime composeTime,
+ @Nullable CharSequence text,
+ @NonNull Bundle bundle) {
+ mAuthor = author;
+ mComposeTime = composeTime;
+ mText = text;
+ mExtras = Preconditions.checkNotNull(bundle);
+ }
+
+ private Message(Parcel in) {
+ mAuthor = in.readParcelable(null);
+ mComposeTime =
+ in.readInt() == 0
+ ? null
+ : ZonedDateTime.parse(
+ in.readString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
+ mText = in.readCharSequence();
+ mExtras = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mAuthor, flags);
+ parcel.writeInt(mComposeTime != null ? 1 : 0);
+ if (mComposeTime != null) {
+ parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+ }
+ parcel.writeCharSequence(mText);
+ parcel.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Message> CREATOR =
+ new Creator<Message>() {
+ @Override
+ public Message createFromParcel(Parcel in) {
+ return new Message(in);
+ }
+
+ @Override
+ public Message[] newArray(int size) {
+ return new Message[size];
+ }
+ };
+
+ /** Returns the person that composed the message. */
+ @Nullable
+ public Person getAuthor() {
+ return mAuthor;
+ }
+
+ /** Returns the compose time of the message. */
+ @Nullable
+ public ZonedDateTime getTime() {
+ return mComposeTime;
+ }
+
+ /** Returns the text of the message. */
+ @Nullable
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the extended data related to this conversation action.
+ *
+ * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+ * prefer to hold a reference to the returned bundle rather than frequently calling this
+ * method.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras.deepCopy();
+ }
+
+ /** Builder class to construct a {@link Message} */
+ public static final class Builder {
+ @Nullable
+ private Person mAuthor;
+ @Nullable
+ private ZonedDateTime mComposeTime;
+ @Nullable
+ private CharSequence mText;
+ @Nullable
+ private Bundle mExtras;
+
+ /** Sets the person who composed this message. */
+ @NonNull
+ public Builder setAuthor(@Nullable Person author) {
+ mAuthor = author;
+ return this;
+ }
+
+ /** Sets the text of this message */
+ @NonNull
+ public Builder setText(@Nullable CharSequence text) {
+ mText = text;
+ return this;
+ }
+
+ /** Sets the compose time of this message */
+ @NonNull
+ public Builder setComposeTime(@Nullable ZonedDateTime composeTime) {
+ mComposeTime = composeTime;
+ return this;
+ }
+
+ /** Sets a set of extended data to the message. */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle bundle) {
+ this.mExtras = bundle;
+ return this;
+ }
+
+ /** Builds the {@link Message} object. */
+ @NonNull
+ public Message build() {
+ return new Message(
+ mAuthor,
+ mComposeTime,
+ mText == null ? null : new SpannedString(mText),
+ mExtras == null ? new Bundle() : mExtras.deepCopy());
+ }
+ }
+ }
+
+ /** Configuration object for specifying what action types to identify. */
+ public static final class TypeConfig implements Parcelable {
+ @NonNull
+ @ActionType
+ private final Set<String> mExcludedTypes;
+ @NonNull
+ @ActionType
+ private final Set<String> mIncludedTypes;
+ private final boolean mIncludeTypesFromTextClassifier;
+
+ private TypeConfig(
+ @NonNull Set<String> includedTypes,
+ @NonNull Set<String> excludedTypes,
+ boolean includeTypesFromTextClassifier) {
+ mIncludedTypes = Preconditions.checkNotNull(includedTypes);
+ mExcludedTypes = Preconditions.checkNotNull(excludedTypes);
+ mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+ }
+
+ private TypeConfig(Parcel in) {
+ mIncludedTypes = new ArraySet<>(in.createStringArrayList());
+ mExcludedTypes = new ArraySet<>(in.createStringArrayList());
+ mIncludeTypesFromTextClassifier = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeStringList(new ArrayList<>(mIncludedTypes));
+ parcel.writeStringList(new ArrayList<>(mExcludedTypes));
+ parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<TypeConfig> CREATOR =
+ new Creator<TypeConfig>() {
+ @Override
+ public TypeConfig createFromParcel(Parcel in) {
+ return new TypeConfig(in);
+ }
+
+ @Override
+ public TypeConfig[] newArray(int size) {
+ return new TypeConfig[size];
+ }
+ };
+
+ /**
+ * Returns a final list of types that the text classifier should look for.
+ *
+ * <p>NOTE: This method is intended for use by a text classifier.
+ *
+ * @param defaultTypes types the text classifier thinks should be included before factoring
+ * in the included/excluded types given by the client.
+ */
+ @NonNull
+ public Collection<String> resolveTypes(@Nullable Collection<String> defaultTypes) {
+ Set<String> types = new ArraySet<>();
+ if (mIncludeTypesFromTextClassifier && defaultTypes != null) {
+ types.addAll(defaultTypes);
+ }
+ types.addAll(mIncludedTypes);
+ types.removeAll(mExcludedTypes);
+ return Collections.unmodifiableCollection(types);
+ }
+
+ /**
+ * Return whether the client allows the text classifier to include its own list of default
+ * types. If this function returns {@code true}, the text classifier can consider specifying
+ * a default list of entity types in {@link #resolveTypes(Collection)}.
+ *
+ * <p>NOTE: This method is intended for use by a text classifier.
+ *
+ * @see #resolveTypes(Collection)
+ */
+ public boolean shouldIncludeTypesFromTextClassifier() {
+ return mIncludeTypesFromTextClassifier;
+ }
+
+ /** Builder class to construct the {@link TypeConfig} object. */
+ public static final class Builder {
+ @Nullable
+ private Collection<String> mExcludedTypes;
+ @Nullable
+ private Collection<String> mIncludedTypes;
+ private boolean mIncludeTypesFromTextClassifier = true;
+
+ /**
+ * Sets a collection of types that are explicitly included, for example, {@link
+ * #TYPE_VIEW_CALENDAR}.
+ */
+ @NonNull
+ public Builder setIncludedTypes(
+ @Nullable @ActionType Collection<String> includedTypes) {
+ mIncludedTypes = includedTypes;
+ return this;
+ }
+
+ /**
+ * Sets a collection of types that are explicitly excluded, for example, {@link
+ * #TYPE_VIEW_CALENDAR}.
+ */
+ @NonNull
+ public Builder setExcludedTypes(
+ @Nullable @ActionType Collection<String> excludedTypes) {
+ mExcludedTypes = excludedTypes;
+ return this;
+ }
+
+ /**
+ * Specifies whether or not to include the types suggested by the text classifier. By
+ * default, it is included.
+ */
+ @NonNull
+ public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
+ mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+ return this;
+ }
+
+ /**
+ * Combines all of the options that have been set and returns a new {@link TypeConfig}
+ * object.
+ */
+ @NonNull
+ public TypeConfig build() {
+ return new TypeConfig(
+ mIncludedTypes == null
+ ? Collections.emptySet()
+ : new ArraySet<>(mIncludedTypes),
+ mExcludedTypes == null
+ ? Collections.emptySet()
+ : new ArraySet<>(mExcludedTypes),
+ mIncludeTypesFromTextClassifier);
+ }
+ }
+ }
+
+ /**
+ * A request object for generating conversation action suggestions.
+ *
+ * @see TextClassifier#suggestConversationActions(Request)
+ */
+ public static final class Request implements Parcelable {
+ @NonNull
+ private final List<Message> mConversation;
+ @NonNull
+ private final TypeConfig mTypeConfig;
+ private final int mMaxSuggestions;
+ @NonNull
+ @Hint
+ private final List<String> mHints;
+
+ private Request(
+ @NonNull List<Message> conversation,
+ @NonNull TypeConfig typeConfig,
+ int maxSuggestions,
+ @Nullable @Hint List<String> hints) {
+ mConversation = Preconditions.checkNotNull(conversation);
+ mTypeConfig = Preconditions.checkNotNull(typeConfig);
+ mMaxSuggestions = maxSuggestions;
+ mHints = hints;
+ }
+
+ private Request(Parcel in) {
+ List<Message> conversation = new ArrayList<>();
+ in.readParcelableList(conversation, null);
+ mConversation = Collections.unmodifiableList(conversation);
+ mTypeConfig = in.readParcelable(null);
+ mMaxSuggestions = in.readInt();
+ List<String> hints = new ArrayList<>();
+ in.readStringList(hints);
+ mHints = Collections.unmodifiableList(hints);
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelableList(mConversation, flags);
+ parcel.writeParcelable(mTypeConfig, flags);
+ parcel.writeInt(mMaxSuggestions);
+ parcel.writeStringList(mHints);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Request> CREATOR =
+ new Creator<Request>() {
+ @Override
+ public Request createFromParcel(Parcel in) {
+ return new Request(in);
+ }
+
+ @Override
+ public Request[] newArray(int size) {
+ return new Request[size];
+ }
+ };
+
+ /** Returns the type config. */
+ @NonNull
+ public TypeConfig getTypeConfig() {
+ return mTypeConfig;
+ }
+
+ /** Returns an immutable list of messages that make up the conversation. */
+ @NonNull
+ public List<Message> getConversation() {
+ return mConversation;
+ }
+
+ /**
+ * Return the maximal number of suggestions the caller wants, value 0 means no restriction.
+ */
+ @IntRange(from = 0)
+ public int getMaxSuggestions() {
+ return mMaxSuggestions;
+ }
+
+ /** Returns an immutable list of hints */
+ @Nullable
+ @Hint
+ public List<String> getHints() {
+ return mHints;
+ }
+
+ /** Builder object to construct the {@link Request} object. */
+ public static final class Builder {
+ @NonNull
+ private List<Message> mConversation;
+ @Nullable
+ private TypeConfig mTypeConfig;
+ private int mMaxSuggestions;
+ @Nullable
+ @Hint
+ private List<String> mHints;
+
+ /**
+ * Constructs a builder.
+ *
+ * @param conversation the conversation that the text classifier is going to generate
+ * actions for.
+ */
+ public Builder(@NonNull List<Message> conversation) {
+ mConversation = Preconditions.checkNotNull(conversation);
+ }
+
+ /**
+ * Sets the hints to help text classifier to generate actions. It could be used to help
+ * text classifier to infer what types of actions the caller may be interested in.
+ */
+ public Builder setHints(@Nullable @Hint List<String> hints) {
+ mHints = hints;
+ return this;
+ }
+
+ /** Sets the type config. */
+ @NonNull
+ public Builder setTypeConfig(@Nullable TypeConfig typeConfig) {
+ mTypeConfig = typeConfig;
+ return this;
+ }
+
+ /** Sets the maximum number of suggestions you want.
+ * <p>
+ * Value 0 means no restriction.
+ */
+ @NonNull
+ public Builder setMaxSuggestions(@IntRange(from = 0) int maxSuggestions) {
+ mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions);
+ return this;
+ }
+
+ /** Builds the {@link Request} object. */
+ @NonNull
+ public Request build() {
+ return new Request(
+ Collections.unmodifiableList(mConversation),
+ mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig,
+ mMaxSuggestions,
+ mHints == null
+ ? Collections.emptyList()
+ : Collections.unmodifiableList(mHints));
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 9511a9e..f6c3d77 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
@@ -126,6 +127,7 @@
@NonNull private final List<RemoteAction> mActions;
@NonNull private final EntityConfidence mEntityConfidence;
@Nullable private final String mId;
+ @NonNull private final Bundle mExtras;
private TextClassification(
@Nullable String text,
@@ -135,7 +137,8 @@
@Nullable OnClickListener legacyOnClickListener,
@NonNull List<RemoteAction> actions,
@NonNull Map<String, Float> entityConfidence,
- @Nullable String id) {
+ @Nullable String id,
+ @NonNull Bundle extras) {
mText = text;
mLegacyIcon = legacyIcon;
mLegacyLabel = legacyLabel;
@@ -144,6 +147,7 @@
mActions = Collections.unmodifiableList(actions);
mEntityConfidence = new EntityConfidence(entityConfidence);
mId = id;
+ mExtras = extras;
}
/**
@@ -255,6 +259,18 @@
return mId;
}
+ /**
+ * Returns the extended data.
+ *
+ * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+ * prefer to hold a reference to the returned bundle rather than frequently calling this
+ * method.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras.deepCopy();
+ }
+
@Override
public String toString() {
return String.format(Locale.US,
@@ -359,6 +375,7 @@
@Nullable private Intent mLegacyIntent;
@Nullable private OnClickListener mLegacyOnClickListener;
@Nullable private String mId;
+ @Nullable private Bundle mExtras;
/**
* Sets the classified text.
@@ -471,12 +488,22 @@
}
/**
+ * Sets the extended data.
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
* Builds and returns a {@link TextClassification} object.
*/
@NonNull
public TextClassification build() {
return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
- mLegacyOnClickListener, mActions, mEntityConfidence, mId);
+ mLegacyOnClickListener, mActions, mEntityConfidence, mId,
+ mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
}
}
@@ -490,18 +517,21 @@
private final int mEndIndex;
@Nullable private final LocaleList mDefaultLocales;
@Nullable private final ZonedDateTime mReferenceTime;
+ @NonNull private final Bundle mExtras;
private Request(
CharSequence text,
int startIndex,
int endIndex,
LocaleList defaultLocales,
- ZonedDateTime referenceTime) {
+ ZonedDateTime referenceTime,
+ Bundle extras) {
mText = text;
mStartIndex = startIndex;
mEndIndex = endIndex;
mDefaultLocales = defaultLocales;
mReferenceTime = referenceTime;
+ mExtras = extras;
}
/**
@@ -548,6 +578,18 @@
}
/**
+ * Returns the extended data.
+ *
+ * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+ * prefer to hold a reference to the returned bundle rather than frequently calling this
+ * method.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras.deepCopy();
+ }
+
+ /**
* A builder for building TextClassification requests.
*/
public static final class Builder {
@@ -555,6 +597,7 @@
private final CharSequence mText;
private final int mStartIndex;
private final int mEndIndex;
+ private Bundle mExtras;
@Nullable private LocaleList mDefaultLocales;
@Nullable private ZonedDateTime mReferenceTime;
@@ -602,11 +645,23 @@
}
/**
+ * Sets the extended data.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
* Builds and returns the request object.
*/
@NonNull
public Request build() {
- return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
+ return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime,
+ mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
}
}
@@ -628,6 +683,7 @@
if (mReferenceTime != null) {
dest.writeString(mReferenceTime.toString());
}
+ dest.writeBundle(mExtras);
}
public static final Parcelable.Creator<Request> CREATOR =
@@ -649,6 +705,7 @@
mEndIndex = in.readInt();
mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString());
+ mExtras = in.readBundle();
}
}
@@ -664,6 +721,7 @@
dest.writeTypedList(mActions);
mEntityConfidence.writeToParcel(dest, flags);
dest.writeString(mId);
+ dest.writeBundle(mExtras);
}
public static final Parcelable.Creator<TextClassification> CREATOR =
@@ -695,6 +753,7 @@
mLegacyIntent = null; // mLegacyIntent is not parcelled.
mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
mId = in.readString();
+ mExtras = in.readBundle();
}
// Best effort attempt to try to load a drawable from the provided icon.
@@ -717,67 +776,4 @@
}
return null;
}
-
- // TODO: Remove once apps can build against the latest sdk.
- /**
- * Optional input parameters for generating TextClassification.
- * @hide
- */
- public static final class Options {
-
- @Nullable private final TextClassificationSessionId mSessionId;
- @Nullable private final Request mRequest;
- @Nullable private LocaleList mDefaultLocales;
- @Nullable private ZonedDateTime mReferenceTime;
-
- public Options() {
- this(null, null);
- }
-
- private Options(
- @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
- mSessionId = sessionId;
- mRequest = request;
- }
-
- /** Helper to create Options from a Request. */
- public static Options from(TextClassificationSessionId sessionId, Request request) {
- final Options options = new Options(sessionId, request);
- options.setDefaultLocales(request.getDefaultLocales());
- options.setReferenceTime(request.getReferenceTime());
- return options;
- }
-
- /** @param defaultLocales ordered list of locale preferences. */
- public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
- mDefaultLocales = defaultLocales;
- return this;
- }
-
- /** @param referenceTime refrence time used for interpreting relatives dates */
- public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) {
- mReferenceTime = referenceTime;
- return this;
- }
-
- @Nullable
- public LocaleList getDefaultLocales() {
- return mDefaultLocales;
- }
-
- @Nullable
- public ZonedDateTime getReferenceTime() {
- return mReferenceTime;
- }
-
- @Nullable
- public Request getRequest() {
- return mRequest;
- }
-
- @Nullable
- public TextClassificationSessionId getSessionId() {
- return mSessionId;
- }
- }
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 2e92f14..e675744 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -325,6 +325,17 @@
}
/**
+ * Suggests and returns a list of actions according to the given conversation.
+ */
+ @WorkerThread
+ default ConversationActions suggestConversationActions(
+ @NonNull ConversationActions.Request request) {
+ Preconditions.checkNotNull(request);
+ Utils.checkMainThread();
+ return new ConversationActions(Collections.emptyList());
+ }
+
+ /**
* Reports a selection event.
*
* <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 1cac3ed..b31438f 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,7 +20,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.LocaleList;
import android.os.Parcel;
@@ -29,8 +28,6 @@
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.text.util.Linkify.LinkifyMask;
import android.view.View;
import android.view.textclassifier.TextClassifier.EntityType;
import android.widget.TextView;
@@ -634,125 +631,4 @@
return new TextLinks(mFullText, mLinks);
}
}
-
- // TODO: Remove once apps can build against the latest sdk.
- /**
- * Optional input parameters for generating TextLinks.
- * @hide
- */
- public static final class Options {
-
- @Nullable private final TextClassificationSessionId mSessionId;
- @Nullable private final Request mRequest;
- @Nullable private LocaleList mDefaultLocales;
- @Nullable private TextClassifier.EntityConfig mEntityConfig;
- private boolean mLegacyFallback;
-
- private @ApplyStrategy int mApplyStrategy;
- private Function<TextLink, TextLinkSpan> mSpanFactory;
-
- private String mCallingPackageName;
-
- @UnsupportedAppUsage
- public Options() {
- this(null, null);
- }
-
- private Options(
- @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
- mSessionId = sessionId;
- mRequest = request;
- }
-
- /** Helper to create Options from a Request. */
- public static Options from(TextClassificationSessionId sessionId, Request request) {
- final Options options = new Options(sessionId, request);
- options.setDefaultLocales(request.getDefaultLocales());
- options.setEntityConfig(request.getEntityConfig());
- return options;
- }
-
- /** Returns a new options object based on the specified link mask. */
- public static Options fromLinkMask(@LinkifyMask int mask) {
- final List<String> entitiesToFind = new ArrayList<>();
-
- if ((mask & Linkify.WEB_URLS) != 0) {
- entitiesToFind.add(TextClassifier.TYPE_URL);
- }
- if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
- entitiesToFind.add(TextClassifier.TYPE_EMAIL);
- }
- if ((mask & Linkify.PHONE_NUMBERS) != 0) {
- entitiesToFind.add(TextClassifier.TYPE_PHONE);
- }
- if ((mask & Linkify.MAP_ADDRESSES) != 0) {
- entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
- }
-
- return new Options().setEntityConfig(
- TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
- }
-
- /** @param defaultLocales ordered list of locale preferences. */
- public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
- mDefaultLocales = defaultLocales;
- return this;
- }
-
- /** @param entityConfig definition of which entity types to look for. */
- public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
- mEntityConfig = entityConfig;
- return this;
- }
-
- /** @param applyStrategy strategy to use when resolving conflicts. */
- public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
- checkValidApplyStrategy(applyStrategy);
- mApplyStrategy = applyStrategy;
- return this;
- }
-
- /** @param spanFactory factory for converting TextLink to TextLinkSpan. */
- public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
- mSpanFactory = spanFactory;
- return this;
- }
-
- @Nullable
- public LocaleList getDefaultLocales() {
- return mDefaultLocales;
- }
-
- @Nullable
- public TextClassifier.EntityConfig getEntityConfig() {
- return mEntityConfig;
- }
-
- @ApplyStrategy
- public int getApplyStrategy() {
- return mApplyStrategy;
- }
-
- @Nullable
- public Function<TextLink, TextLinkSpan> getSpanFactory() {
- return mSpanFactory;
- }
-
- @Nullable
- public Request getRequest() {
- return mRequest;
- }
-
- @Nullable
- public TextClassificationSessionId getSessionId() {
- return mSessionId;
- }
-
- private static void checkValidApplyStrategy(int applyStrategy) {
- if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) {
- throw new IllegalArgumentException(
- "Invalid apply strategy. See TextLinks.ApplyStrategy for options.");
- }
- }
- }
}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index a4550af..52d01ea 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -375,56 +375,4 @@
mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
mId = in.readString();
}
-
-
- // TODO: Remove once apps can build against the latest sdk.
- /**
- * Optional input parameters for generating TextSelection.
- * @hide
- */
- public static final class Options {
-
- @Nullable private final TextClassificationSessionId mSessionId;
- @Nullable private final Request mRequest;
- @Nullable private LocaleList mDefaultLocales;
- private boolean mDarkLaunchAllowed;
-
- public Options() {
- this(null, null);
- }
-
- private Options(
- @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
- mSessionId = sessionId;
- mRequest = request;
- }
-
- /** Helper to create Options from a Request. */
- public static Options from(TextClassificationSessionId sessionId, Request request) {
- final Options options = new Options(sessionId, request);
- options.setDefaultLocales(request.getDefaultLocales());
- return options;
- }
-
- /** @param defaultLocales ordered list of locale preferences. */
- public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
- mDefaultLocales = defaultLocales;
- return this;
- }
-
- @Nullable
- public LocaleList getDefaultLocales() {
- return mDefaultLocales;
- }
-
- @Nullable
- public Request getRequest() {
- return mRequest;
- }
-
- @Nullable
- public TextClassificationSessionId getSessionId() {
- return mSessionId;
- }
- }
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index d1c2799..0a7cff6 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -113,11 +113,12 @@
/**
* Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
- * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
+ * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean)
*/
public static void apply464xlatAdjustments(NetworkStats baseTraffic,
- NetworkStats stackedTraffic) {
- NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces);
+ NetworkStats stackedTraffic, boolean useBpfStats) {
+ NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces,
+ useBpfStats);
}
@VisibleForTesting
@@ -263,7 +264,7 @@
// No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
// TODO: remove this and only apply adjustments in NetworkStatsService.
- stats.apply464xlatAdjustments(sStackedIfaces);
+ stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats);
return stats;
}
diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java
index 0651975..3d4df89 100644
--- a/core/java/com/android/internal/os/ProcStatsUtil.java
+++ b/core/java/com/android/internal/os/ProcStatsUtil.java
@@ -20,6 +20,8 @@
import android.os.StrictMode;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
@@ -27,7 +29,8 @@
/**
* Utility functions for reading {@code proc} files
*/
-final class ProcStatsUtil {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+public final class ProcStatsUtil {
private static final String TAG = "ProcStatsUtil";
@@ -48,8 +51,9 @@
*
* @param path path of the file to read
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
@Nullable
- static String readNullSeparatedFile(String path) {
+ public static String readNullSeparatedFile(String path) {
String contents = readSingleLineProcFile(path);
if (contents == null) {
return null;
@@ -72,8 +76,9 @@
*
* @param path path of the file to read
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
@Nullable
- static String readSingleLineProcFile(String path) {
+ public static String readSingleLineProcFile(String path) {
return readTerminatedProcFile(path, (byte) '\n');
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 98b7b5d..65213c0 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -216,6 +216,11 @@
*/
native protected static void nativeUnmountStorageOnInit();
+ private static void callPostForkSystemServerHooks() {
+ // SystemServer specific post fork hooks run before child post fork hooks.
+ VM_HOOKS.postForkSystemServer();
+ }
+
private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
boolean isZygote, String instructionSet) {
VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3e04bb3..4e20e29 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -84,6 +84,7 @@
static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
static jclass gZygoteClass;
+static jmethodID gCallPostForkSystemServerHooks;
static jmethodID gCallPostForkChildHooks;
static bool g_is_security_enforced = true;
@@ -886,6 +887,18 @@
// Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers).
UnsetChldSignalHandler();
+ if (is_system_server) {
+ env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks);
+ if (env->ExceptionCheck()) {
+ fail_fn("Error calling post fork system server hooks.");
+ }
+ // TODO(oth): Remove hardcoded label here (b/117874058).
+ static const char* kSystemServerLabel = "u:r:system_server:s0";
+ if (selinux_android_setcon(kSystemServerLabel) != 0) {
+ fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel));
+ }
+ }
+
env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
is_system_server, is_child_zygote, instructionSet);
if (env->ExceptionCheck()) {
@@ -1181,6 +1194,9 @@
int register_com_android_internal_os_Zygote(JNIEnv* env) {
gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
+ gCallPostForkSystemServerHooks = GetStaticMethodIDOrDie(env, gZygoteClass,
+ "callPostForkSystemServerHooks",
+ "()V");
gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
"(IZZLjava/lang/String;)V");
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index de863d7..91a5440 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -29,6 +29,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -48,6 +49,13 @@
@RunWith(AndroidJUnit4.class)
public class TextClassificationTest {
+ private static final String BUNDLE_KEY = "key";
+ private static final String BUNDLE_VALUE = "value";
+ private static final Bundle BUNDLE = new Bundle();
+ static {
+ BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+ }
+
public Icon generateTestIcon(int width, int height, int colorValue) {
final int numPixels = width * height;
final int[] colors = new int[numPixels];
@@ -89,6 +97,7 @@
.setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
.setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
.setId(id)
+ .setExtras(BUNDLE)
.build();
// Parcel and unparcel
@@ -119,6 +128,9 @@
assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+
+ // Extras
+ assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
}
@Test
@@ -182,6 +194,7 @@
new TextClassification.Request.Builder(text, 0, text.length())
.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
.setReferenceTime(referenceTime)
+ .setExtras(BUNDLE)
.build();
// Parcel and unparcel.
@@ -197,5 +210,6 @@
assertEquals(referenceTime, result.getReferenceTime());
assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
assertEquals(referenceTime, result.getReferenceTime());
+ assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
new file mode 100644
index 0000000..489e164
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcStatsUtilTest {
+
+ private File mProcDirectory;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
+
+ @Test
+ public void testReadNullSeparatedFile_empty() throws IOException {
+ assertEquals(
+ "",
+ runReadNullSeparatedFile(""));
+ }
+
+ @Test
+ public void testReadNullSeparatedFile_simple() throws IOException {
+ assertEquals(
+ "abc def ghi",
+ runReadNullSeparatedFile("abc\0def\0ghi"));
+ }
+
+ @Test
+ public void testReadNullSeparatedFile_trailingNulls() throws IOException {
+ assertEquals(
+ "abc",
+ runReadNullSeparatedFile("abc\0\0\0\0"));
+ }
+
+ @Test
+ public void testReadNullSeparatedFile_doubleNullEnds() throws IOException {
+ assertEquals(
+ "abc",
+ runReadNullSeparatedFile("abc\0\0def"));
+ }
+
+ @Test
+ public void testReadSingleLineProcFile_simple() throws IOException {
+ assertEquals(
+ "abc",
+ runReadSingleLineProcFile("abc"));
+ }
+
+ @Test
+ public void testReadSingleLineProcFile_empty() throws IOException {
+ assertEquals(
+ "",
+ runReadSingleLineProcFile(""));
+ }
+
+ @Test
+ public void testReadSingleLineProcFile_newLine() throws IOException {
+ assertEquals(
+ "abc",
+ runReadSingleLineProcFile("abc\ndef"));
+ }
+
+ @Test
+ public void testReadSingleLineProcFile_doubleNewLine() throws IOException {
+ assertEquals(
+ "abc",
+ runReadSingleLineProcFile("abc\n\ndef"));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_simple() throws IOException {
+ assertEquals(
+ "abc",
+ runReadTerminatedProcFile("abc\0", (byte) '\0'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_withExtra() throws IOException {
+ assertEquals(
+ "123",
+ runReadTerminatedProcFile("123\0extra", (byte) '\0'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_noTerminator() throws IOException {
+ assertEquals(
+ "noterm",
+ runReadTerminatedProcFile("noterm", (byte) '\0'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_newLineTerm() throws IOException {
+ assertEquals(
+ "123",
+ runReadTerminatedProcFile("123\n456", (byte) '\n'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_normalCharTerm() throws IOException {
+ assertEquals(
+ "abc",
+ runReadTerminatedProcFile("abcdef", (byte) 'd'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_largeUnterminated() throws IOException {
+ String longString = new String(new char[10000]).replace('\0', 'a');
+ assertEquals(
+ longString,
+ runReadTerminatedProcFile(longString, (byte) '\0'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_largeTerminated() throws IOException {
+ String longString = new String(new char[10000]).replace('\0', 'a');
+ assertEquals(
+ longString,
+ runReadTerminatedProcFile(longString + "\0", (byte) '\0'));
+ }
+
+ @Test
+ public void testReadTerminatedProcFile_largeExtra() throws IOException {
+ String longString = new String(new char[10000]).replace('\0', 'a');
+ assertEquals(
+ longString,
+ runReadTerminatedProcFile(longString + "\0abc", (byte) '\0'));
+ }
+
+ private String runReadNullSeparatedFile(String fileContents) throws IOException {
+ File tempFile = File.createTempFile("null-separated-file", null, mProcDirectory);
+ Files.write(tempFile.toPath(), fileContents.getBytes());
+ String result = ProcStatsUtil.readNullSeparatedFile(tempFile.toString());
+ Files.delete(tempFile.toPath());
+ return result;
+ }
+
+ private String runReadSingleLineProcFile(String fileContents) throws IOException {
+ File tempFile = File.createTempFile("single-line-proc-file", null, mProcDirectory);
+ Files.write(tempFile.toPath(), fileContents.getBytes());
+ String result = ProcStatsUtil.readSingleLineProcFile(tempFile.toString());
+ Files.delete(tempFile.toPath());
+ return result;
+ }
+
+ private String runReadTerminatedProcFile(
+ String fileContents, byte terminator) throws IOException {
+ File tempFile = File.createTempFile("terminated-proc-file", null, mProcDirectory);
+ Files.write(tempFile.toPath(), fileContents.getBytes());
+ String result = ProcStatsUtil.readTerminatedProcFile(tempFile.toString(), terminator);
+ Files.delete(tempFile.toPath());
+ return result;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
new file mode 100644
index 0000000..f2a531f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcTimeInStateReaderTest {
+
+ private File mProcDirectory;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
+
+ @Test
+ public void testSimple() throws IOException {
+ Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+ Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+ ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+ assertArrayEquals(
+ "Reported frequencies are correct",
+ new long[]{1, 3, 5, 7},
+ reader.getFrequenciesKhz());
+ assertArrayEquals(
+ "Reported usage times are correct",
+ new long[]{20, 40, 60, 80},
+ reader.getUsageTimesMillis(initialTimeInStateFile));
+ }
+
+ @Test
+ public void testDifferentFile() throws IOException {
+ Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+ Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+ ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+ Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state");
+ Files.write(timeInStateFile, "1 20\n3 40\n5 60\n7 80\n".getBytes());
+ assertArrayEquals(
+ "Reported usage times are correct",
+ new long[]{200, 400, 600, 800},
+ reader.getUsageTimesMillis(timeInStateFile));
+ }
+
+ @Test
+ public void testWrongLength() throws IOException {
+ Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+ Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+ ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+ Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state");
+ Files.write(timeInStateFile, "1 2\n3 4\n5 6\n".getBytes());
+ assertNull(reader.getUsageTimesMillis(timeInStateFile));
+ }
+
+ @Test
+ public void testEmptyInitialFails() throws IOException {
+ Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+ Files.write(initialTimeInStateFile, "".getBytes());
+ try {
+ new ProcTimeInStateReader(initialTimeInStateFile);
+ fail("Instantiation didn't fail with empty initial time_in_state file");
+ } catch (IOException ignored) {
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 3efe655..6f78651 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -48,9 +48,6 @@
* </p>
*/
public class MeasuredText {
- private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
-
private long mNativePtr;
private @NonNull char[] mChars;
@@ -166,6 +163,9 @@
* Note: The appendStyle and appendReplacementRun should be called to cover the text length.
*/
public static class Builder {
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
private long mNativePtr;
private final @NonNull char[] mText;
diff --git a/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml
new file mode 100644
index 0000000..cfc6961
--- /dev/null
+++ b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="help_feedback_label" msgid="4550436169116444686">"सहायता और सुझाव"</string>
+</resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-or/strings.xml b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml
new file mode 100644
index 0000000..3523037
--- /dev/null
+++ b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="search_menu" msgid="1604061903696928905">"ସର୍ଚ୍ଚ ସେଟିଙ୍ଗ"</string>
+</resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml
new file mode 100644
index 0000000..8b62fd4
--- /dev/null
+++ b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="search_menu" msgid="1604061903696928905">"ترتیبات تلاش کریں"</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 5c11db1..83c4b8e 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -240,7 +240,7 @@
<string name="wifi_unmetered_label" msgid="6124098729457992931">"بدون قياس"</string>
<string name="select_logd_size_title" msgid="7433137108348553508">"أحجام ذاكرة التخزين المؤقت للتسجيل"</string>
<string name="select_logd_size_dialog_title" msgid="1206769310236476760">"حدد أحجامًا أكبر لكل ذاكرة تخزين مؤقت للتسجيل"</string>
- <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو سعة التخزين الدائمة للمسجِّل؟"</string>
+ <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو مساحة التخزين الدائمة للمسجِّل؟"</string>
<string name="dev_logpersist_clear_warning_message" msgid="2256582531342994562">"عندما نتوقف عن رصد أي أخطاء باستخدام المسجِّل الدائم مرة أخرى، يتعين علينا محو بيانات المسجِّل الموجودة على جهازك."</string>
<string name="select_logpersist_title" msgid="7530031344550073166">"تخزين بيانات المسجِّل باستمرار على الجهاز"</string>
<string name="select_logpersist_dialog_title" msgid="4003400579973269060">"تحديد مخازن السجلات المؤقتة المراد تخزينها باستمرار على الجهاز"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 72dd8cf..5638947 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -446,6 +446,6 @@
<string name="alarm_template_far" msgid="3779172822607461675">"Data: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="zen_mode_duration_settings_title" msgid="229547412251222757">"Durada"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"Pregunta sempre"</string>
- <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivi"</string>
+ <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivis"</string>
<string name="time_unit_just_now" msgid="6363336622778342422">"Ara mateix"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 5a11e72..5da253c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -317,7 +317,7 @@
<string name="show_all_anrs" msgid="4924885492787069007">"הצגת מקרי ANR ברקע"</string>
<string name="show_all_anrs_summary" msgid="6636514318275139826">"הצגת תיבת דו-שיח של \'אפליקציה לא מגיבה\' עבור אפליקציות שפועלות ברקע"</string>
<string name="show_notification_channel_warnings" msgid="1399948193466922683">"אזהרות לגבי ערוץ הודעות"</string>
- <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת הודעה ללא ערוץ חוקי"</string>
+ <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת התראה ללא ערוץ חוקי"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"אילוץ הרשאת אפליקציות באחסון חיצוני"</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"מאפשר כתיבה של כל אפליקציה באחסון חיצוני, ללא התחשבות בערכי המניפסט"</string>
<string name="force_resizable_activities" msgid="8615764378147824985">"אלץ יכולת קביעת גודל של הפעילויות"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index df122e2..d693077 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -415,7 +415,7 @@
<string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Kiwango maalum (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string>
<string name="retail_demo_reset_message" msgid="118771671364131297">"Weka nenosiri ili urejeshe mipangilio ya kiwandani ikiwa katika hali ya onyesho."</string>
- <string name="retail_demo_reset_next" msgid="8356731459226304963">"Inayofuata"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Endelea"</string>
<string name="retail_demo_reset_title" msgid="696589204029930100">"Nenosiri linahitajika"</string>
<string name="active_input_method_subtypes" msgid="3596398805424733238">"Mbinu zinazotumika"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Tumia lugha za mfumo"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index a842229..3152e65 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -127,4 +127,17 @@
default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
}
+
+ /**
+ * Called when ACL connection state is changed. It listens to
+ * {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED} and {@link
+ * android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}
+ *
+ * @param cachedDevice Bluetooth device that changed
+ * @param state the Bluetooth device connection state, the possible values are:
+ * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}
+ */
+ default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 022bf69..2b7babd0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -119,6 +119,10 @@
addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
new AudioModeChangedHandler());
+ // ACL connection changed broadcasts
+ addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
+ addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
+
registerAdapterIntentReceiver();
}
@@ -236,6 +240,15 @@
}
}
+ private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice,
+ int state) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onAclConnectionStateChanged(activeDevice, state);
+ }
+ }
+ }
+
@VisibleForTesting
void addHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
@@ -447,6 +460,32 @@
}
}
+ private class AclStateChangedHandler implements Handler {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ final String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, "AclStateChangedHandler: action is null");
+ return;
+ }
+ final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
+ final int state;
+ switch (action) {
+ case BluetoothDevice.ACTION_ACL_CONNECTED:
+ state = BluetoothAdapter.STATE_CONNECTED;
+ break;
+ case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+ state = BluetoothAdapter.STATE_DISCONNECTED;
+ break;
+ default:
+ Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
+ return;
+
+ }
+ dispatchAclStateChanged(activeDevice, state);
+ }
+ }
+
private class AudioModeChangedHandler implements Handler {
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 020234c6..c147d5e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -19,7 +19,10 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
@@ -50,6 +53,8 @@
private BluetoothCallback mBluetoothCallback;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
private Context mContext;
private Intent mIntent;
@@ -62,6 +67,7 @@
mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
+ when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
}
@Test
@@ -126,4 +132,28 @@
verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
}
+
+ @Test
+ public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
+ mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_CONNECTED);
+ }
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index bf4374a..bca3530 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -116,6 +116,7 @@
public boolean isTransient = false;
public String expandedAccessibilityClassName;
public SlashState slash;
+ public boolean handlesLongClick = true;
public boolean copyTo(State other) {
if (other == null) throw new IllegalArgumentException();
@@ -133,7 +134,8 @@
|| !Objects.equals(other.state, state)
|| !Objects.equals(other.isTransient, isTransient)
|| !Objects.equals(other.dualTarget, dualTarget)
- || !Objects.equals(other.slash, slash);
+ || !Objects.equals(other.slash, slash)
+ || !Objects.equals(other.handlesLongClick, handlesLongClick);
other.icon = icon;
other.iconSupplier = iconSupplier;
other.label = label;
@@ -146,6 +148,7 @@
other.dualTarget = dualTarget;
other.isTransient = isTransient;
other.slash = slash != null ? slash.copy() : null;
+ other.handlesLongClick = handlesLongClick;
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index d42127e..0638998 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -194,6 +194,7 @@
}
setClickable(state.state != Tile.STATE_UNAVAILABLE);
+ setLongClickable(state.handlesLongClick);
mIcon.setIcon(state, allowAnimations);
setContentDescription(state.contentDescription);
@@ -287,10 +288,14 @@
info.setText(label);
info.setChecked(b);
info.setCheckable(true);
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.getId(),
- getResources().getString(R.string.accessibility_long_click_tile)));
+ if (isLongClickable()) {
+ info.addAction(
+ new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction
+ .ACTION_LONG_CLICK.getId(),
+ getResources().getString(
+ R.string.accessibility_long_click_tile)));
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index f2ead1c..d7ac253 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -18,7 +18,6 @@
import android.app.ActivityManager;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.provider.MediaStore;
import android.service.quicksettings.Tile;
import android.widget.Switch;
@@ -50,7 +49,9 @@
@Override
public BooleanState newTileState() {
- return new BooleanState();
+ BooleanState state = new BooleanState();
+ state.handlesLongClick = false;
+ return state;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c560301..df99a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -124,7 +125,8 @@
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
- private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+ private final KeyguardMonitorImpl mKeyguardMonitor =
+ (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
private final NotificationMediaManager mMediaManager =
Dependency.get(NotificationMediaManager.class);
@@ -202,6 +204,8 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowController.setKeyguardShowing(true);
+ mKeyguardMonitor.notifyKeyguardState(
+ mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
reset(true /* hideBouncerWhenShowing */);
StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -424,6 +428,8 @@
*/
public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
+ mKeyguardMonitor.notifyKeyguardState(
+ mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
launchPendingWakeupAction();
if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index ebd9e77..6eba914 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -457,13 +457,12 @@
final int interrogatingPid = Binder.getCallingPid();
callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
- final int callingUid = Binder.getCallingUid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
- return mSecurityPolicy.computeValidReportedPackages(callingUid,
+ return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} catch (RemoteException re) {
if (DEBUG) {
@@ -514,13 +513,12 @@
final int interrogatingPid = Binder.getCallingPid();
callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
- final int callingUid = Binder.getCallingUid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
- return mSecurityPolicy.computeValidReportedPackages(callingUid,
+ return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} catch (RemoteException re) {
if (DEBUG) {
@@ -571,13 +569,12 @@
final int interrogatingPid = Binder.getCallingPid();
callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
- final int callingUid = Binder.getCallingUid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments);
- return mSecurityPolicy.computeValidReportedPackages(callingUid,
+ return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} catch (RemoteException re) {
if (DEBUG) {
@@ -628,13 +625,12 @@
final int interrogatingPid = Binder.getCallingPid();
callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
- final int callingUid = Binder.getCallingUid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findFocus(accessibilityNodeId, focusType,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
- return mSecurityPolicy.computeValidReportedPackages(callingUid,
+ return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} catch (RemoteException re) {
if (DEBUG) {
@@ -684,13 +680,12 @@
final int interrogatingPid = Binder.getCallingPid();
callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
- final int callingUid = Binder.getCallingUid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().focusSearch(accessibilityNodeId, direction,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
- return mSecurityPolicy.computeValidReportedPackages(callingUid,
+ return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} catch (RemoteException re) {
if (DEBUG) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 097405a..e89b951 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -348,11 +348,24 @@
}
boolean getBindInstantServiceAllowed(int userId) {
- return mSecurityPolicy.getBindInstantServiceAllowed(userId);
+ final UserState userState = getUserState(userId);
+ if (userState == null) return false;
+ return userState.getBindInstantServiceAllowed();
}
void setBindInstantServiceAllowed(int userId, boolean allowed) {
- mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed);
+ UserState userState;
+ synchronized (mLock) {
+ userState = getUserState(userId);
+ if (userState == null) {
+ if (!allowed) {
+ return;
+ }
+ userState = new UserState(userId);
+ mUserStates.put(userId, userState);
+ }
+ }
+ userState.setBindInstantServiceAllowed(allowed);
}
private void registerBroadcastReceivers() {
@@ -1316,7 +1329,7 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (userState.mBindInstantServiceAllowed) {
+ if (userState.getBindInstantServiceAllowed()) {
flags |= PackageManager.MATCH_INSTANT;
}
@@ -3158,9 +3171,15 @@
return packageNames[0];
}
- String[] computeValidReportedPackages(int callingUid,
- String targetPackage, int targetUid) {
- if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
+ /**
+ * Get a list of package names an app may report, including any widget packages it owns.
+ *
+ * @param targetPackage The known valid target package
+ * @param targetUid The uid of the target app
+ * @return
+ */
+ String[] computeValidReportedPackages(String targetPackage, int targetUid) {
+ if (UserHandle.getAppId(targetUid) == Process.SYSTEM_UID) {
// Empty array means any package is Okay
return EmptyArray.STRING;
}
@@ -3184,32 +3203,6 @@
return uidPackages;
}
- private boolean getBindInstantServiceAllowed(int userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "getBindInstantServiceAllowed");
- UserState state = mUserStates.get(userId);
- return (state != null) && state.mBindInstantServiceAllowed;
- }
-
- private void setBindInstantServiceAllowed(int userId, boolean allowed) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "setBindInstantServiceAllowed");
- UserState state = mUserStates.get(userId);
- if (state == null) {
- if (!allowed) {
- return;
- }
- state = new UserState(userId);
- mUserStates.put(userId, state);
- }
- if (state.mBindInstantServiceAllowed != allowed) {
- state.mBindInstantServiceAllowed = allowed;
- onUserStateChangedLocked(state);
- }
- }
-
public void clearWindowsLocked() {
List<WindowInfo> windows = Collections.emptyList();
final int activeWindowId = mActiveWindowId;
@@ -3819,7 +3812,7 @@
public boolean mUserMinimumUiTimeoutEnabled;
public int mUserMinimumUiTimeout;
- public boolean mBindInstantServiceAllowed;
+ private boolean mBindInstantServiceAllowed;
public UserState(int userId) {
mUserId = userId;
@@ -4051,6 +4044,24 @@
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0)
& SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
}
+
+ public boolean getBindInstantServiceAllowed() {
+ synchronized (mLock) {
+ return mBindInstantServiceAllowed;
+ }
+ }
+
+ public void setBindInstantServiceAllowed(boolean allowed) {
+ synchronized (mLock) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "setBindInstantServiceAllowed");
+ if (allowed) {
+ mBindInstantServiceAllowed = allowed;
+ onUserStateChangedLocked(this);
+ }
+ }
+ }
}
private final class AccessibilityContentObserver extends ContentObserver {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 2396ded..86132a8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -92,7 +92,7 @@
final long identity = Binder.clearCallingIdentity();
try {
int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
- if (userState.mBindInstantServiceAllowed) {
+ if (userState.getBindInstantServiceAllowed()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
if (mService == null && mContext.bindServiceAsUser(
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index a72470d..05293b5 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -373,7 +373,8 @@
return getOrCreateStack(windowingMode, activityType, onTop);
}
- private int getNextStackId() {
+ @VisibleForTesting
+ int getNextStackId() {
return sNextFreeStackId++;
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 026c5cc..0049e22 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -48,41 +48,7 @@
import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
import static com.android.server.am.ActivityDisplay.POSITION_TOP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_APP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_APP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
-import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
import static com.android.server.am.ActivityStack.ActivityState.FINISHING;
@@ -102,8 +68,42 @@
import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
-
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_APP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_APP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG;
+import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
+import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+
import static java.lang.Integer.MAX_VALUE;
import android.app.Activity;
@@ -1807,13 +1807,6 @@
return false;
}
- final ActivityRecord top = topRunningActivityLocked();
- if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
- // Shouldn't be visible if you don't have any running activities, not starting one, and
- // not the top stack on display.
- return false;
- }
-
final ActivityDisplay display = getDisplay();
boolean gotSplitScreenStack = false;
boolean gotOpaqueSplitScreenPrimary = false;
@@ -1822,9 +1815,16 @@
final boolean isAssistantType = isActivityTypeAssistant();
for (int i = display.getChildCount() - 1; i >= 0; --i) {
final ActivityStack other = display.getChildAt(i);
+ final boolean hasRunningActivities = other.topRunningActivityLocked() != null;
if (other == this) {
- // Should be visible if there is no other stack occluding it.
- return true;
+ // Should be visible if there is no other stack occluding it, unless it doesn't
+ // have any running activities, not starting one and not home stack.
+ return hasRunningActivities || isInStackLocked(starting) != null
+ || isActivityTypeHome();
+ }
+
+ if (!hasRunningActivities) {
+ continue;
}
final int otherWindowingMode = other.getWindowingMode();
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 1d0ab8f..60f70c7 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -16,12 +16,15 @@
package com.android.server.location;
+import android.Manifest;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.contexthub.V1_0.Result;
import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
@@ -30,7 +33,7 @@
import android.os.RemoteException;
import android.util.Log;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
@@ -69,14 +72,15 @@
private final short mHostEndPointId;
/*
- * The remote callback interface for this client.
+ * The remote callback interface for this client. This will be set to null whenever the
+ * client connection is closed (either explicitly or via binder death).
*/
- private final IContextHubClientCallback mCallbackInterface;
+ private IContextHubClientCallback mCallbackInterface = null;
/*
- * false if the connection has been closed by the client, true otherwise.
+ * True if the client is still registered with the Context Hub Service, false otherwise.
*/
- private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+ private boolean mRegistered = true;
/*
* Internal interface used to invoke client callbacks.
@@ -85,6 +89,74 @@
void accept(IContextHubClientCallback callback) throws RemoteException;
}
+ /*
+ * The PendingIntent registered with this client.
+ */
+ private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
+
+ /*
+ * Helper class to manage registered PendingIntent requests from the client.
+ */
+ private class PendingIntentRequest {
+ /*
+ * The PendingIntent object to request, null if there is no open request.
+ */
+ private PendingIntent mPendingIntent;
+
+ /*
+ * The ID of the nanoapp the request is for, invalid if there is no open request.
+ */
+ private long mNanoAppId;
+
+ PendingIntentRequest() {}
+
+ PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) {
+ mPendingIntent = pendingIntent;
+ mNanoAppId = nanoAppId;
+ }
+
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ public boolean hasPendingIntent() {
+ return mPendingIntent != null;
+ }
+
+ public void clear() {
+ mPendingIntent = null;
+ }
+
+ public boolean register(PendingIntent pendingIntent, long nanoAppId) {
+ boolean success = false;
+ if (hasPendingIntent()) {
+ Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
+ } else {
+ mNanoAppId = nanoAppId;
+ mPendingIntent = pendingIntent;
+ success = true;
+ }
+
+ return success;
+ }
+
+ public boolean unregister(PendingIntent pendingIntent) {
+ boolean success = false;
+ if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
+ Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
+ } else {
+ mPendingIntent = null;
+ success = true;
+ }
+
+ return success;
+ }
+ }
+
/* package */ ContextHubClientBroker(
Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
ContextHubInfo contextHubInfo, short hostEndPointId,
@@ -102,8 +174,10 @@
*
* @throws RemoteException if the client has already died
*/
- /* package */ void attachDeathRecipient() throws RemoteException {
- mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ /* package */ synchronized void attachDeathRecipient() throws RemoteException {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
}
/**
@@ -118,9 +192,13 @@
ContextHubServiceUtil.checkPermissions(mContext);
int result;
- if (mConnectionOpen.get()) {
- ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
- mHostEndPointId, message);
+ IContextHubClientCallback callback = null;
+ synchronized (this) {
+ callback = mCallbackInterface;
+ }
+ if (callback != null) {
+ ContextHubMsg messageToNanoApp =
+ ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
int contextHubId = mAttachedContextHubInfo.getId();
try {
@@ -139,24 +217,37 @@
}
/**
- * @param intent the intent to register
- * @param nanoAppId the ID of the nanoapp to send events for
+ * @param pendingIntent the intent to register
+ * @param nanoAppId the ID of the nanoapp to send events for
* @return true on success, false otherwise
*/
@Override
- public boolean registerIntent(PendingIntent intent, long nanoAppId) {
- // TODO: Implement this
- return false;
+ public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+
+ boolean success = false;
+ synchronized (this) {
+ if (mCallbackInterface == null) {
+ Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
+ } else {
+ success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
+ }
+ }
+
+ return success;
}
/**
- * @param intent the intent to unregister
+ * @param pendingIntent the intent to unregister
* @return true on success, false otherwise
*/
@Override
- public boolean unregisterIntent(PendingIntent intent) {
- // TODO: Implement this
- return false;
+ public boolean unregisterIntent(PendingIntent pendingIntent) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+
+ synchronized (this) {
+ return mPendingIntentRequest.unregister(pendingIntent);
+ }
}
/**
@@ -164,8 +255,15 @@
*/
@Override
public void close() {
- if (mConnectionOpen.getAndSet(false)) {
- mClientManager.unregisterClient(mHostEndPointId);
+ synchronized (this) {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
+ mCallbackInterface = null;
+ }
+ if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ mRegistered = false;
+ }
}
}
@@ -197,7 +295,12 @@
* @param message the message that came from a nanoapp
*/
/* package */ void sendMessageToClient(NanoAppMessage message) {
- invokeCallbackConcurrent(callback -> callback.onMessageFromNanoApp(message));
+ invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+
+ Supplier<Intent> supplier =
+ () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+ .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
+ sendPendingIntent(supplier);
}
/**
@@ -206,7 +309,8 @@
* @param nanoAppId the ID of the nanoapp that was loaded.
*/
/* package */ void onNanoAppLoaded(long nanoAppId) {
- invokeCallbackConcurrent(callback -> callback.onNanoAppLoaded(nanoAppId));
+ invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
+ sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId));
}
/**
@@ -215,14 +319,16 @@
* @param nanoAppId the ID of the nanoapp that was unloaded.
*/
/* package */ void onNanoAppUnloaded(long nanoAppId) {
- invokeCallbackConcurrent(callback -> callback.onNanoAppUnloaded(nanoAppId));
+ invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId));
+ sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId));
}
/**
* Notifies the client of a hub reset event if the connection is open.
*/
/* package */ void onHubReset() {
- invokeCallbackConcurrent(callback -> callback.onHubReset());
+ invokeCallback(callback -> callback.onHubReset());
+ sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
}
/**
@@ -232,7 +338,12 @@
* @param abortCode the nanoapp specific abort code
*/
/* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
- invokeCallbackConcurrent(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
+ invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
+
+ Supplier<Intent> supplier =
+ () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId)
+ .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode);
+ sendPendingIntent(supplier);
}
/**
@@ -240,8 +351,8 @@
*
* @param consumer the consumer specifying the callback to invoke
*/
- private void invokeCallbackConcurrent(CallbackConsumer consumer) {
- if (mConnectionOpen.get()) {
+ private synchronized void invokeCallback(CallbackConsumer consumer) {
+ if (mCallbackInterface != null) {
try {
consumer.accept(mCallbackInterface);
} catch (RemoteException e) {
@@ -250,4 +361,53 @@
}
}
}
+
+ /**
+ * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE extra field
+ *
+ * @param eventType the ContextHubManager.Event type describing the event
+ * @return the Intent object
+ */
+ private Intent createIntent(int eventType) {
+ Intent intent = new Intent();
+ intent.putExtra(ContextHubManager.EXTRA_EVENT_TYPE, eventType);
+ intent.putExtra(ContextHubManager.EXTRA_CONTEXT_HUB_INFO, mAttachedContextHubInfo);
+ return intent;
+ }
+
+ /**
+ * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE and the
+ * ContextHubManager.EXTRA_NANOAPP_ID extra fields
+ *
+ * @param eventType the ContextHubManager.Event type describing the event
+ * @param nanoAppId the ID of the nanoapp this event is for
+ * @return the Intent object
+ */
+ private Intent createIntent(int eventType, long nanoAppId) {
+ Intent intent = createIntent(eventType);
+ intent.putExtra(ContextHubManager.EXTRA_NANOAPP_ID, nanoAppId);
+ return intent;
+ }
+
+ /**
+ * Sends an intent to any existing PendingIntent
+ *
+ * @param supplier method to create the extra Intent
+ */
+ private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
+ if (mPendingIntentRequest.hasPendingIntent()) {
+ Intent intent = supplier.get();
+ try {
+ mPendingIntentRequest.getPendingIntent().send(
+ mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */,
+ Manifest.permission.LOCATION_HARDWARE /* requiredPermission */,
+ null /* options */);
+ } catch (PendingIntent.CanceledException e) {
+ // The PendingIntent is no longer valid
+ Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
+ + " (host endpoint ID " + mHostEndPointId + ")");
+ mPendingIntentRequest.clear();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 6d3a3b6..dc3bfbc 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1627,7 +1627,8 @@
// fold tethering stats and operations into uid snapshot
final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
- NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot);
+ NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
+ mUseBpfTrafficStats);
uidSnapshot.combineAllValues(tetherSnapshot);
final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -1637,7 +1638,8 @@
final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
if (vtStats != null) {
vtStats.filter(UID_ALL, ifaces, TAG_ALL);
- NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats);
+ NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
+ mUseBpfTrafficStats);
uidSnapshot.combineAllValues(vtStats);
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 7b35b91..465a2cf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -1052,6 +1052,7 @@
e.writeLong(callStat.maxRequestSizeBytes);
e.writeLong(callStat.recordedCallCount);
e.writeInt(callStat.screenInteractive ? 1 : 0);
+ e.writeInt(callStat.callingUid);
pulledData.add(e);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
index 0da5742..59c4067 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
@@ -60,8 +60,7 @@
public void testLastFocusedStackIsUpdatedWhenMovingStack() {
// Create a stack at bottom.
final ActivityDisplay display = mSupervisor.getDefaultDisplay();
- final ActivityStack stack = display.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, !ON_TOP);
+ final ActivityStack stack = new StackBuilder(mSupervisor).setOnTop(!ON_TOP).build();
final ActivityStack prevFocusedStack = display.getFocusedStack();
stack.moveToFront("moveStackToFront");
@@ -140,16 +139,14 @@
*/
@Test
public void testTopRunningActivity() {
- // Create stack to hold focus.
final ActivityDisplay display = mSupervisor.getDefaultDisplay();
- final ActivityStack emptyStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
-
final KeyguardController keyguard = mSupervisor.getKeyguardController();
- final ActivityStack stack = mSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
- .setStack(stack).build();
+ final ActivityStack stack = new StackBuilder(mSupervisor).build();
+ final ActivityRecord activity = stack.getTopActivity();
+
+ // Create empty stack on top.
+ final ActivityStack emptyStack =
+ new StackBuilder(mSupervisor).setCreateActivity(false).build();
// Make sure the top running activity is not affected when keyguard is not locked.
assertTopRunningActivity(activity, display);
@@ -159,8 +156,8 @@
assertEquals(activity, display.topRunningActivity());
assertNull(display.topRunningActivity(true /* considerKeyguardState */));
- // Change focus to stack with activity.
- stack.moveToFront("focusChangeToTestStack");
+ // Move stack with activity to top.
+ stack.moveToFront("testStackToFront");
assertEquals(stack, display.getFocusedStack());
assertEquals(activity, display.topRunningActivity());
assertNull(display.topRunningActivity(true /* considerKeyguardState */));
@@ -175,11 +172,10 @@
// Ensure the show when locked activity is returned.
assertTopRunningActivity(showWhenLockedActivity, display);
- // Change focus back to empty stack.
- emptyStack.moveToFront("focusChangeToEmptyStack");
- assertEquals(emptyStack, display.getFocusedStack());
- // If there is no running activity in focused stack, the running activity in next focusable
- // stack should be returned.
+ // Move empty stack to front. The running activity in focusable stack which below the
+ // empty stack should be returned.
+ emptyStack.moveToFront("emptyStackToFront");
+ assertEquals(stack, display.getFocusedStack());
assertTopRunningActivity(showWhenLockedActivity, display);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index ffc7fa2..1015008 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -79,10 +79,9 @@
super.setUp();
setupActivityTaskManagerService();
- mStack = mSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
- mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ mStack = new StackBuilder(mSupervisor).build();
+ mTask = mStack.getChildAt(0);
+ mActivity = mTask.getTopActivity();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 2c993d3..bdceec7 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -359,15 +359,12 @@
public void testFindTaskToMoveToFrontWhenRecentsOnTop() throws Exception {
// Create stack/task on default display.
final ActivityDisplay display = mSupervisor.getDefaultDisplay();
- final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final TaskRecord targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build();
+ final TestActivityStack targetStack = new StackBuilder(mSupervisor).setOnTop(false).build();
+ final TaskRecord targetTask = targetStack.getChildAt(0);
// Create Recents on top of the display.
- final ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_RECENTS, true /* onTop */);
- final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
- new ActivityBuilder(mService).setTask(task).build();
+ final ActivityStack stack =
+ new StackBuilder(mSupervisor).setActivityType(ACTIVITY_TYPE_RECENTS).build();
final String reason = "findTaskToMoveToFront";
mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 53f67af..ea33de74 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -26,8 +26,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
-import static com.android.server.am.ActivityStack.ActivityState.FINISHING;
import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
@@ -361,14 +361,12 @@
translucentStack.topRunningActivityLocked();
topRunningTranslucentActivity.finishing = true;
- // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
- // of the stack list.
- assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ // Home stack should be visible even there are no running activities.
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
// Home should be visible if we are starting an activity within it.
assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
- // The translucent stack should be visible since it is the top of the stack list even though
- // it has its activity marked as finishing.
- assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+ // The translucent stack shouldn't be visible since its activity marked as finishing.
+ assertFalse(translucentStack.shouldBeVisible(null /* starting */));
}
@Test
@@ -672,9 +670,10 @@
// There is still an activity1 in stack1 so the activity2 should be added to finishing list
// that will be destroyed until idle.
+ stack2.getTopActivity().visible = true;
final ActivityRecord activity2 = finishCurrentActivity(stack2);
- assertEquals(FINISHING, activity2.getState());
- assertTrue(mSupervisor.mFinishingActivities.contains(activity2));
+ assertEquals(STOPPING, activity2.getState());
+ assertTrue(mSupervisor.mStoppingActivities.contains(activity2));
// The display becomes empty. Since there is no next activity to be idle, the activity
// should be destroyed immediately with updating configuration to restore original state.
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 094241e..adf861b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -39,14 +39,6 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
-import android.content.pm.PackageManagerInternal;
-import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.DisplayWindowController;
-
-import org.junit.Rule;
-import org.mockito.invocation.InvocationOnMock;
-
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
@@ -54,6 +46,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -73,7 +66,10 @@
import com.android.server.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.ServiceThread;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.AppWindowContainerController;
+import com.android.server.wm.DisplayWindowController;
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.RootWindowContainerController;
import com.android.server.wm.StackWindowController;
@@ -83,7 +79,9 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
import java.io.File;
import java.util.List;
@@ -608,23 +606,9 @@
@Override
<T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
int stackId, boolean onTop) {
- if (windowingMode == WINDOWING_MODE_PINNED) {
- return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) {
- @Override
- Rect getDefaultPictureInPictureBounds(float aspectRatio) {
- return new Rect(50, 50, 100, 100);
- }
-
- @Override
- PinnedStackWindowController createStackWindowController(int displayId,
- boolean onTop, Rect outBounds) {
- return mock(PinnedStackWindowController.class);
- }
- };
- } else {
- return (T) new TestActivityStack(
- this, stackId, mSupervisor, windowingMode, activityType, onTop);
- }
+ return new StackBuilder(mSupervisor).setDisplay(this)
+ .setWindowingMode(windowingMode).setActivityType(activityType)
+ .setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build();
}
@Override
@@ -677,8 +661,19 @@
private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET;
TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
- int windowingMode, int activityType, boolean onTop) {
+ int windowingMode, int activityType, boolean onTop, boolean createActivity) {
super(display, stackId, supervisor, windowingMode, activityType, onTop);
+ if (createActivity) {
+ new ActivityBuilder(mService).setCreateTask(true).setStack(this).build();
+ if (onTop) {
+ // We move the task to front again in order to regain focus after activity
+ // added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack}
+ // could be other stacks (e.g. home stack).
+ moveToFront("createActivityStack");
+ } else {
+ moveToBack("createActivityStack", null);
+ }
+ }
}
@Override
@@ -750,4 +745,71 @@
ActivityOptions options) {
}
}
+
+ protected static class StackBuilder {
+ private final ActivityStackSupervisor mSupervisor;
+ private ActivityDisplay mDisplay;
+ private int mStackId = -1;
+ private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ private int mActivityType = ACTIVITY_TYPE_STANDARD;
+ private boolean mOnTop = true;
+ private boolean mCreateActivity = true;
+
+ StackBuilder(ActivityStackSupervisor supervisor) {
+ mSupervisor = supervisor;
+ mDisplay = mSupervisor.getDefaultDisplay();
+ }
+
+ StackBuilder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ StackBuilder setActivityType(int activityType) {
+ mActivityType = activityType;
+ return this;
+ }
+
+ StackBuilder setStackId(int stackId) {
+ mStackId = stackId;
+ return this;
+ }
+
+ StackBuilder setDisplay(ActivityDisplay display) {
+ mDisplay = display;
+ return this;
+ }
+
+ StackBuilder setOnTop(boolean onTop) {
+ mOnTop = onTop;
+ return this;
+ }
+
+ StackBuilder setCreateActivity(boolean createActivity) {
+ mCreateActivity = createActivity;
+ return this;
+ }
+
+ <T extends ActivityStack> T build() {
+ final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
+ if (mWindowingMode == WINDOWING_MODE_PINNED) {
+ return (T) new PinnedActivityStack(mDisplay, stackId, mSupervisor, mOnTop) {
+ @Override
+ Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+ return new Rect(50, 50, 100, 100);
+ }
+
+ @Override
+ PinnedStackWindowController createStackWindowController(int displayId,
+ boolean onTop, Rect outBounds) {
+ return mock(PinnedStackWindowController.class);
+ }
+ };
+ } else {
+ return (T) new TestActivityStack(mDisplay, stackId, mSupervisor, mWindowingMode,
+ mActivityType, mOnTop, mCreateActivity);
+ }
+ }
+
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index 27e8c63..129b835 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -883,7 +883,7 @@
MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, true);
+ ACTIVITY_TYPE_STANDARD, true /* onTop */, false /* createActivity */);
mDisplay = display;
}
diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
index aa3046f..849a411 100644
--- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
@@ -16,9 +16,7 @@
package com.android.server.am;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -30,7 +28,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -72,8 +69,8 @@
final int numStacks = 2;
for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
- final ActivityStack stack = new TestActivityStack(display, stackIndex, mSupervisor,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
+ final ActivityStack stack =
+ new StackBuilder(mSupervisor).setCreateActivity(false).build();
display.addChild(stack, POSITION_BOTTOM);
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index 047addd..793d6b0 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -61,6 +61,7 @@
private static final long TIME_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_000L;
+ private static final long TIME_1_MIN = 10 * 60_000L;
private static final long MAX_OBSERVER_PER_UID = 10;
private static final long MIN_TIME_LIMIT = 4_000L;
@@ -77,7 +78,8 @@
PKG_GAME1, PKG_GAME2
};
- private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private CountDownLatch mLimitReachedLatch = new CountDownLatch(1);
+ private CountDownLatch mSessionEndLatch = new CountDownLatch(1);
private AppTimeLimitController mController;
@@ -85,18 +87,24 @@
private long mUptimeMillis;
- AppTimeLimitController.OnLimitReachedListener mListener
- = new AppTimeLimitController.OnLimitReachedListener() {
+ AppTimeLimitController.TimeLimitCallbackListener mListener =
+ new AppTimeLimitController.TimeLimitCallbackListener() {
+ @Override
+ public void onLimitReached(int observerId, int userId, long timeLimit,
+ long timeElapsed,
+ PendingIntent callbackIntent) {
+ mLimitReachedLatch.countDown();
+ }
- @Override
- public void onLimitReached(int observerId, int userId, long timeLimit, long timeElapsed,
- PendingIntent callbackIntent) {
- mCountDownLatch.countDown();
- }
- };
+ @Override
+ public void onSessionEnd(int observerId, int userId, long timeElapsed,
+ PendingIntent callbackIntent) {
+ mSessionEndLatch.countDown();
+ }
+ };
class MyAppTimeLimitController extends AppTimeLimitController {
- MyAppTimeLimitController(AppTimeLimitController.OnLimitReachedListener listener,
+ MyAppTimeLimitController(AppTimeLimitController.TimeLimitCallbackListener listener,
Looper looper) {
super(listener, looper);
}
@@ -107,7 +115,12 @@
}
@Override
- protected long getObserverPerUidLimit() {
+ protected long getAppUsageObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ @Override
+ protected long getUsageSessionObserverPerUidLimit() {
return MAX_OBSERVER_PER_UID;
}
@@ -129,188 +142,551 @@
mThread.quit();
}
- /** Verify observer is added */
+ /** Verify app usage observer is added */
@Test
- public void testAddObserver() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID2));
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
+ public void testAppUsageObserver_AddObserver() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
}
- /** Verify observer is removed */
+ /** Verify usage session observer is added */
@Test
- public void testRemoveObserver() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- mController.removeObserver(UID, OBS_ID1, USER_ID);
- assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+ public void testUsageSessionObserver_AddObserver() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify app usage observer is removed */
+ @Test
+ public void testAppUsageObserver_RemoveObserver() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Verify usage session observer is removed */
+ @Test
+ public void testUsageSessionObserver_RemoveObserver() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
/** Re-adding an observer should result in only one copy */
@Test
- public void testObserverReAdd() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- addObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ public void testAppUsageObserver_ObserverReAdd() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
assertTrue("Observer wasn't added",
- mController.getObserverGroup(OBS_ID1, USER_ID).timeLimit == TIME_10_MIN);
- mController.removeObserver(UID, OBS_ID1, USER_ID);
- assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+ mController.getAppUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Re-adding an observer should result in only one copy */
+ @Test
+ public void testUsageSessionObserver_ObserverReAdd() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added",
+ mController.getSessionUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Different type observers can be registered to the same observerId value */
+ @Test
+ public void testAllObservers_ExclusiveObserverIds() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+
+ AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
+ OBS_ID1);
+ AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
+ OBS_ID1);
+
+ // Verify data still intact
+ assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
+ assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
}
/** Verify that usage across different apps within a group are added up */
@Test
- public void testAccumulation() throws Exception {
+ public void testAppUsageObserver_Accumulation() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- moveToForeground(PKG_SOC1);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
// Add 10 mins
setTime(TIME_10_MIN);
- moveToBackground(PKG_SOC1);
+ stopUsage(PKG_SOC1);
- long timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+ AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
assertEquals(TIME_10_MIN * 2, timeRemaining);
- moveToForeground(PKG_SOC1);
+ startUsage(PKG_SOC1);
setTime(TIME_10_MIN * 2);
- moveToBackground(PKG_SOC1);
+ stopUsage(PKG_SOC1);
- timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
assertEquals(TIME_10_MIN, timeRemaining);
setTime(TIME_30_MIN);
- assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
// Add a different package in the group
- moveToForeground(PKG_GAME1);
+ startUsage(PKG_GAME1);
setTime(TIME_30_MIN + TIME_10_MIN);
- moveToBackground(PKG_GAME1);
+ stopUsage(PKG_GAME1);
- assertEquals(0, mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining);
- assertTrue(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
+ /** Verify that usage across different apps within a group are added up */
+ @Test
+ public void testUsageSessionObserver_Accumulation() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN * 2);
+ stopUsage(PKG_SOC1);
+
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN, timeRemaining);
+
+ setTime(TIME_30_MIN);
+
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ // Add a different package in the group
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
/** Verify that time limit does not get triggered due to a different app */
@Test
- public void testTimeoutOtherApp() throws Exception {
+ public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, 4_000L);
- moveToForeground(PKG_SOC2);
- assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
setTime(6_000L);
- moveToBackground(PKG_SOC2);
- assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
+ /** Verify that time limit does not get triggered due to a different app */
+ @Test
+ public void testUsageSessionObserver_TimeoutOtherApp() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(6_000L);
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
}
/** Verify the timeout message is delivered at the right time */
@Test
- public void testTimeout() throws Exception {
+ public void testAppUsageObserver_Timeout() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, 4_000L);
- moveToForeground(PKG_SOC1);
+ addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC1);
setTime(6_000L);
- assertTrue(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
- moveToBackground(PKG_SOC1);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID1));
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Verify the timeout message is delivered at the right time */
+ @Test
+ public void testUsageSessionObserver_Timeout() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
/** If an app was already running, make sure it is partially counted towards the time limit */
@Test
- public void testAlreadyRunning() throws Exception {
+ public void testAppUsageObserver_AlreadyRunning() throws Exception {
setTime(TIME_10_MIN);
- moveToForeground(PKG_GAME1);
+ startUsage(PKG_GAME1);
setTime(TIME_30_MIN);
- addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
setTime(TIME_30_MIN + TIME_10_MIN);
- moveToBackground(PKG_GAME1);
- assertFalse(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
- moveToForeground(PKG_GAME2);
+ startUsage(PKG_GAME2);
setTime(TIME_30_MIN + TIME_30_MIN);
- moveToBackground(PKG_GAME2);
- assertTrue(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID2));
+ assertFalse(hasAppUsageObserver(UID, OBS_ID2));
+ }
+
+ /** If an app was already running, make sure it is partially counted towards the time limit */
+ @Test
+ public void testUsageSessionObserver_AlreadyRunning() throws Exception {
+ setTime(TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN);
+ addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ startUsage(PKG_GAME2);
+ setTime(TIME_30_MIN + TIME_30_MIN);
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
}
/** If watched app is already running, verify the timeout callback happens at the right time */
@Test
- public void testAlreadyRunningTimeout() throws Exception {
+ public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
setTime(0);
- moveToForeground(PKG_SOC1);
+ startUsage(PKG_SOC1);
setTime(TIME_10_MIN);
// 10 second time limit
- addObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ addAppUsageObserver(OBS_ID1, GROUP_SOC, 10_000L);
setTime(TIME_10_MIN + 5_000L);
// Shouldn't call back in 6 seconds
- assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
setTime(TIME_10_MIN + 10_000L);
// Should call back by 11 seconds (6 earlier + 5 now)
- assertTrue(mCountDownLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID1));
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
}
- /** Verify that App Time Limit Controller will limit the number of observerIds */
+ /** If watched app is already running, verify the timeout callback happens at the right time */
@Test
- public void testMaxObserverLimit() throws Exception {
+ public void testUsageSessionObserver_AlreadyRunningTimeout() throws Exception {
+ setTime(0);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ // 10 second time limit
+ addUsageSessionObserver(OBS_ID1, GROUP_SOC, 10_000L, 1_000L);
+ setTime(TIME_10_MIN + 5_000L);
+ // Shouldn't call back in 6 seconds
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(TIME_10_MIN + 10_000L);
+ // Should call back by 11 seconds (6 earlier + 5 now)
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+ * observers
+ */
+ @Test
+ public void testAppUsageObserver_MaxObserverLimit() throws Exception {
boolean receivedException = false;
int ANOTHER_UID = UID + 1;
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID2, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID3, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID4, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID6, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID7, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID8, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID9, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID10, GROUP1, TIME_30_MIN);
// Readding an observer should not cause an IllegalStateException
- addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
// Adding an observer for a different uid shouldn't cause an IllegalStateException
- mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+ mController.addAppUsageObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
try {
- addObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID11, GROUP1, TIME_30_MIN);
} catch (IllegalStateException ise) {
receivedException = true;
}
assertTrue("Should have caused an IllegalStateException", receivedException);
}
- /** Verify that addObserver minimum time limit is one minute */
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for usage session
+ * observers
+ */
@Test
- public void testMinimumTimeLimit() throws Exception {
+ public void testUsageSessionObserver_MaxObserverLimit() throws Exception {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ boolean receivedException = false;
+ int ANOTHER_UID = UID + 1;
+ addUsageSessionObserver(OBS_ID2, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID3, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID4, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID6, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID7, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID8, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID9, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID10, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ // Readding an observer should not cause an IllegalStateException
+ addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ // Adding an observer for a different uid shouldn't cause an IllegalStateException
+ mController.addUsageSessionObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN,
+ null, null, USER_ID);
+ try {
+ addUsageSessionObserver(OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ } catch (IllegalStateException ise) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalStateException", receivedException);
+ }
+
+ /** Verify that addAppUsageObserver minimum time limit is one minute */
+ @Test
+ public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
boolean receivedException = false;
// adding an observer with a one minute time limit should not cause an exception
- addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+ addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
try {
- addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+ addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
} catch (IllegalArgumentException iae) {
receivedException = true;
}
assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
- private void moveToForeground(String packageName) {
- mController.moveToForeground(packageName, "class", USER_ID);
+ /** Verify that addUsageSessionObserver minimum time limit is one minute */
+ @Test
+ public void testUsageSessionObserver_MinimumTimeLimit() throws Exception {
+ boolean receivedException = false;
+ // test also for session observers
+ addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT, TIME_1_MIN);
+ try {
+ addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT - 1, TIME_1_MIN);
+ } catch (IllegalArgumentException iae) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
- private void moveToBackground(String packageName) {
- mController.moveToBackground(packageName, "class", USER_ID);
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testAppUsageObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
- private void addObserver(int observerId, String[] packages, long timeLimit) {
- mController.addObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testUsageSessionObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
- /** Is there still an observer by that id */
- private boolean hasObserver(int observerId) {
- return mController.getObserverGroup(observerId, USER_ID) != null;
+ /** Verify that a session will continue if usage starts again within the session threshold */
+ @Test
+ public void testUsageSessionObserver_ContinueSession() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 2_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ stopUsage(PKG_SOC1);
+ // Wait momentarily, Session should not end
+ assertFalse(mSessionEndLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ setTime(7_000L);
+ startUsage(PKG_SOC1);
+ setTime(10_500L);
+ stopUsage(PKG_SOC1);
+ // Total usage time has not reached the limit. Time limit callback should not fire yet
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ setTime(10_600L);
+ startUsage(PKG_SOC1);
+ setTime(12_000L);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(1_900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify that a new session will start if next usage starts after the session threshold */
+ @Test
+ public void testUsageSessionObserver_NewSession() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ stopUsage(PKG_SOC1);
+ // Wait for longer than the session threshold. Session end callback should not be triggered
+ // because the usage timelimit hasn't been triggered.
+ assertFalse(mSessionEndLatch.await(1_500L, TimeUnit.MILLISECONDS));
+
+ setTime(7_500L);
+ // This should be the start of a new session
+ startUsage(PKG_SOC1);
+ setTime(16_000L);
+ stopUsage(PKG_SOC1);
+ // Total usage has exceed the timelimit, but current session time has not
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ setTime(16_100L);
+ startUsage(PKG_SOC1);
+ setTime(18_000L);
+ assertTrue(mLimitReachedLatch.await(2000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify that the callbacks will be triggered for multiple sessions */
+ @Test
+ public void testUsageSessionObserver_RepeatSessions() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(9_000L);
+ stopUsage(PKG_SOC1);
+ // Stutter usage here, to reduce real world time needed trigger limit reached callback
+ startUsage(PKG_SOC1);
+ setTime(11_000L);
+ assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 1 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+
+ // Rearm the countdown latches
+ mLimitReachedLatch = new CountDownLatch(1);
+ mSessionEndLatch = new CountDownLatch(1);
+
+ // New session start
+ setTime(20_000L);
+ startUsage(PKG_SOC1);
+ setTime(29_000L);
+ stopUsage(PKG_SOC1);
+ startUsage(PKG_SOC1);
+ setTime(31_000L);
+ assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ private void startUsage(String packageName) {
+ mController.noteUsageStart(packageName, USER_ID);
+ }
+
+ private void stopUsage(String packageName) {
+ mController.noteUsageStop(packageName, USER_ID);
+ }
+
+ private void addAppUsageObserver(int observerId, String[] packages, long timeLimit) {
+ mController.addAppUsageObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ }
+
+ private void addUsageSessionObserver(int observerId, String[] packages, long timeLimit,
+ long sessionThreshold) {
+ mController.addUsageSessionObserver(UID, observerId, packages, timeLimit, sessionThreshold,
+ null, null, USER_ID);
+ }
+
+ /** Is there still an app usage observer by that id */
+ private boolean hasAppUsageObserver(int uid, int observerId) {
+ return mController.getAppUsageGroup(uid, observerId) != null;
+ }
+
+ /** Is there still an usage session observer by that id */
+ private boolean hasUsageSessionObserver(int uid, int observerId) {
+ return mController.getSessionUsageGroup(uid, observerId) != null;
}
private void setTime(long time) {
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 5916b04c..eaaf9b2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -22,17 +22,16 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
-import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -57,72 +56,432 @@
private final MyHandler mHandler;
- private OnLimitReachedListener mListener;
+ private TimeLimitCallbackListener mListener;
private static final long MAX_OBSERVER_PER_UID = 1000;
private static final long ONE_MINUTE = 60_000L;
+ /** Collection of data for each user that has reported usage */
@GuardedBy("mLock")
private final SparseArray<UserData> mUsers = new SparseArray<>();
- private static class UserData {
+ /**
+ * Collection of data for each app that is registering observers
+ * WARNING: Entries are currently not removed, based on the assumption there are a small
+ * fixed number of apps on device that can register observers.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ObserverAppData> mObserverApps = new SparseArray<>();
+
+ private class UserData {
/** userId of the user */
- private @UserIdInt int userId;
+ private @UserIdInt
+ int userId;
- /** The app that is currently in the foreground */
- private String currentForegroundedPackage;
+ /** Set of the currently active entities */
+ private final ArraySet<String> currentlyActive = new ArraySet<>();
- /** The time when the current app came to the foreground */
- private long currentForegroundedTime;
-
- /** Map from package name for quick lookup */
- private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>();
-
- /** Map of observerId to details of the time limit group */
- private SparseArray<TimeLimitGroup> groups = new SparseArray<>();
-
- /** Map of the number of observerIds registered by uid */
- private SparseIntArray observerIdCounts = new SparseIntArray();
+ /** Map from entity name for quick lookup */
+ private final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
private UserData(@UserIdInt int userId) {
this.userId = userId;
}
+
+ @GuardedBy("mLock")
+ boolean isActive(String[] entities) {
+ // TODO: Consider using a bloom filter here if number of actives becomes large
+ final int size = entities.length;
+ for (int i = 0; i < size; i++) {
+ if (currentlyActive.contains(entities[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ void addUsageGroup(UsageGroup group) {
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+ if (list == null) {
+ list = new ArrayList<>();
+ observedMap.put(group.mObserved[i], list);
+ }
+ list.add(group);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void removeUsageGroup(UsageGroup group) {
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ final ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+ if (list != null) {
+ list.remove(group);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" userId=");
+ pw.println(userId);
+ pw.print(" Currently Active:");
+ final int nActive = currentlyActive.size();
+ for (int i = 0; i < nActive; i++) {
+ pw.print(currentlyActive.valueAt(i));
+ pw.print(", ");
+ }
+ pw.println();
+ pw.print(" Observed Entities:");
+ final int nEntities = currentlyActive.size();
+ for (int i = 0; i < nEntities; i++) {
+ pw.print(observedMap.keyAt(i));
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ }
+
+
+ private class ObserverAppData {
+ /** uid of the observing app */
+ private int uid;
+
+ /** Map of observerId to details of the time limit group */
+ SparseArray<AppUsageGroup> appUsageGroups = new SparseArray<>();
+
+ /** Map of observerId to details of the time limit group */
+ SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
+
+ private ObserverAppData(int uid) {
+ this.uid = uid;
+ }
+
+ @GuardedBy("mLock")
+ void removeAppUsageGroup(int observerId) {
+ appUsageGroups.remove(observerId);
+ }
+
+ @GuardedBy("mLock")
+ void removeSessionUsageGroup(int observerId) {
+ sessionUsageGroups.remove(observerId);
+ }
+
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" uid=");
+ pw.println(uid);
+ pw.println(" App Usage Groups:");
+ final int nAppUsageGroups = appUsageGroups.size();
+ for (int i = 0; i < nAppUsageGroups; i++) {
+ appUsageGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
+ pw.println(" Session Usage Groups:");
+ final int nSessionUsageGroups = appUsageGroups.size();
+ for (int i = 0; i < nSessionUsageGroups; i++) {
+ sessionUsageGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
+ }
}
/**
* Listener interface for being informed when an app group's time limit is reached.
*/
- public interface OnLimitReachedListener {
+ public interface TimeLimitCallbackListener {
/**
* Time limit for a group, keyed by the observerId, has been reached.
- * @param observerId The observerId of the group whose limit was reached
- * @param userId The userId
- * @param timeLimit The original time limit in milliseconds
- * @param timeElapsed How much time was actually spent on apps in the group, in milliseconds
+ *
+ * @param observerId The observerId of the group whose limit was reached
+ * @param userId The userId
+ * @param timeLimit The original time limit in milliseconds
+ * @param timeElapsed How much time was actually spent on apps in the group, in
+ * milliseconds
* @param callbackIntent The PendingIntent to send when the limit is reached
*/
public void onLimitReached(int observerId, @UserIdInt int userId, long timeLimit,
long timeElapsed, PendingIntent callbackIntent);
+
+ /**
+ * Session ended for a group, keyed by the observerId, after limit was reached.
+ *
+ * @param observerId The observerId of the group whose limit was reached
+ * @param userId The userId
+ * @param timeElapsed How much time was actually spent on apps in the group, in
+ * milliseconds
+ * @param callbackIntent The PendingIntent to send when the limit is reached
+ */
+ public void onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed,
+ PendingIntent callbackIntent);
}
- static class TimeLimitGroup {
- int requestingUid;
- int observerId;
- String[] packages;
- long timeLimit;
- long timeRequested;
- long timeRemaining;
- PendingIntent callbackIntent;
- String currentPackage;
- long timeCurrentPackageStarted;
- int userId;
+ abstract class UsageGroup {
+ protected int mObserverId;
+ protected String[] mObserved;
+ protected long mTimeLimitMs;
+ protected long mUsageTimeMs;
+ protected int mActives;
+ protected long mLastKnownUsageTimeMs;
+ protected WeakReference<UserData> mUserRef;
+ protected WeakReference<ObserverAppData> mObserverAppRef;
+ protected PendingIntent mLimitReachedCallback;
+
+ UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed,
+ long timeLimitMs, PendingIntent limitReachedCallback) {
+ mUserRef = new WeakReference<>(user);
+ mObserverAppRef = new WeakReference<>(observerApp);
+ mObserverId = observerId;
+ mObserved = observed;
+ mTimeLimitMs = timeLimitMs;
+ mLimitReachedCallback = limitReachedCallback;
+ }
+
+ @GuardedBy("mLock")
+ public long getTimeLimitMs() { return mTimeLimitMs; }
+
+ @GuardedBy("mLock")
+ public long getUsageTimeMs() { return mUsageTimeMs; }
+
+ @GuardedBy("mLock")
+ public void remove() {
+ UserData user = mUserRef.get();
+ if (user != null) {
+ user.removeUsageGroup(this);
+ }
+ // Clear the callback, so any racy inflight message will do nothing
+ mLimitReachedCallback = null;
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStart(long startTimeMs) {
+ noteUsageStart(startTimeMs, startTimeMs);
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStart(long startTimeMs, long currentTimeMs) {
+ if (mActives++ == 0) {
+ mLastKnownUsageTimeMs = startTimeMs;
+ final long timeRemaining =
+ mTimeLimitMs - mUsageTimeMs + currentTimeMs - startTimeMs;
+ if (timeRemaining > 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Posting timeout for " + mObserverId + " for "
+ + timeRemaining + "ms");
+ }
+ postCheckTimeoutLocked(this, timeRemaining);
+ }
+ } else {
+ if (mActives > mObserved.length) {
+ // Try to get to a sane state and log the issue
+ mActives = mObserved.length;
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+ final Object[] array = user.currentlyActive.toArray();
+ Slog.e(TAG,
+ "Too many noted usage starts! Observed entities: " + Arrays.toString(
+ mObserved) + " Active Entities: " + Arrays.toString(array));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStop(long stopTimeMs) {
+ if (--mActives == 0) {
+ final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs;
+ mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs;
+ if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) {
+ // Crossed the limit
+ if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId);
+ postInformLimitReachedListenerLocked(this);
+ }
+ cancelCheckTimeoutLocked(this);
+ } else {
+ if (mActives < 0) {
+ // Try to get to a sane state and log the issue
+ mActives = 0;
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+ final Object[] array = user.currentlyActive.toArray();
+ Slog.e(TAG,
+ "Too many noted usage stops! Observed entities: " + Arrays.toString(
+ mObserved) + " Active Entities: " + Arrays.toString(array));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void checkTimeout(long currentTimeMs) {
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+
+ long timeRemainingMs = mTimeLimitMs - mUsageTimeMs;
+
+ if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + timeRemainingMs);
+
+ // Already reached the limit, no need to report again
+ if (timeRemainingMs <= 0) return;
+
+ if (DEBUG) {
+ Slog.d(TAG, "checkTimeout");
+ }
+
+ // Double check that at least one entity in this group is currently active
+ if (user.isActive(mObserved)) {
+ if (DEBUG) {
+ Slog.d(TAG, "checkTimeout group is active");
+ }
+ final long timeUsedMs = currentTimeMs - mLastKnownUsageTimeMs;
+ if (timeRemainingMs <= timeUsedMs) {
+ if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
+ // Hit the limit, set timeRemaining to zero to avoid checking again
+ mUsageTimeMs += timeUsedMs;
+ mLastKnownUsageTimeMs = currentTimeMs;
+ AppTimeLimitController.this.postInformLimitReachedListenerLocked(this);
+ } else {
+ if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
+ AppTimeLimitController.this.postCheckTimeoutLocked(this,
+ timeRemainingMs - timeUsedMs);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void onLimitReached() {
+ UserData user = mUserRef.get();
+ if (user == null) return;
+ if (mListener != null) {
+ mListener.onLimitReached(mObserverId, user.userId, mTimeLimitMs, mUsageTimeMs,
+ mLimitReachedCallback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" Group id=");
+ pw.print(mObserverId);
+ pw.print(" timeLimit=");
+ pw.print(mTimeLimitMs);
+ pw.print(" used=");
+ pw.print(mUsageTimeMs);
+ pw.print(" lastKnownUsage=");
+ pw.print(mLastKnownUsageTimeMs);
+ pw.print(" mActives=");
+ pw.print(mActives);
+ pw.print(" observed=");
+ pw.print(Arrays.toString(mObserved));
+ }
}
+ class AppUsageGroup extends UsageGroup {
+ public AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeAppUsageGroup(mObserverId);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void onLimitReached() {
+ super.onLimitReached();
+ // Unregister since the limit has been met and observer was informed.
+ remove();
+ }
+ }
+
+ class SessionUsageGroup extends UsageGroup {
+ private long mLastUsageEndTimeMs;
+ private long mNewSessionThresholdMs;
+ private PendingIntent mSessionEndCallback;
+
+ public SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback,
+ long newSessionThresholdMs, PendingIntent sessionEndCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ this.mNewSessionThresholdMs = newSessionThresholdMs;
+ this.mSessionEndCallback = sessionEndCallback;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeSessionUsageGroup(mObserverId);
+ }
+ // Clear the callback, so any racy inflight messages will do nothing
+ mSessionEndCallback = null;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void noteUsageStart(long startTimeMs, long currentTimeMs) {
+ if (mActives == 0) {
+ if (startTimeMs - mLastUsageEndTimeMs > mNewSessionThresholdMs) {
+ // New session has started, clear usage time.
+ mUsageTimeMs = 0;
+ }
+ AppTimeLimitController.this.cancelInformSessionEndListener(this);
+ }
+ super.noteUsageStart(startTimeMs, currentTimeMs);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void noteUsageStop(long stopTimeMs) {
+ super.noteUsageStop(stopTimeMs);
+ if (mActives == 0) {
+ mLastUsageEndTimeMs = stopTimeMs;
+ if (mUsageTimeMs >= mTimeLimitMs) {
+ // Usage has ended. Schedule the session end callback to be triggered once
+ // the new session threshold has been reached
+ AppTimeLimitController.this.postInformSessionEndListenerLocked(this,
+ mNewSessionThresholdMs);
+ }
+
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void onSessionEnd() {
+ UserData user = mUserRef.get();
+ if (user == null) return;
+ if (mListener != null) {
+ mListener.onSessionEnd(mObserverId, user.userId, mUsageTimeMs, mSessionEndCallback);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.print(" lastUsageEndTime=");
+ pw.print(mLastUsageEndTimeMs);
+ pw.print(" newSessionThreshold=");
+ pw.print(mNewSessionThresholdMs);
+ }
+ }
+
+
private class MyHandler extends Handler {
-
static final int MSG_CHECK_TIMEOUT = 1;
- static final int MSG_INFORM_LISTENER = 2;
+ static final int MSG_INFORM_LIMIT_REACHED_LISTENER = 2;
+ static final int MSG_INFORM_SESSION_END = 3;
MyHandler(Looper looper) {
super(looper);
@@ -132,10 +491,19 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK_TIMEOUT:
- checkTimeout((TimeLimitGroup) msg.obj);
+ synchronized (mLock) {
+ ((UsageGroup) msg.obj).checkTimeout(getUptimeMillis());
+ }
break;
- case MSG_INFORM_LISTENER:
- informListener((TimeLimitGroup) msg.obj);
+ case MSG_INFORM_LIMIT_REACHED_LISTENER:
+ synchronized (mLock) {
+ ((UsageGroup) msg.obj).onLimitReached();
+ }
+ break;
+ case MSG_INFORM_SESSION_END:
+ synchronized (mLock) {
+ ((SessionUsageGroup) msg.obj).onSessionEnd();
+ }
break;
default:
super.handleMessage(msg);
@@ -144,7 +512,7 @@
}
}
- public AppTimeLimitController(OnLimitReachedListener listener, Looper looper) {
+ public AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper) {
mHandler = new MyHandler(looper);
mListener = listener;
}
@@ -157,7 +525,13 @@
/** Overrideable for testing purposes */
@VisibleForTesting
- protected long getObserverPerUidLimit() {
+ protected long getAppUsageObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ /** Overrideable for testing purposes */
+ @VisibleForTesting
+ protected long getUsageSessionObserverPerUidLimit() {
return MAX_OBSERVER_PER_UID;
}
@@ -167,6 +541,21 @@
return ONE_MINUTE;
}
+ @VisibleForTesting
+ AppUsageGroup getAppUsageGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).appUsageGroups.get(observerId);
+ }
+ }
+
+ @VisibleForTesting
+ SessionUsageGroup getSessionUsageGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).sessionUsageGroups.get(
+ observerId);
+ }
+ }
+
/** Returns an existing UserData object for the given userId, or creates one */
@GuardedBy("mLock")
private UserData getOrCreateUserDataLocked(int userId) {
@@ -178,6 +567,17 @@
return userData;
}
+ /** Returns an existing ObserverAppData object for the given uid, or creates one */
+ @GuardedBy("mLock")
+ private ObserverAppData getOrCreateObserverAppDataLocked(int uid) {
+ ObserverAppData appData = mObserverApps.get(uid);
+ if (appData == null) {
+ appData = new ObserverAppData(uid);
+ mObserverApps.put(uid, appData);
+ }
+ return appData;
+ }
+
/** Clean up data if user is removed */
public void onUserRemoved(int userId) {
synchronized (mLock) {
@@ -187,300 +587,219 @@
}
/**
- * Registers an observer with the given details. Existing observer with the same observerId
- * is removed.
+ * Check if group has any currently active entities.
*/
- public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit,
- PendingIntent callbackIntent, @UserIdInt int userId) {
+ @GuardedBy("mLock")
+ private void noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs) {
+ // TODO: Consider using a bloom filter here if number of actives becomes large
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ if (user.currentlyActive.contains(group.mObserved[i])) {
+ // Entity is currently active. Start group's usage.
+ group.noteUsageStart(currentTimeMs);
+ }
+ }
+ }
+ /**
+ * Registers an app usage observer with the given details.
+ * Existing app usage observer with the same observerId will be removed.
+ */
+ public void addAppUsageObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
if (timeLimit < getMinTimeLimit()) {
throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
}
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
- removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true);
-
- final int observerIdCount = user.observerIdCounts.get(requestingUid, 0);
- if (observerIdCount >= getObserverPerUidLimit()) {
- throw new IllegalStateException(
- "Too many observers added by uid " + requestingUid);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ AppUsageGroup group = observerApp.appUsageGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ observerApp.appUsageGroups.get(observerId).remove();
}
- user.observerIdCounts.put(requestingUid, observerIdCount + 1);
- TimeLimitGroup group = new TimeLimitGroup();
- group.observerId = observerId;
- group.callbackIntent = callbackIntent;
- group.packages = packages;
- group.timeLimit = timeLimit;
- group.timeRemaining = group.timeLimit;
- group.timeRequested = getUptimeMillis();
- group.requestingUid = requestingUid;
- group.timeCurrentPackageStarted = -1L;
- group.userId = userId;
-
- user.groups.append(observerId, group);
-
- addGroupToPackageMapLocked(user, packages, group);
+ final int observerIdCount = observerApp.appUsageGroups.size();
+ if (observerIdCount >= getAppUsageObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
+ }
+ group = new AppUsageGroup(user, observerApp, observerId, observed, timeLimit,
+ callbackIntent);
+ observerApp.appUsageGroups.append(observerId, group);
if (DEBUG) {
- Slog.d(TAG, "addObserver " + packages + " for " + timeLimit);
+ Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
}
- // Handle the case where a target package is already in the foreground when observer
- // is added.
- if (user.currentForegroundedPackage != null && inPackageList(group.packages,
- user.currentForegroundedPackage)) {
- group.timeCurrentPackageStarted = group.timeRequested;
- group.currentPackage = user.currentForegroundedPackage;
- if (group.timeRemaining > 0) {
- postCheckTimeoutLocked(group, group.timeRemaining);
- }
- }
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
}
}
/**
* Remove a registered observer by observerId and calling uid.
+ *
* @param requestingUid The calling uid
- * @param observerId The unique observer id for this user
- * @param userId The user id of the observer
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
*/
- public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) {
+ public void removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId) {
+ synchronized (mLock) {
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ observerApp.appUsageGroups.get(observerId).remove();
+ }
+ }
+
+
+ /**
+ * Registers a usage session observer with the given details.
+ * Existing usage session observer with the same observerId will be removed.
+ */
+ public void addUsageSessionObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, long sessionThresholdTime,
+ PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+ @UserIdInt int userId) {
+ if (timeLimit < getMinTimeLimit()) {
+ throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+ }
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
- removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ observerApp.sessionUsageGroups.get(observerId).remove();
+ }
+
+ final int observerIdCount = observerApp.sessionUsageGroups.size();
+ if (observerIdCount >= getUsageSessionObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
+ }
+ group = new SessionUsageGroup(user, observerApp, observerId, observed, timeLimit,
+ limitReachedCallbackIntent, sessionThresholdTime, sessionEndCallbackIntent);
+ observerApp.sessionUsageGroups.append(observerId, group);
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
}
}
- @VisibleForTesting
- TimeLimitGroup getObserverGroup(int observerId, int userId) {
+ /**
+ * Remove a registered observer by observerId and calling uid.
+ *
+ * @param requestingUid The calling uid
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
+ */
+ public void removeUsageSessionObserver(int requestingUid, int observerId,
+ @UserIdInt int userId) {
synchronized (mLock) {
- return getOrCreateUserDataLocked(userId).groups.get(observerId);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ observerApp.sessionUsageGroups.get(observerId).remove();
}
}
- private static boolean inPackageList(String[] packages, String packageName) {
- return ArrayUtils.contains(packages, packageName);
+ /**
+ * Called when an entity becomes active.
+ *
+ * @param name The entity that became active
+ * @param userId The user
+ */
+ public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active");
+ if (user.currentlyActive.contains(name)) {
+ throw new IllegalArgumentException(
+ "Unable to start usage for " + name + ", already in use");
+ }
+ final long currentTime = getUptimeMillis();
+
+ // Add to the list of active entities
+ user.currentlyActive.add(name);
+
+ ArrayList<UsageGroup> groups = user.observedMap.get(name);
+ if (groups == null) return;
+
+ final int size = groups.size();
+ for (int i = 0; i < size; i++) {
+ UsageGroup group = groups.get(i);
+ group.noteUsageStart(currentTime);
+ }
+ }
+ }
+
+ /**
+ * Called when an entity becomes inactive.
+ *
+ * @param name The entity that became inactive
+ * @param userId The user
+ */
+ public void noteUsageStop(String name, int userId) throws IllegalArgumentException {
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive");
+ if (!user.currentlyActive.remove(name)) {
+ throw new IllegalArgumentException(
+ "Unable to stop usage for " + name + ", not in use");
+ }
+ final long currentTime = getUptimeMillis();
+
+ // Check if any of the groups need to watch for this entity
+ ArrayList<UsageGroup> groups = user.observedMap.get(name);
+ if (groups == null) return;
+
+ final int size = groups.size();
+ for (int i = 0; i < size; i++) {
+ UsageGroup group = groups.get(i);
+ group.noteUsageStop(currentTime);
+ }
+ }
}
@GuardedBy("mLock")
- private void removeObserverLocked(UserData user, int requestingUid, int observerId,
- boolean readding) {
- TimeLimitGroup group = user.groups.get(observerId);
- if (group != null && group.requestingUid == requestingUid) {
- removeGroupFromPackageMapLocked(user, group);
- user.groups.remove(observerId);
- mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
- final int observerIdCount = user.observerIdCounts.get(requestingUid);
- if (observerIdCount <= 1 && !readding) {
- user.observerIdCounts.delete(requestingUid);
- } else {
- user.observerIdCounts.put(requestingUid, observerIdCount - 1);
- }
- }
- }
-
- /**
- * Called when an app has moved to the foreground.
- * @param packageName The app that is foregrounded
- * @param className The className of the activity
- * @param userId The user
- */
- public void moveToForeground(String packageName, String className, int userId) {
- synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(userId);
- if (DEBUG) Slog.d(TAG, "Setting mCurrentForegroundedPackage to " + packageName);
- // Note the current foreground package
- user.currentForegroundedPackage = packageName;
- user.currentForegroundedTime = getUptimeMillis();
-
- // Check if any of the groups need to watch for this package
- maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime);
- }
- }
-
- /**
- * Called when an app is sent to the background.
- *
- * @param packageName
- * @param className
- * @param userId
- */
- public void moveToBackground(String packageName, String className, int userId) {
- synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(userId);
- if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) {
- Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage
- + " and now backgrounded = " + packageName);
- return;
- }
- final long stopTime = getUptimeMillis();
-
- // Add up the usage time to all groups that contain the package
- ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
- if (groups != null) {
- final int size = groups.size();
- for (int i = 0; i < size; i++) {
- final TimeLimitGroup group = groups.get(i);
- // Don't continue to send
- if (group.timeRemaining <= 0) continue;
-
- final long startTime = Math.max(user.currentForegroundedTime,
- group.timeRequested);
- long diff = stopTime - startTime;
- group.timeRemaining -= diff;
- if (group.timeRemaining <= 0) {
- if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + group.observerId);
- postInformListenerLocked(group);
- }
- // Reset indicators that observer was added when package was already fg
- group.currentPackage = null;
- group.timeCurrentPackageStarted = -1L;
- mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
- }
- }
- user.currentForegroundedPackage = null;
- }
- }
-
- private void postInformListenerLocked(TimeLimitGroup group) {
- mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LISTENER,
+ private void postInformLimitReachedListenerLocked(UsageGroup group) {
+ mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LIMIT_REACHED_LISTENER,
group));
}
- /**
- * Inform the observer and unregister it, as the limit has been reached.
- * @param group the observed group
- */
- private void informListener(TimeLimitGroup group) {
- if (mListener != null) {
- mListener.onLimitReached(group.observerId, group.userId, group.timeLimit,
- group.timeLimit - group.timeRemaining, group.callbackIntent);
- }
- // Unregister since the limit has been met and observer was informed.
- synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(group.userId);
- removeObserverLocked(user, group.requestingUid, group.observerId, false);
- }
- }
-
- /** Check if any of the groups care about this package and set up delayed messages */
@GuardedBy("mLock")
- private void maybeWatchForPackageLocked(UserData user, String packageName, long uptimeMillis) {
- ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
- if (groups == null) return;
-
- final int size = groups.size();
- for (int i = 0; i < size; i++) {
- TimeLimitGroup group = groups.get(i);
- if (group.timeRemaining > 0) {
- group.timeCurrentPackageStarted = uptimeMillis;
- group.currentPackage = packageName;
- if (DEBUG) {
- Slog.d(TAG, "Posting timeout for " + packageName + " for "
- + group.timeRemaining + "ms");
- }
- postCheckTimeoutLocked(group, group.timeRemaining);
- }
- }
+ private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
+ timeout);
}
- private void addGroupToPackageMapLocked(UserData user, String[] packages,
- TimeLimitGroup group) {
- for (int i = 0; i < packages.length; i++) {
- ArrayList<TimeLimitGroup> list = user.packageMap.get(packages[i]);
- if (list == null) {
- list = new ArrayList<>();
- user.packageMap.put(packages[i], list);
- }
- list.add(group);
- }
+ @GuardedBy("mLock")
+ private void cancelInformSessionEndListener(SessionUsageGroup group) {
+ mHandler.removeMessages(MyHandler.MSG_INFORM_SESSION_END, group);
}
- /**
- * Remove the group reference from the package to group mapping, which is 1 to many.
- * @param group The group to remove from the package map.
- */
- private void removeGroupFromPackageMapLocked(UserData user, TimeLimitGroup group) {
- final int mapSize = user.packageMap.size();
- for (int i = 0; i < mapSize; i++) {
- ArrayList<TimeLimitGroup> list = user.packageMap.valueAt(i);
- list.remove(group);
- }
- }
-
- private void postCheckTimeoutLocked(TimeLimitGroup group, long timeout) {
+ @GuardedBy("mLock")
+ private void postCheckTimeoutLocked(UsageGroup group, long timeout) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_CHECK_TIMEOUT, group),
timeout);
}
- /**
- * See if the given group has reached the timeout if the current foreground app is included
- * and it exceeds the time remaining.
- * @param group the group of packages to check
- */
- void checkTimeout(TimeLimitGroup group) {
- // For each package in the group, check if any of the currently foregrounded apps are adding
- // up to hit the limit and inform the observer
- synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(group.userId);
- // This group doesn't exist anymore, nothing to see here.
- if (user.groups.get(group.observerId) != group) return;
-
- if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + group.timeRemaining);
-
- // Already reached the limit, no need to report again
- if (group.timeRemaining <= 0) return;
-
- if (DEBUG) {
- Slog.d(TAG, "checkTimeout foregroundedPackage="
- + user.currentForegroundedPackage);
- }
-
- if (inPackageList(group.packages, user.currentForegroundedPackage)) {
- if (DEBUG) {
- Slog.d(TAG, "checkTimeout package in foreground="
- + user.currentForegroundedPackage);
- }
- if (group.timeCurrentPackageStarted < 0) {
- Slog.w(TAG, "startTime was not set correctly for " + group);
- }
- final long timeInForeground = getUptimeMillis() - group.timeCurrentPackageStarted;
- if (group.timeRemaining <= timeInForeground) {
- if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
- // Hit the limit, set timeRemaining to zero to avoid checking again
- group.timeRemaining -= timeInForeground;
- postInformListenerLocked(group);
- // Reset
- group.timeCurrentPackageStarted = -1L;
- group.currentPackage = null;
- } else {
- if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
- postCheckTimeoutLocked(group, group.timeRemaining - timeInForeground);
- }
- }
- }
+ @GuardedBy("mLock")
+ private void cancelCheckTimeoutLocked(UsageGroup group) {
+ mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
}
void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println("\n App Time Limits");
- int nUsers = mUsers.size();
+ final int nUsers = mUsers.size();
for (int i = 0; i < nUsers; i++) {
- UserData user = mUsers.valueAt(i);
- pw.print(" User "); pw.println(user.userId);
- int nGroups = user.groups.size();
- for (int j = 0; j < nGroups; j++) {
- TimeLimitGroup group = user.groups.valueAt(j);
- pw.print(" Group id="); pw.print(group.observerId);
- pw.print(" timeLimit="); pw.print(group.timeLimit);
- pw.print(" remaining="); pw.print(group.timeRemaining);
- pw.print(" currentPackage="); pw.print(group.currentPackage);
- pw.print(" timeCurrentPkgStarted="); pw.print(group.timeCurrentPackageStarted);
- pw.print(" packages="); pw.println(Arrays.toString(group.packages));
- }
- pw.println();
- pw.print(" currentForegroundedPackage=");
- pw.println(user.currentForegroundedPackage);
+ pw.print(" User ");
+ mUsers.valueAt(i).dump(pw);
+ }
+ pw.println();
+ final int nObserverApps = mObserverApps.size();
+ for (int i = 0; i < nObserverApps; i++) {
+ pw.print(" Observer App ");
+ mObserverApps.valueAt(i).dump(pw);
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dd1ddfa..2621252 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -165,16 +165,36 @@
mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
mAppTimeLimit = new AppTimeLimitController(
- (observerId, userId, timeLimit, timeElapsed, callbackIntent) -> {
- Intent intent = new Intent();
- intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
- intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
- intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
- try {
- callbackIntent.send(getContext(), 0, intent);
- } catch (PendingIntent.CanceledException e) {
- Slog.w(TAG, "Couldn't deliver callback: "
- + callbackIntent);
+ new AppTimeLimitController.TimeLimitCallbackListener() {
+ @Override
+ public void onLimitReached(int observerId, int userId, long timeLimit,
+ long timeElapsed, PendingIntent callbackIntent) {
+ if (callbackIntent == null) return;
+ Intent intent = new Intent();
+ intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+ try {
+ callbackIntent.send(getContext(), 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Couldn't deliver callback: "
+ + callbackIntent);
+ }
+ }
+
+ @Override
+ public void onSessionEnd(int observerId, int userId, long timeElapsed,
+ PendingIntent callbackIntent) {
+ if (callbackIntent == null) return;
+ Intent intent = new Intent();
+ intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+ try {
+ callbackIntent.send(getContext(), 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Couldn't deliver callback: "
+ + callbackIntent);
+ }
}
}, mHandler.getLooper());
@@ -412,12 +432,18 @@
mAppStandby.reportEvent(event, elapsedRealtime, userId);
switch (event.mEventType) {
case Event.MOVE_TO_FOREGROUND:
- mAppTimeLimit.moveToForeground(event.getPackageName(), event.getClassName(),
- userId);
+ try {
+ mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to note usage start", iae);
+ }
break;
case Event.MOVE_TO_BACKGROUND:
- mAppTimeLimit.moveToBackground(event.getPackageName(), event.getClassName(),
- userId);
+ try {
+ mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to note usage stop", iae);
+ }
break;
}
}
@@ -1151,16 +1177,70 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void registerUsageSessionObserver(int sessionObserverId, String[] observed,
+ long timeLimitMs, long sessionThresholdTimeMs,
+ PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+ String callingPackage) {
+ if (!hasObserverPermission(callingPackage)) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+
+ if (observed == null || observed.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one observed entity");
+ }
+ if (limitReachedCallbackIntent == null) {
+ throw new NullPointerException("limitReachedCallbackIntent can't be null");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.registerUsageSessionObserver(callingUid, sessionObserverId,
+ observed, timeLimitMs, sessionThresholdTimeMs, limitReachedCallbackIntent,
+ sessionEndCallbackIntent, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
+ if (!hasObserverPermission(callingPackage)) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
long timeLimitMs, PendingIntent callbackIntent, int userId) {
- mAppTimeLimit.addObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
+ mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
userId);
}
void unregisterAppUsageObserver(int callingUid, int observerId, int userId) {
- mAppTimeLimit.removeObserver(callingUid, observerId, userId);
+ mAppTimeLimit.removeAppUsageObserver(callingUid, observerId, userId);
+ }
+
+ void registerUsageSessionObserver(int callingUid, int observerId, String[] observed,
+ long timeLimitMs, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent,
+ PendingIntent sessionEndCallbackIntent, int userId) {
+ mAppTimeLimit.addUsageSessionObserver(callingUid, observerId, observed, timeLimitMs,
+ sessionThresholdTime, limitReachedCallbackIntent, sessionEndCallbackIntent, userId);
+ }
+
+ void unregisterUsageSessionObserver(int callingUid, int sessionObserverId, int userId) {
+ mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 66918eb7..f4b5f86 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2231,6 +2231,13 @@
public static final String KEY_CALL_WAITING_OVER_UT_WARNING_BOOL =
"call_waiting_over_ut_warning_bool";
+ /**
+ * Flag indicating whether to support "Network default" option in Caller ID settings for Calling
+ * Line Identification Restriction (CLIR).
+ */
+ public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL =
+ "support_clir_network_default_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -2579,6 +2586,7 @@
sDefaults.putBoolean(KEY_CALL_BARRING_OVER_UT_WARNING_BOOL, false);
sDefaults.putBoolean(KEY_CALLER_ID_OVER_UT_WARNING_BOOL, false);
sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
}
/**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 0459667..0ba18ee 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -43,6 +43,7 @@
import android.net.INetworkPolicyManager;
import android.net.NetworkCapabilities;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -781,8 +782,13 @@
IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
@Override
public void onSubscriptionsChanged() {
- if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
- mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
+ mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
};
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 203b7b1..f484d1f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -41,6 +41,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.BatteryStats;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
@@ -1311,10 +1312,9 @@
* Returns the unique device ID, for example, the IMEI for GSM and the MEID
* or ESN for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
@@ -1322,7 +1322,7 @@
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId() {
try {
@@ -1341,10 +1341,9 @@
* Returns the unique device ID of a subscription, for example, the IMEI for
* GSM and the MEID for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
@@ -1354,7 +1353,7 @@
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId(int slotIndex) {
// FIXME this assumes phoneId == slotIndex
@@ -1374,14 +1373,13 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei() {
return getImei(getSlotIndex());
@@ -1391,16 +1389,15 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
* @param slotIndex of which IMEI is returned
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -1445,14 +1442,13 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid() {
return getMeid(getSlotIndex());
@@ -1461,16 +1457,15 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
- * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
- * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
- * managed profile on the device; for more details see <a
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+ * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+ * that owns a managed profile on the device; for more details see <a
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*
* @param slotIndex of which MEID is returned
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // No support for device / profile owner.
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -2968,7 +2963,7 @@
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSimSerialNumber() {
return getSimSerialNumber(getSubId());
@@ -3130,7 +3125,7 @@
* href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
* access is deprecated and will be removed in a future release.
*/
- @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSubscriberId() {
return getSubscriberId(getSubId());
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 56f0c09..eb6be65 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -188,13 +188,6 @@
if (checkReadDeviceIdentifiers(context, pid, uid, callingPackage)) {
return true;
}
- // Calling packages with carrier privileges will also have access to device identifiers, but
- // this may be removed in a future release.
- if (SubscriptionManager.isValidSubscriptionId(subId) && getCarrierPrivilegeStatus(
- TELEPHONY_SUPPLIER, subId, uid)
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return true;
- }
// else the calling package is not authorized to access the device identifiers; call
// a central method to report the failure based on the target SDK and if the calling package
// has the READ_PHONE_STATE permission or carrier privileges that were previously required
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index 8f18d07..d6dbf5a 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -39,6 +39,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.NetworkStats.Entry;
import android.os.Process;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
@@ -785,7 +786,38 @@
ArrayMap<String, String> stackedIface = new ArrayMap<>();
stackedIface.put(v4Iface, baseIface);
- NetworkStats.Entry otherEntry = new NetworkStats.Entry(
+ // Ipv4 traffic sent/received by an app on stacked interface.
+ final NetworkStats.Entry appEntry = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+ 30501490 /* rxBytes */,
+ 22401 /* rxPackets */,
+ 876235 /* txBytes */,
+ 13805 /* txPackets */,
+ 0 /* operations */);
+
+ // Traffic measured for the root uid on the base interface if eBPF is in use.
+ // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+ // overhead (20 bytes per packet), only for TX traffic.
+ final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry(
+ baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+ 163577 /* rxBytes */,
+ 187 /* rxPackets */,
+ 1169942 /* txBytes */,
+ 13902 /* txPackets */,
+ 0 /* operations */);
+
+ // Traffic measured for the root uid on the base interface if xt_qtaguid is in use.
+ // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+ // overhead (20 bytes per packet), in both directions.
+ final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry(
+ baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+ 31113087 /* rxBytes */,
+ 22588 /* rxPackets */,
+ 1169942 /* txBytes */,
+ 13902 /* txPackets */,
+ 0 /* operations */);
+
+ final NetworkStats.Entry otherEntry = new NetworkStats.Entry(
otherIface, appUid, SET_DEFAULT, TAG_NONE,
2600 /* rxBytes */,
2 /* rxPackets */,
@@ -793,39 +825,41 @@
3 /* txPackets */,
0 /* operations */);
- NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE,
- 30501490 /* rxBytes */,
- 22401 /* rxPackets */,
- 876235 /* txBytes */,
- 13805 /* txPackets */,
- 0 /* operations */)
- .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE,
- 31113087,
- 22588,
- 1169942,
- 13902,
- 0)
+ final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
+ .addValues(appEntry)
+ .addValues(xtRootUidEntry)
.addValues(otherEntry);
- stats.apply464xlatAdjustments(stackedIface);
+ final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
+ .addValues(appEntry)
+ .addValues(ebpfRootUidEntry)
+ .addValues(otherEntry);
- assertEquals(3, stats.size());
- assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ statsXt.apply464xlatAdjustments(stackedIface, false);
+ statsEbpf.apply464xlatAdjustments(stackedIface, true);
+
+ assertEquals(3, statsXt.size());
+ assertEquals(3, statsEbpf.size());
+ final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
30949510,
22401,
1152335,
13805,
0);
- assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry(
+ baseIface, 0, SET_DEFAULT, TAG_NONE,
163577,
187,
17607,
97,
0);
- assertEquals(otherEntry, stats.getValues(2, null));
+ assertEquals(expectedAppUid, statsXt.getValues(0, null));
+ assertEquals(expectedRootUid, statsXt.getValues(1, null));
+ assertEquals(otherEntry, statsXt.getValues(2, null));
+ assertEquals(expectedAppUid, statsEbpf.getValues(0, null));
+ assertEquals(expectedRootUid, statsEbpf.getValues(1, null));
+ assertEquals(otherEntry, statsEbpf.getValues(2, null));
}
@Test
@@ -850,7 +884,7 @@
.addValues(secondEntry);
// Empty map: no adjustment
- stats.apply464xlatAdjustments(new ArrayMap<>());
+ stats.apply464xlatAdjustments(new ArrayMap<>(), false);
assertEquals(2, stats.size());
assertEquals(firstEntry, stats.getValues(0, null));