Merge "Create a new permission to override Overridable change ids" into sc-dev
diff --git a/Android.bp b/Android.bp
index 33b66b6..8b15f02 100644
--- a/Android.bp
+++ b/Android.bp
@@ -465,6 +465,7 @@
static_libs: [
"android.net.ipsec.ike.stubs.module_lib",
"framework-appsearch.stubs.module_lib",
+ "framework-connectivity.stubs.module_lib",
"framework-graphics.stubs.module_lib",
"framework-media.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
@@ -487,6 +488,7 @@
"android.net.ipsec.ike.impl",
"framework-minus-apex",
"framework-appsearch.impl",
+ "framework-connectivity.impl",
"framework-graphics.impl",
"framework-mediaprovider.impl",
"framework-permission.impl",
@@ -643,14 +645,17 @@
defaults: ["framework-aidl-export-defaults"],
srcs: [
":framework-non-updatable-sources",
- ":framework-connectivity-sources",
"core/java/**/*.logtags",
],
// See comment on framework-atb-backward-compatibility module below
exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"],
aidl: {
generate_get_transaction_name: true,
- local_include_dirs: ["media/aidl"],
+ local_include_dirs: [
+ "media/aidl",
+ // TODO: move to include_dirs when migrated to packages/modules
+ "packages/Connectivity/framework/aidl-export",
+ ],
include_dirs: ["frameworks/av/aidl"],
},
dxflags: [
@@ -703,8 +708,6 @@
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base",
- // TODO: remove when framework-connectivity can build against API
- "//frameworks/base/packages/Connectivity/framework",
// TODO(b/147128803) remove the below lines
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
@@ -743,6 +746,7 @@
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
+ "framework-connectivity.impl", // TODO(b/182859030): should be removed
"framework-appsearch.impl", // TODO(b/146218515): should be removed
"framework-updatable-stubs-module_libs_api",
],
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index b51c2aa..f766fcf 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -32,6 +32,7 @@
},
libs: [
"app-compat-annotations",
+ "framework-connectivity.stubs.module_lib",
"framework-minus-apex",
"unsupportedappusage",
],
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 78c5b15..3ea1922 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -29,6 +30,7 @@
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -42,7 +44,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
/**
* This class provides access to the system alarm services. These allow you
@@ -194,6 +198,15 @@
public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
/**
+ * Flag for alarms: Used to mark prioritized alarms. These alarms will get to execute while idle
+ * and can be sent separately from other alarms that may be already due at the time.
+ * These alarms can be set via
+ * {@link #setPrioritized(int, long, long, String, Executor, OnAlarmListener)}
+ * @hide
+ */
+ public static final int FLAG_PRIORITIZE = 1 << 6;
+
+ /**
* For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
* {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
* {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
@@ -227,15 +240,15 @@
final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
final OnAlarmListener mListener;
- Handler mHandler;
+ Executor mExecutor;
IAlarmCompleteListener mCompletion;
public ListenerWrapper(OnAlarmListener listener) {
mListener = listener;
}
- public void setHandler(Handler h) {
- mHandler = h;
+ void setExecutor(Executor e) {
+ mExecutor = e;
}
public void cancel() {
@@ -250,7 +263,7 @@
public void doAlarm(IAlarmCompleteListener alarmManager) {
mCompletion = alarmManager;
- mHandler.post(this);
+ mExecutor.execute(this);
}
@Override
@@ -368,7 +381,7 @@
*/
public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
- null, null, null);
+ (Handler) null, null, null);
}
/**
@@ -457,7 +470,7 @@
public void setRepeating(@AlarmType int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
@@ -507,7 +520,7 @@
public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
@@ -526,6 +539,53 @@
}
/**
+ * Schedule an alarm that is prioritized by the system while the device is in power saving modes
+ * such as battery saver and device idle (doze).
+ *
+ * <p>
+ * Apps that use this are not guaranteed to get all alarms as requested during power saving
+ * modes, i.e. the system may still impose restrictions on how frequently these alarms will go
+ * off for a particular application, like requiring a certain minimum duration be elapsed
+ * between consecutive alarms. This duration will be normally be in the order of a few minutes.
+ *
+ * <p>
+ * When the system wakes up to deliver these alarms, it may not deliver any of the other pending
+ * alarms set earlier by the calling app, even the special ones set via
+ * {@link #setAndAllowWhileIdle(int, long, PendingIntent)} or
+ * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)}. So the caller should not
+ * expect these to arrive in any relative order to its other alarms.
+ *
+ * @param type type of alarm
+ * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+ * be delivered, expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param windowLengthMillis The length of the requested delivery window,
+ * in milliseconds. The alarm will be delivered no later than this many
+ * milliseconds after {@code windowStartMillis}. Note that this parameter
+ * is a <i>duration,</i> not the timestamp of the end of the window.
+ * @param tag string describing the alarm, used for logging and battery-use
+ * attribution
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param executor {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SCHEDULE_PRIORITIZED_ALARM)
+ public void setPrioritized(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @NonNull String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(tag);
+ Objects.requireNonNull(listener);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, FLAG_PRIORITIZE, null, listener,
+ tag, executor, null, null);
+ }
+
+ /**
* Schedule an alarm to be delivered precisely at the stated time.
*
* <p>
@@ -565,7 +625,7 @@
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null,
null, null);
}
@@ -645,7 +705,7 @@
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
- null, null, null, null, info);
+ null, null, (Handler) null, null, info);
}
/** @hide */
@@ -654,7 +714,7 @@
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, PendingIntent operation, WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
- null, workSource, null);
+ (Handler) null, workSource, null);
}
/**
@@ -698,6 +758,15 @@
long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
String listenerTag, Handler targetHandler, WorkSource workSource,
AlarmClockInfo alarmClock) {
+ final Handler handlerToUse = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation, listener,
+ listenerTag, new HandlerExecutor(handlerToUse), workSource, alarmClock);
+ }
+
+ private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
+ String listenerTag, Executor targetExecutor, WorkSource workSource,
+ AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -726,9 +795,7 @@
sWrappers.put(listener, new WeakReference<>(recipientWrapper));
}
}
-
- final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
- recipientWrapper.setHandler(handler);
+ recipientWrapper.setExecutor(targetExecutor);
}
try {
@@ -834,7 +901,7 @@
public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
- null, null, null, null);
+ null, (Handler) null, null, null);
}
/**
@@ -884,7 +951,7 @@
public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
- operation, null, null, null, null, null);
+ operation, null, null, (Handler) null, null, null);
}
/**
@@ -945,7 +1012,7 @@
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 58fc874..3c9496f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -23,6 +23,7 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_PRIORITIZE;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.INTERVAL_DAY;
import static android.app.AlarmManager.INTERVAL_HOUR;
@@ -100,6 +101,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -221,6 +223,7 @@
AlarmHandler mHandler;
AppWakeupHistory mAppWakeupHistory;
AppWakeupHistory mAllowWhileIdleHistory;
+ private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
IBinder.DeathRecipient mListenerDeathRecipient;
@@ -432,6 +435,8 @@
@VisibleForTesting
static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";
+ @VisibleForTesting
+ static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -470,6 +475,8 @@
// TODO (b/171306433): Change to true by default.
private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false;
+ private static final long DEFAULT_PRIORITY_ALARM_DELAY = 9 * 60_000;
+
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -525,6 +532,12 @@
*/
public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS;
+ /**
+ * Minimum delay between two slots that an app can get for their prioritized alarms, while
+ * the device is in doze.
+ */
+ public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
+
private long mLastAllowWhileIdleWhitelistDuration = -1;
Constants() {
@@ -662,6 +675,10 @@
CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS,
DEFAULT_CRASH_NON_CLOCK_APPS);
break;
+ case KEY_PRIORITY_ALARM_DELAY:
+ PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
+ DEFAULT_PRIORITY_ALARM_DELAY);
+ break;
default:
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
// The quotas need to be updated in order, so we can't just rely
@@ -809,6 +826,11 @@
pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS);
pw.println();
+ pw.print(KEY_PRIORITY_ALARM_DELAY);
+ pw.print("=");
+ TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
+ pw.println();
+
pw.decreaseIndent();
}
@@ -1794,6 +1816,11 @@
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + window;
}
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0);
+ batterySaverPolicyElapsed = (lastDispatch == 0)
+ ? nowElapsed
+ : lastDispatch + mConstants.PRIORITY_ALARM_DELAY;
} else {
// Not allowed.
batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
@@ -1849,6 +1876,12 @@
alarm.sourcePackage, userId, quota) + window;
deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
}
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0);
+ final long whenAllowed = (lastDispatch == 0)
+ ? nowElapsed
+ : lastDispatch + mConstants.PRIORITY_ALARM_DELAY;
+ deviceIdlePolicyTime = Math.min(whenAllowed, mPendingIdleUntil.getWhenElapsed());
} else {
// Not allowed.
deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed();
@@ -2025,7 +2058,12 @@
// make sure the caller is allowed to use the requested kind of alarm, and also
// decide what quota and broadcast options to use.
Bundle idleOptions = null;
- if (exact || allowWhileIdle) {
+ if ((flags & FLAG_PRIORITIZE) != 0) {
+ getContext().enforcePermission(
+ Manifest.permission.SCHEDULE_PRIORITIZED_ALARM,
+ Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
+ flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+ } else if (exact || allowWhileIdle) {
final boolean needsPermission;
boolean lowerQuota;
if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
@@ -2107,6 +2145,7 @@
flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
+ flags &= ~FLAG_PRIORITIZE;
idleOptions = null;
}
@@ -2489,6 +2528,19 @@
pw.println("Allow while idle history:");
mAllowWhileIdleHistory.dump(pw, nowELAPSED);
+ if (mLastPriorityAlarmDispatch.size() > 0) {
+ pw.println("Last priority alarm dispatches:");
+ pw.increaseIndent();
+ for (int i = 0; i < mLastPriorityAlarmDispatch.size(); i++) {
+ pw.print("UID: ");
+ UserHandle.formatUid(pw, mLastPriorityAlarmDispatch.keyAt(i));
+ pw.print(": ");
+ TimeUtils.formatDuration(mLastPriorityAlarmDispatch.valueAt(i), nowELAPSED, pw);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
if (mLog.dump(pw, "Recent problems:")) {
pw.println();
}
@@ -3303,6 +3355,11 @@
mPendingBackgroundAlarms.removeAt(i);
}
}
+ for (int i = mLastPriorityAlarmDispatch.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mLastPriorityAlarmDispatch.keyAt(i)) == userHandle) {
+ mLastPriorityAlarmDispatch.removeAt(i);
+ }
+ }
if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
if (mPendingIdleUntil != null) {
@@ -4103,6 +4160,7 @@
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
sdFilter.addAction(Intent.ACTION_USER_STOPPED);
+ sdFilter.addAction(Intent.ACTION_UID_REMOVED);
getContext().registerReceiver(this, sdFilter);
}
@@ -4132,6 +4190,9 @@
mAllowWhileIdleHistory.removeForUser(userHandle);
}
return;
+ case Intent.ACTION_UID_REMOVED:
+ mLastPriorityAlarmDispatch.delete(uid);
+ return;
case Intent.ACTION_PACKAGE_REMOVED:
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
// This package is being updated; don't kill its alarms.
@@ -4522,11 +4583,11 @@
if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid);
}
- if (isAllowedWhileIdleRestricted(alarm)) {
- final boolean doze = (mPendingIdleUntil != null);
- final boolean batterySaver = (mAppStateTracker != null
- && mAppStateTracker.isForceAllAppsStandbyEnabled());
- if (doze || batterySaver) {
+ final boolean doze = (mPendingIdleUntil != null);
+ final boolean batterySaver = (mAppStateTracker != null
+ && mAppStateTracker.isForceAllAppsStandbyEnabled());
+ if (doze || batterySaver) {
+ if (isAllowedWhileIdleRestricted(alarm)) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
// device was in doze or battery saver.
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
@@ -4538,6 +4599,16 @@
return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
|| (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
});
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ mLastPriorityAlarmDispatch.put(alarm.creatorUid, nowELAPSED);
+ mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a.creatorUid != alarm.creatorUid
+ || (alarm.flags & FLAG_PRIORITIZE) == 0) {
+ return false;
+ }
+ return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
+ || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
+ });
}
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
diff --git a/core/api/current.txt b/core/api/current.txt
index 2e2147f..935cf70 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -48364,6 +48364,7 @@
method protected int computeVerticalScrollRange();
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
+ method @Nullable public android.view.translation.ViewTranslationRequest createTranslationRequest(@NonNull int[]);
method @Deprecated public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
@@ -48584,6 +48585,7 @@
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarThumbDrawable();
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable();
method public int getVerticalScrollbarWidth();
+ method @Nullable public android.view.translation.ViewTranslationCallback getViewTranslationCallback();
method public android.view.ViewTreeObserver getViewTreeObserver();
method public int getVisibility();
method public final int getWidth();
@@ -48727,6 +48729,7 @@
method public void onStartTemporaryDetach();
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
+ method public void onTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse);
method @CallSuper public void onVisibilityAggregated(boolean);
method protected void onVisibilityChanged(@NonNull android.view.View, int);
method public void onWindowFocusChanged(boolean);
@@ -48935,6 +48938,7 @@
method public void setVerticalScrollbarPosition(int);
method public void setVerticalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable);
method public void setVerticalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable);
+ method public void setViewTranslationCallback(@NonNull android.view.translation.ViewTranslationCallback);
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
@@ -52757,6 +52761,12 @@
method public void onStarted(@NonNull String, @NonNull String);
}
+ @UiThread public interface ViewTranslationCallback {
+ method public boolean onClearTranslation(@NonNull android.view.View);
+ method public boolean onHideTranslation(@NonNull android.view.View);
+ method public boolean onShowTranslation(@NonNull android.view.View);
+ }
+
public final class ViewTranslationRequest implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.view.autofill.AutofillId getAutofillId();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0944e76..e8bdd41 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -245,6 +245,7 @@
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String ROTATE_SURFACE_FLINGER = "android.permission.ROTATE_SURFACE_FLINGER";
+ field public static final String SCHEDULE_PRIORITIZED_ALARM = "android.permission.SCHEDULE_PRIORITIZED_ALARM";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
@@ -424,6 +425,7 @@
public class AlarmManager {
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
+ method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
}
public class AppOpsManager {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 6dd6744..2430c09 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -921,6 +922,43 @@
mGlobal.setTemporaryBrightness(displayId, brightness);
}
+
+ /**
+ * Sets the brightness of the specified display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId the logical display id
+ * @param brightness The brightness value from 0.0f to 1.0f.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ public void setBrightness(int displayId, @FloatRange(from = 0f, to = 1f) float brightness) {
+ mGlobal.setBrightness(displayId, brightness);
+ }
+
+
+ /**
+ * Gets the brightness of the specified display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The display of which brightness value to get from.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getBrightness(int displayId) {
+ return mGlobal.getBrightness(displayId);
+ }
+
+
/**
* Temporarily sets the auto brightness adjustment factor.
* <p>
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index fd0431c5..06efc4f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -690,6 +690,37 @@
}
}
+
+ /**
+ * Sets the brightness of the display.
+ *
+ * @param brightness The brightness value from 0.0f to 1.0f.
+ *
+ * @hide
+ */
+ public void setBrightness(int displayId, float brightness) {
+ try {
+ mDm.setBrightness(displayId, brightness);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the brightness of the display.
+ *
+ * @param displayId The display from which to get the brightness
+ *
+ * @hide
+ */
+ public float getBrightness(int displayId) {
+ try {
+ return mDm.getBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
/**
* Temporarily sets the auto brightness adjustment factor.
* <p>
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index dee9144..3538ff1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -119,6 +119,12 @@
// Temporarily sets the display brightness.
void setTemporaryBrightness(int displayId, float brightness);
+ // Saves the display brightness.
+ void setBrightness(int displayId, float brightness);
+
+ // Retrieves the display brightness.
+ float getBrightness(int displayId);
+
// Temporarily sets the auto brightness adjustment factor.
void setTemporaryAutoBrightnessAdjustment(float adjustment);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1f3c415..591f05f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9176,6 +9176,22 @@
public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete";
/**
+ * Whether the assistant can be triggered by a touch gesture.
+ *
+ * @hide
+ */
+ public static final String ASSIST_TOUCH_GESTURE_ENABLED =
+ "assist_touch_gesture_enabled";
+
+ /**
+ * Whether the assistant can be triggered by long-pressing the home button
+ *
+ * @hide
+ */
+ public static final String ASSIST_LONG_PRESS_HOME_ENABLED =
+ "assist_long_press_home_enabled";
+
+ /**
* Control whether Trust Agents are in active unlock or extend unlock mode.
* @hide
*/
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 98b7dbf..8db6456 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -64,8 +64,14 @@
}
@Override
- void hide(boolean animationFinished, @AnimationType int animationType) {
+ public void hide() {
super.hide();
+ mIsRequestedVisibleAwaitingControl = false;
+ }
+
+ @Override
+ void hide(boolean animationFinished, @AnimationType int animationType) {
+ hide();
if (animationFinished) {
// remove IME surface as IME has finished hide animation.
@@ -122,6 +128,9 @@
hide();
removeSurface();
}
+ if (control != null) {
+ mIsRequestedVisibleAwaitingControl = false;
+ }
}
@Override
diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java
index f177451..bd86a47 100644
--- a/core/java/android/view/SoundEffectConstants.java
+++ b/core/java/android/view/SoundEffectConstants.java
@@ -16,11 +16,14 @@
package android.view;
+import android.annotation.IntDef;
import android.media.AudioManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Random;
/**
@@ -34,25 +37,55 @@
public static final int CLICK = 0;
+ /** Effect id for a navigation left */
public static final int NAVIGATION_LEFT = 1;
+ /** Effect id for a navigation up */
public static final int NAVIGATION_UP = 2;
+ /** Effect id for a navigation right */
public static final int NAVIGATION_RIGHT = 3;
+ /** Effect id for a navigation down */
public static final int NAVIGATION_DOWN = 4;
- /** Sound effect for a repeatedly triggered navigation, e.g. due to long pressing a button */
+ /** Effect id for a repeatedly triggered navigation left, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_LEFT = 5;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation up, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_UP = 6;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation right, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_RIGHT = 7;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation down, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_DOWN = 8;
+ /** @hide */
+ @IntDef(value = {
+ CLICK,
+ NAVIGATION_LEFT,
+ NAVIGATION_UP,
+ NAVIGATION_RIGHT,
+ NAVIGATION_DOWN,
+ NAVIGATION_REPEAT_LEFT,
+ NAVIGATION_REPEAT_UP,
+ NAVIGATION_REPEAT_RIGHT,
+ NAVIGATION_REPEAT_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SoundEffect {}
+
+ /** @hide */
+ @IntDef(prefix = { "NAVIGATION_" }, value = {
+ NAVIGATION_LEFT,
+ NAVIGATION_UP,
+ NAVIGATION_RIGHT,
+ NAVIGATION_DOWN,
+ NAVIGATION_REPEAT_LEFT,
+ NAVIGATION_REPEAT_UP,
+ NAVIGATION_REPEAT_RIGHT,
+ NAVIGATION_REPEAT_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NavigationSoundEffect {}
+
/**
* Get the sonification constant for the focus directions.
- * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
- * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
- * or {@link View#FOCUS_BACKWARD}
-
+ * @param direction The direction of the focus.
* @return The appropriate sonification constant.
* @throws {@link IllegalArgumentException} when the passed direction is not one of the
* documented values.
@@ -76,16 +109,14 @@
/**
* Get the sonification constant for the focus directions
- * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
- * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
- * or {@link View#FOCUS_BACKWARD}
+ * @param direction The direction of the focus.
* @param repeating True if the user long-presses a direction
* @return The appropriate sonification constant
* @throws IllegalArgumentException when the passed direction is not one of the
* documented values.
*/
- public static int getConstantForFocusDirection(@View.FocusDirection int direction,
- boolean repeating) {
+ public static @NavigationSoundEffect int getConstantForFocusDirection(
+ @View.FocusDirection int direction, boolean repeating) {
if (repeating) {
switch (direction) {
case View.FOCUS_RIGHT:
@@ -112,7 +143,7 @@
* @hide
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
- public static boolean isNavigationRepeat(int effectId) {
+ public static boolean isNavigationRepeat(@NavigationSoundEffect int effectId) {
return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN
|| effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT
|| effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7455b8b..a03e9e3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -151,6 +151,8 @@
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
+import android.view.translation.TranslationSpec.DataFormat;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.Checkable;
@@ -5253,6 +5255,9 @@
@Nullable
private String[] mOnReceiveContentMimeTypes;
+ @Nullable
+ private ViewTranslationCallback mViewTranslationCallback;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -26136,9 +26141,9 @@
* <p>The sound effect will only be played if sound effects are enabled by the user, and
* {@link #isSoundEffectsEnabled()} is true.
*
- * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
+ * @param soundConstant One of the constants defined in {@link SoundEffectConstants}.
*/
- public void playSoundEffect(int soundConstant) {
+ public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) {
if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
return;
}
@@ -30717,71 +30722,62 @@
}
}
+ //TODO(b/1960696): update javadoc when dispatchRequestTranslation is ready.
/**
- * Returns a {@link ViewTranslationRequest} to the {@link onStartUiTranslation} which represents
- * the content to be translated.
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * <p>The default implementation does nothing and return null.</p>
+ * <p>The default implementation does nothing and returns null.</p>
*
- * @hide
- *
- * @return the {@link ViewTranslationRequest} which contains the information to be translated.
+ * @param supportedFormats the supported translation formats. For now, the only possible value
+ * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated or
+ * {@code null} if this View doesn't support translation.
+ * The {@link AutofillId} must be set on the returned value.
*/
@Nullable
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public ViewTranslationRequest onCreateTranslationRequest() {
+ public ViewTranslationRequest createTranslationRequest(
+ @NonNull @DataFormat int[] supportedFormats) {
return null;
}
/**
- * Called when the user wants to show the original text instead of the translated text.
+ * Returns a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. If the View supports displaying translated content, it should implement
+ * {@link ViewTranslationCallback}.
*
- * @hide
+ * <p>The default implementation returns null if developers don't set the customized
+ * {@link ViewTranslationCallback} by {@link #setViewTranslationCallback} </p>
*
- * <p> The default implementation does nothing.
+ * @return a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information or {@code null} if this View doesn't support translation.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onPauseUiTranslation() {
- // no-op
+ @Nullable
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return mViewTranslationCallback;
}
/**
- * User can switch back to show the original text, this method called when the user wants to
- * re-show the translated text again.
+ * Sets a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. Developers can provide the customized implementation for show/hide translated
+ * information.
*
- * @hide
- *
- * <p> The default implementation does nothing.</p>
+ * @param callback a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onRestoreUiTranslation() {
- // no-op
+ public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
+ mViewTranslationCallback = callback;
}
/**
- * Called when the user finish the Ui translation and no longer to show the translated text.
- *
- * @hide
- *
- * <p> The default implementation does nothing.</p>
- */
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onFinishUiTranslation() {
- // no-op
- }
-
- /**
- * Called when the request from {@link onStartUiTranslation} is completed by the translation
- * service so that the translation result can be shown.
- *
- * @hide
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService.
*
* <p> The default implementation does nothing.</p>
*
- * @param response the translated information which can be shown in the view.
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
+ public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
// no-op
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdbec8b..0a246a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7754,7 +7754,7 @@
* {@inheritDoc}
*/
@Override
- public void playSoundEffect(int effectId) {
+ public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) {
checkThread();
try {
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 2b12230..ce01469 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -143,6 +143,9 @@
private @Nullable ContentCaptureContext mClientContext;
private @Nullable Insets mInsets;
+ /** Only used in the main Content Capture session, no need to parcel */
+ private boolean mTextHasComposingSpan;
+
/** @hide */
public ContentCaptureEvent(int sessionId, int type, long eventTime) {
mSessionId = sessionId;
@@ -243,11 +246,21 @@
/** @hide */
@NonNull
- public ContentCaptureEvent setText(@Nullable CharSequence text) {
+ public ContentCaptureEvent setText(@Nullable CharSequence text, boolean hasComposingSpan) {
mText = text;
+ mTextHasComposingSpan = hasComposingSpan;
return this;
}
+ /**
+ * The value is not parcelled, become false after parcelled.
+ * @hide
+ */
+ @NonNull
+ public boolean getTextHasComposingSpan() {
+ return mTextHasComposingSpan;
+ }
+
/** @hide */
@NonNull
public ContentCaptureEvent setInsets(@NonNull Insets insets) {
@@ -361,7 +374,7 @@
throw new IllegalArgumentException("mergeEvent(): got "
+ "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
} else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
- setText(event.getText());
+ setText(event.getText(), event.getTextHasComposingSpan());
} else {
Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
+ ") does not support this event type.");
@@ -479,7 +492,7 @@
if (node != null) {
event.setViewNode(node);
}
- event.setText(parcel.readCharSequence());
+ event.setText(parcel.readCharSequence(), false);
if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
event.setParentSessionId(parcel.readInt());
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 5ca793e..f196f75 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -43,12 +43,15 @@
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
+import android.text.Spannable;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Log;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.inputmethod.BaseInputConnection;
import com.android.internal.os.IResultReceiver;
@@ -57,6 +60,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -147,6 +151,12 @@
private final LocalLog mFlushHistory;
/**
+ * If the event in the buffer is of type {@link TYPE_VIEW_TEXT_CHANGED}, this value
+ * indicates whether the event has composing span or not.
+ */
+ private final Map<AutofillId, Boolean> mLastComposingSpan = new ArrayMap<>();
+
+ /**
* Binder object used to update the session state.
*/
@NonNull
@@ -335,26 +345,47 @@
// Some type of events can be merged together
boolean addEvent = true;
- if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
- final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
+ if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+ // We determine whether to add or merge the current event by following criteria:
+ // 1. Don't have composing span: always add.
+ // 2. Have composing span:
+ // 2.1 either last or current text is empty: add.
+ // 2.2 last event doesn't have composing span: add.
+ // Otherwise, merge.
- // We merge two consecutive text change event, unless one of them clears the text.
- if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
- && lastEvent.getId().equals(event.getId())) {
- boolean bothNonEmpty = !TextUtils.isEmpty(lastEvent.getText())
- && !TextUtils.isEmpty(event.getText());
- boolean equalContent = TextUtils.equals(lastEvent.getText(), event.getText());
- if (equalContent) {
- addEvent = false;
- } else if (bothNonEmpty) {
- lastEvent.mergeEvent(event);
- addEvent = false;
- }
- if (!addEvent && sVerbose) {
- Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
- + getSanitizedString(event.getText()));
+ final CharSequence text = event.getText();
+ final boolean textHasComposingSpan = event.getTextHasComposingSpan();
+
+ if (textHasComposingSpan && !mLastComposingSpan.isEmpty()) {
+ final Boolean lastEventHasComposingSpan = mLastComposingSpan.get(event.getId());
+ if (lastEventHasComposingSpan != null && lastEventHasComposingSpan.booleanValue()) {
+ ContentCaptureEvent lastEvent = null;
+ for (int index = mEvents.size() - 1; index >= 0; index--) {
+ final ContentCaptureEvent tmpEvent = mEvents.get(index);
+ if (event.getId().equals(tmpEvent.getId())) {
+ lastEvent = tmpEvent;
+ break;
+ }
+ }
+ if (lastEvent != null) {
+ final CharSequence lastText = lastEvent.getText();
+ final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
+ && !TextUtils.isEmpty(text);
+ boolean equalContent = TextUtils.equals(lastText, text);
+ if (equalContent) {
+ addEvent = false;
+ } else if (bothNonEmpty && lastEventHasComposingSpan) {
+ lastEvent.mergeEvent(event);
+ addEvent = false;
+ }
+ if (!addEvent && sVerbose) {
+ Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+ + getSanitizedString(text));
+ }
+ }
}
}
+ mLastComposingSpan.put(event.getId(), textHasComposingSpan);
}
if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
@@ -374,6 +405,11 @@
mEvents.add(event);
}
+ // TODO: we need to change when the flush happens so that we don't flush while the
+ // composing span hasn't changed. But we might need to keep flushing the events for the
+ // non-editable views and views that don't have the composing state; otherwise some other
+ // Content Capture features may be delayed.
+
final int numberEvents = mEvents.size();
final boolean bufferEvent = numberEvents < maxBufferSize;
@@ -550,6 +586,7 @@
? Collections.emptyList()
: mEvents;
mEvents = null;
+ mLastComposingSpan.clear();
return new ParceledListSlice<>(events);
}
@@ -677,9 +714,16 @@
}
void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ // Since the same CharSequence instance may be reused in the TextView, we need to make
+ // a copy of its content so that its value will not be changed by subsequent updates
+ // in the TextView.
+ final String eventText = text == null ? null : text.toString();
+ final boolean textHasComposingSpan =
+ text instanceof Spannable && BaseInputConnection.getComposingSpanStart(
+ (Spannable) text) >= 0;
mHandler.post(() -> sendEvent(
new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
- .setAutofillId(id).setText(text)));
+ .setAutofillId(id).setText(eventText, textHasComposingSpan)));
}
/** Public because is also used by ViewRootImpl */
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index d79ecca..15d01ae 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -46,7 +46,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
/**
* A controller to manage the ui translation requests for the {@link Activity}.
@@ -77,6 +77,7 @@
private final HandlerThread mWorkerThread;
@NonNull
private final Handler mWorkerHandler;
+ private int mCurrentState;
public UiTranslationController(Activity activity, Context context) {
mActivity = activity;
@@ -101,6 +102,9 @@
}
Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
+ (DEBUG ? ", views: " + views : ""));
+ synchronized (mLock) {
+ mCurrentState = state;
+ }
switch (state) {
case STATE_UI_TRANSLATION_STARTED:
final Pair<TranslationSpec, TranslationSpec> specs =
@@ -114,14 +118,14 @@
}
break;
case STATE_UI_TRANSLATION_PAUSED:
- runForEachView(View::onPauseUiTranslation);
+ runForEachView((view, callback) -> callback.onHideTranslation(view));
break;
case STATE_UI_TRANSLATION_RESUMED:
- runForEachView(View::onRestoreUiTranslation);
+ runForEachView((view, callback) -> callback.onShowTranslation(view));
break;
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
- runForEachView(View::onFinishUiTranslation);
+ runForEachView((view, callback) -> callback.onClearTranslation(view));
synchronized (mLock) {
mViews.clear();
}
@@ -232,11 +236,16 @@
}
final SparseArray<ViewTranslationResponse> translatedResult =
response.getViewTranslationResponses();
+ // TODO(b/177960696): handle virtual views. Check the result if the AutofillId is virtual
+ // AutofillId?
onTranslationCompleted(translatedResult);
}
private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
if (!mActivity.isResumed()) {
+ if (DEBUG) {
+ Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
+ }
return;
}
final int resultCount = translatedResult.size();
@@ -244,6 +253,11 @@
Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
}
synchronized (mLock) {
+ if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) {
+ Log.w(TAG, "onTranslationCompleted: the translation state is finished now. "
+ + "Skip to show the translated text.");
+ return;
+ }
for (int i = 0; i < resultCount; i++) {
final ViewTranslationResponse response = translatedResult.get(i);
final AutofillId autofillId = response.getAutofillId();
@@ -256,18 +270,28 @@
+ " may be gone.");
continue;
}
- mActivity.runOnUiThread(() -> view.onTranslationComplete(response));
+ mActivity.runOnUiThread(() -> {
+ if (view.getViewTranslationCallback() == null) {
+ if (DEBUG) {
+ Log.d(TAG, view + " doesn't support showing translation because of "
+ + "null ViewTranslationCallback.");
+ }
+ return;
+ }
+ view.onTranslationResponse(response);
+ view.getViewTranslationCallback().onShowTranslation(view);
+ });
}
}
}
/**
- * Called when there is an ui translation request comes to request view translation.
+ * Creates a Translator for the given source and target translation specs and start the ui
+ * translation when the Translator is created successfully.
*/
@WorkerThread
private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec,
List<AutofillId> views) {
- // Create Translator
final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec);
if (translator == null) {
Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:"
@@ -295,31 +319,51 @@
*/
private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
synchronized (mLock) {
- // Find Views collect the translation data
- final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
- final ArrayList<View> foundViews = new ArrayList<>();
- findViewsTraversalByAutofillIds(views, foundViews);
- for (int i = 0; i < foundViews.size(); i++) {
- final View view = foundViews.get(i);
- final int currentCount = i;
- mActivity.runOnUiThread(() -> {
- final ViewTranslationRequest request = view.onCreateTranslationRequest();
- if (request != null
- && request.getKeys().size() > 0) {
- requests.add(request);
- }
- if (currentCount == (foundViews.size() - 1)) {
- Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
- + " requests.");
- mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
- UiTranslationController::sendTranslationRequest,
- UiTranslationController.this, translator, requests));
- }
- });
- }
+ // TODO(b/177960696): handle virtual views. Need to check the requested view list is
+ // virtual AutofillId or not
+ findViewsAndCollectViewTranslationRequest(translator, views);
}
}
+ /**
+ * If the translation requested views are not virtual view, we need to traverse the tree to
+ * find the views and get the View's ViewTranslationRequest.
+ */
+ private void findViewsAndCollectViewTranslationRequest(Translator translator,
+ List<AutofillId> views) {
+ // Find Views collect the translation data
+ final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
+ final ArrayList<View> foundViews = new ArrayList<>();
+ findViewsTraversalByAutofillIds(views, foundViews);
+ final int[] supportedFormats = getSupportedFormatsLocked();
+ for (int i = 0; i < foundViews.size(); i++) {
+ final View view = foundViews.get(i);
+ final int currentCount = i;
+ mActivity.runOnUiThread(() -> {
+ final ViewTranslationRequest request =
+ view.createTranslationRequest(supportedFormats);
+ // TODO(b/177960696): handle null case, the developers may want to handle the
+ // translation, call dispatchRequestTranslation() instead.
+ if (request != null
+ && request.getKeys().size() > 0) {
+ requests.add(request);
+ }
+ if (currentCount == (foundViews.size() - 1)) {
+ Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
+ + " requests.");
+ mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+ UiTranslationController::sendTranslationRequest,
+ UiTranslationController.this, translator, requests));
+ }
+ });
+ }
+ }
+
+ private int[] getSupportedFormatsLocked() {
+ // We only support text now
+ return new int[] {TranslationSpec.DATA_FORMAT_TEXT};
+ }
+
private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds,
ArrayList<View> foundViews) {
final ArrayList<ViewRootImpl> roots =
@@ -356,20 +400,21 @@
}
}
- private void runForEachView(Consumer<View> action) {
+ private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
synchronized (mLock) {
final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
mActivity.runOnUiThread(() -> {
final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
final View view = views.valueAt(i).get();
- if (view == null) {
+ if (view == null || view.getViewTranslationCallback() == null) {
if (DEBUG) {
- Log.d(TAG, "View was gone for autofillid = " + views.keyAt(i));
+ Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid "
+ + "= " + views.keyAt(i));
}
continue;
}
- action.accept(view);
+ action.accept(view, view.getViewTranslationCallback());
}
});
}
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
new file mode 100644
index 0000000..c895984
--- /dev/null
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 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.translation;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.view.View;
+
+/**
+ * Callback for handling the translated information show or hide in the {@link View}. See {@link
+ * View#onTranslationResponse} for how to get the translated information.
+ */
+@UiThread
+public interface ViewTranslationCallback {
+ /**
+ * Called when the translated text is ready to show or if the user has requested to reshow the
+ * translated content after hiding it. This may be called before the translation results are
+ * returned through the {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles showing the translation.
+ */
+ boolean onShowTranslation(@NonNull View view);
+ /**
+ * Called when the user wants to show the original text instead of the translated text. This
+ * may be called before the translation results are returned through the
+ * {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles hiding the translation.
+ */
+ boolean onHideTranslation(@NonNull View view);
+ /**
+ * Called when the user finish the Ui translation and no longer to show the translated text.
+ *
+ * @return {@code true} if the View handles clearing the translation.
+ */
+ boolean onClearTranslation(@NonNull View view);
+}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a63305e..287c182 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -20,12 +20,10 @@
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
-import android.text.TextUtils;
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.util.Log;
-import android.util.LruCache;
import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
@@ -98,10 +96,6 @@
private Runnable mSpellRunnable;
- private static final int SUGGESTION_SPAN_CACHE_SIZE = 10;
- private final LruCache<Long, SuggestionSpan> mSuggestionSpanCache =
- new LruCache<Long, SuggestionSpan>(SUGGESTION_SPAN_CACHE_SIZE);
-
public SpellChecker(TextView textView) {
mTextView = textView;
@@ -144,7 +138,6 @@
// Remove existing misspelled SuggestionSpans
mTextView.removeMisspelledSpans((Editable) mTextView.getText());
- mSuggestionSpanCache.evictAll();
}
private void setLocale(Locale locale) {
@@ -410,16 +403,7 @@
}
if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
&& end > start) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Remove existing misspelled span. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- mSuggestionSpanCache.remove(key);
- }
+ removeErrorSuggestionSpan(editable, start, end, RemoveReason.OBSOLETE);
}
}
return spellCheckSpan;
@@ -428,6 +412,35 @@
return null;
}
+ private enum RemoveReason {
+ /**
+ * Indicates the previous SuggestionSpan is replaced by a new SuggestionSpan.
+ */
+ REPLACE,
+ /**
+ * Indicates the previous SuggestionSpan is removed because corresponding text is
+ * considered as valid words now.
+ */
+ OBSOLETE,
+ }
+
+ private static void removeErrorSuggestionSpan(
+ Editable editable, int start, int end, RemoveReason reason) {
+ SuggestionSpan[] spans = editable.getSpans(start, end, SuggestionSpan.class);
+ for (SuggestionSpan span : spans) {
+ if (editable.getSpanStart(span) == start
+ && editable.getSpanEnd(span) == end
+ && (span.getFlags() & (SuggestionSpan.FLAG_MISSPELLED
+ | SuggestionSpan.FLAG_GRAMMAR_ERROR)) != 0) {
+ if (DBG) {
+ Log.i(TAG, "Remove existing misspelled/grammar error span on "
+ + editable.subSequence(start, end) + ", reason: " + reason);
+ }
+ editable.removeSpan(span);
+ }
+ }
+ }
+
@Override
public void onGetSuggestions(SuggestionsInfo[] results) {
final Editable editable = (Editable) mTextView.getText();
@@ -543,16 +556,7 @@
}
SuggestionSpan suggestionSpan =
new SuggestionSpan(mTextView.getContext(), suggestions, flags);
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Cached span on the same position is cleard. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- }
- mSuggestionSpanCache.put(key, suggestionSpan);
+ removeErrorSuggestionSpan(editable, start, end, RemoveReason.REPLACE);
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 940a3c9..6733c0d3 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -195,7 +195,9 @@
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.view.translation.TranslationRequestValue;
+import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationController;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.RemoteViews.RemoteView;
@@ -203,6 +205,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
@@ -737,7 +740,7 @@
private MovementMethod mMovement;
private TransformationMethod mTransformation;
- private TranslationTransformationMethod mTranslationTransformation;
+ private TextViewTranslationCallback mDefaultTranslationCallback;
@UnsupportedAppUsage
private boolean mAllowTransformationLengthChange;
@UnsupportedAppUsage
@@ -13857,136 +13860,104 @@
}
/**
- * Provides a {@link ViewTranslationRequest} that represents the content to be translated via
- * translation service.
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * <p>NOTE: When overriding the method, it should not translate the password. We also suggest
- * that not translating the text is selectable or editable. We use the transformation method to
- * implement showing the translated text. The TextView does not support the transformation
- * method text length change. If the text is selectable or editable, it will crash while
- * selecting the text. To support it, it needs broader changes to text APIs, we only allow to
- * translate non selectable and editable text now.
+ * <p>NOTE: When overriding the method, it should not translate the password. If the subclass
+ * uses {@link TransformationMethod} to display the translated result, it's also not recommend
+ * to translate text is selectable or editable.
*
- * @hide
+ * @param supportedFormats the supported translation format. The value could be {@link
+ * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated.
*/
@Nullable
@Override
- public ViewTranslationRequest onCreateTranslationRequest() {
- if (mText == null || mText.length() == 0) {
+ public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) {
+ if (supportedFormats == null || supportedFormats.length == 0) {
// TODO(b/182433547): remove before S release
if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ Log.w(LOG_TAG, "Do not provide the support translation formats.");
}
return null;
}
- // Not translate password, editable text and not important for translation
- // TODO(b/177214256): support selectable text translation. It needs to broader changes to
- // text selection apis, not support in S.
- boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
- if (isTextEditable() || isPassword || isTextSelectable()) {
- // TODO(b/182433547): remove before S release
- if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable()
- + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable());
+ ViewTranslationRequest.Builder requestBuilder =
+ new ViewTranslationRequest.Builder(getAutofillId());
+ // Support Text translation
+ if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
+ if (mText == null || mText.length() == 0) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ }
+ return null;
}
- return null;
+ boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
+ // TODO(b/177214256): support selectable text translation.
+ // We use the TransformationMethod to implement showing the translated text. The
+ // TextView does not support the text length change for TransformationMethod. If the
+ // text is selectable or editable, it will crash while selecting the text. To support
+ // it, it needs broader changes to text APIs, we only allow to translate non selectable
+ // and editable text in S.
+ if (isTextEditable() || isPassword || isTextSelectable()) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request. editable = "
+ + isTextEditable() + ", isPassword = " + isPassword + ", selectable = "
+ + isTextSelectable());
+ }
+ return null;
+ }
+ // TODO(b/176488462): apply the view's important for translation
+ requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ TranslationRequestValue.forText(mText));
}
- // TODO(b/176488462): apply the view's important for translation property
- // TODO(b/174283799): remove the spans from the mText and save the spans information
- // TODO: use fixed ids for request texts.
- ViewTranslationRequest request =
- new ViewTranslationRequest.Builder(getAutofillId())
- .setValue(ViewTranslationRequest.ID_TEXT,
- TranslationRequestValue.forText(mText))
- .build();
- return request;
+ return requestBuilder.build();
}
/**
- * Provides the implementation that pauses the ongoing Ui translation, it will show the original
- * text instead of the translated text and restore the original transformation method.
+ * Returns a {@link ViewTranslationCallback} that is used to display the translated information.
+ * The default implementation will use a {@link TransformationMethod} that allow to replace the
+ * current {@link TransformationMethod} to transform the original text to the translated text
+ * display.
*
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
+ * @return a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information or {@code null} if this View doesn't support translation.
*/
+ @Nullable
@Override
- public void onPauseUiTranslation() {
- // Restore to original text content.
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text.");
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return getDefaultViewTranslationCallback();
+ }
+
+ private ViewTranslationCallback getDefaultViewTranslationCallback() {
+ if (mDefaultTranslationCallback == null) {
+ mDefaultTranslationCallback = new TextViewTranslationCallback();
}
+ return mDefaultTranslationCallback;
}
/**
- * Provides the implementation that restoes the paused Ui translation, it will show the
- * translated text again if the text had been translated. This method will replace the current
- * tansformation method with {@link TranslationTransformationMethod}.
*
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService. The default implementation will replace the current
+ * {@link TransformationMethod} to transform the original text to the translated text display.
*
- * @hide
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
@Override
- public void onRestoreUiTranslation() {
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation);
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text.");
- }
- }
-
- /**
- * Provides the implementation that finishes the current Ui translation and it's no longer to
- * show the translated text. This method restores the original transformation method and resets
- * the saved {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onFinishUiTranslation() {
- // Restore to original text content and clear TranslationTransformation
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- mTranslationTransformation = null;
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text.");
- }
- }
-
- /**
- * Default {@link TextView} implementation after the translation request is done by the
- * translation service, it's ok to show the translated text. This method will save the original
- * transformation method and replace the current transformation method with
- * {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
- // Show the translated text.
- TransformationMethod originalTranslationMethod = mTranslationTransformation != null
- ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation;
- mTranslationTransformation =
+ public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
+ // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default
+ // implementation if the view has overridden getViewTranslationCallback.
+ TextViewTranslationCallback callback =
+ (TextViewTranslationCallback) getDefaultViewTranslationCallback();
+ TranslationTransformationMethod oldTranslationMethod =
+ callback.getTranslationTransformation();
+ TransformationMethod originalTranslationMethod = oldTranslationMethod != null
+ ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation;
+ TranslationTransformationMethod newTranslationMethod =
new TranslationTransformationMethod(response, originalTranslationMethod);
// TODO(b/178353965): well-handle setTransformationMethod.
- setTransformationMethod(mTranslationTransformation);
+ callback.setTranslationTransformation(newTranslationMethod);
}
}
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
new file mode 100644
index 0000000..296d93c
--- /dev/null
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 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.widget;
+
+import android.annotation.NonNull;
+import android.text.method.TranslationTransformationMethod;
+import android.util.Log;
+import android.view.View;
+import android.view.translation.UiTranslationManager;
+import android.view.translation.ViewTranslationCallback;
+import android.view.translation.ViewTranslationResponse;
+
+/**
+ * Default implementation for {@link ViewTranslationCallback} for {@link TextView} components.
+ * This class handles how to display the translated information for {@link TextView}.
+ *
+ * @hide
+ */
+public class TextViewTranslationCallback implements ViewTranslationCallback {
+
+ private static final String TAG = "TextViewTranslationCallback";
+
+ private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+
+ private TranslationTransformationMethod mTranslationTransformation;
+
+ /**
+ * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the
+ * view that provides the translatable information by {@link View#createTranslationRequest} and
+ * sent by the platform.
+ */
+ void setTranslationTransformation(TranslationTransformationMethod method) {
+ if (method == null) {
+ if (DEBUG) {
+ Log.w(TAG, "setTranslationTransformation: should not set null "
+ + "TranslationTransformationMethod");
+ }
+ return;
+ }
+ mTranslationTransformation = method;
+ }
+
+ TranslationTransformationMethod getTranslationTransformation() {
+ return mTranslationTransformation;
+ }
+
+ private void clearTranslationTransformation() {
+ if (DEBUG) {
+ Log.v(TAG, "clearTranslationTransformation: " + mTranslationTransformation);
+ }
+ mTranslationTransformation = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onShowTranslation(@NonNull View view) {
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(mTranslationTransformation);
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onShowTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onHideTranslation(@NonNull View view) {
+ // Restore to original text content.
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onHideTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onClearTranslation(@NonNull View view) {
+ // Restore to original text content and clear TranslationTransformation
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ clearTranslationTransformation();
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onClearTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 922f96e..3b6a877 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -221,32 +221,19 @@
@Override
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- if (mIsSendAction) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_share,
- /* subtitleRes */ 0);
- } else {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_resolve,
- /* subtitleRes */ 0);
- }
+ showEmptyState(listAdapter,
+ R.drawable.ic_no_apps,
+ R.string.resolver_no_personal_apps_available,
+ /* subtitleRes */ 0);
+
}
@Override
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- if (mIsSendAction) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_share,
- /* subtitleRes */ 0);
- } else {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_resolve,
- /* subtitleRes */ 0);
- }
+ showEmptyState(listAdapter,
+ R.drawable.ic_no_apps,
+ R.string.resolver_no_work_apps_available,
+ /* subtitleRes */ 0);
}
void setEmptyStateBottomOffset(int bottomOffset) {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index a2f014c..622f166 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -221,7 +221,7 @@
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_resolve,
+ R.string.resolver_no_personal_apps_available,
/* subtitleRes */ 0);
}
@@ -229,7 +229,7 @@
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_resolve,
+ R.string.resolver_no_work_apps_available,
/* subtitleRes */ 0);
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index fae5862..6776c27 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -20,6 +20,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -28,6 +29,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
+import android.view.Display;
import java.util.LinkedList;
import java.util.Queue;
@@ -52,6 +54,7 @@
// This value is approximately 1/3 of the smallest possible brightness value.
public static final float EPSILON = 0.001f;
+ private DisplayManager mDisplayManager;
private final Context mContext;
private final Queue<Object> mWriteHistory = new LinkedList<>();
@@ -87,11 +90,15 @@
* value, if float is invalid. If both are invalid, use default float value from config.
*/
public void startSynchronizing() {
+ if (mDisplayManager == null) {
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
final BrightnessSyncObserver brightnessSyncObserver;
brightnessSyncObserver = new BrightnessSyncObserver(mHandler);
brightnessSyncObserver.startObserving();
- final float currentFloatBrightness = getScreenBrightnessFloat(mContext);
+ final float currentFloatBrightness = getScreenBrightnessFloat();
final int currentIntBrightness = getScreenBrightnessInt(mContext);
if (!Float.isNaN(currentFloatBrightness)) {
@@ -101,9 +108,7 @@
} else {
final float defaultBrightness = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, defaultBrightness,
- UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness);
}
}
@@ -135,7 +140,7 @@
/**
* Translates specified value from the float brightness system to the int brightness system,
* given the min/max of each range. Accounts for special values such as OFF and invalid values.
- * Value returned as a float privimite (to preserve precision), but is a value within the
+ * Value returned as a float primitive (to preserve precision), but is a value within the
* int-system range.
*/
public static float brightnessFloatToIntRange(float brightnessFloat) {
@@ -152,10 +157,8 @@
}
}
- private static float getScreenBrightnessFloat(Context context) {
- return Settings.System.getFloatForUser(context.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- UserHandle.USER_CURRENT);
+ private float getScreenBrightnessFloat() {
+ return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
}
private static int getScreenBrightnessInt(Context context) {
@@ -184,9 +187,7 @@
float newBrightnessFloat = brightnessIntToFloat(value);
mWriteHistory.offer(newBrightnessFloat);
mPreferredSettingValue = newBrightnessFloat;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, newBrightnessFloat,
- UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat);
}
}
@@ -255,7 +256,7 @@
mHandler.removeMessages(MSG_UPDATE_FLOAT);
mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
} else if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
- float currentFloat = getScreenBrightnessFloat(mContext);
+ float currentFloat = getScreenBrightnessFloat();
int toSend = Float.floatToIntBits(currentFloat);
mHandler.removeMessages(MSG_UPDATE_INT);
mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f1fa5db..20d257e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -221,7 +221,6 @@
static_libs: [
"libasync_safe",
- "libconnectivityframeworkutils",
"libbinderthreadstateutils",
"libdmabufinfo",
"libgif",
@@ -238,7 +237,6 @@
"android.hardware.camera.device@3.2",
"media_permission-aidl-cpp",
"libandroidicu",
- "libandroid_net",
"libbpf_android",
"libnetdbpf",
"libnetdutils",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 94ac183..0c3f265 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -156,7 +156,6 @@
extern int register_android_service_DataLoaderService(JNIEnv* env);
extern int register_android_os_incremental_IncrementalManager(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
-extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
@@ -1548,7 +1547,6 @@
REG_JNI(register_android_os_Trace),
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
- REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_android_os_incremental_IncrementalManager),
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index f0badbe..530cb44 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -123,6 +123,8 @@
optional SettingProto gesture_silence_alerts_enabled = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gesture_wake_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d9fa78f..90755b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4003,6 +4003,15 @@
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
android:protectionLevel="signature" />
+ <!-- Allows an app to schedule a prioritized alarm that can be used to perform
+ background work even when the device is in doze.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM"
+ android:protectionLevel="signature|privileged"/>
+
<!-- Allows an app to use exact alarm scheduling APIs to perform timing
sensitive background work.
-->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e79fbb5..dd64750 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5662,15 +5662,11 @@
<!-- Button text. This button turns on a user's work profile so they can access their work apps and data. [CHAR LIMIT=NONE] -->
<string name="resolver_switch_on_work">Tap to turn on</string>
- <!-- Error message. This text lets the user know that their current work apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_work_apps_available_share">No work apps can support this content</string>
- <!-- Error message. This text lets the user know that their current work apps can't open this specific content. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_work_apps_available_resolve">No work apps can open this content</string>
+ <!-- Error message. This text lets the user know that their current work apps don't support the specific content. [CHAR LIMIT=NONE] -->
+ <string name="resolver_no_work_apps_available">No work apps</string>
- <!-- Error message. This text lets the user know that their current personal apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_personal_apps_available_share">No personal apps can support this content</string>
- <!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string>
+ <!-- Error message. This text lets the user know that their current personal apps don't support the specific content. [CHAR LIMIT=NONE] -->
+ <string name="resolver_no_personal_apps_available">No personal apps</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b5f4764..b924ecd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4102,10 +4102,8 @@
<java-symbol type="string" name="resolver_cant_access_work_apps_explanation" />
<java-symbol type="string" name="resolver_cant_access_personal_apps_explanation" />
<java-symbol type="string" name="resolver_turn_on_work_apps" />
- <java-symbol type="string" name="resolver_no_work_apps_available_share" />
- <java-symbol type="string" name="resolver_no_work_apps_available_resolve" />
- <java-symbol type="string" name="resolver_no_personal_apps_available_share" />
- <java-symbol type="string" name="resolver_no_personal_apps_available_resolve" />
+ <java-symbol type="string" name="resolver_no_work_apps_available" />
+ <java-symbol type="string" name="resolver_no_personal_apps_available" />
<java-symbol type="string" name="resolver_switch_on_work" />
<java-symbol type="drawable" name="ic_work_apps_off" />
<java-symbol type="drawable" name="ic_sharing_disabled" />
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
index 67614bb..e6a25d0 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
@@ -236,12 +236,13 @@
@Test
public void testMergeEvent_typeViewTextChanged() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED)
- .setText("test");
+ .setText("test", false);
final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED)
- .setText("empty");
+ .setText("empty", true);
event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo(event2.getText());
+ assertThat(event.getTextHasComposingSpan()).isEqualTo(event2.getTextHasComposingSpan());
}
@Test
@@ -282,16 +283,18 @@
@Test
public void testMergeEvent_differentEventTypes() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
- .setText("test").setAutofillId(new AutofillId(1));
+ .setText("test", false).setAutofillId(new AutofillId(1));
final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED)
- .setText("empty").setAutofillId(new AutofillId(2));
+ .setText("empty", true).setAutofillId(new AutofillId(2));
event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo("test");
+ assertThat(event.getTextHasComposingSpan()).isFalse();
assertThat(event.getId()).isEqualTo(new AutofillId(1));
event2.mergeEvent(event);
assertThat(event2.getText()).isEqualTo("empty");
+ assertThat(event2.getTextHasComposingSpan()).isTrue();
assertThat(event2.getId()).isEqualTo(new AutofillId(2));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 80d47a9..1633d28 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1660,7 +1660,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_share))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -1711,7 +1711,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_share))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -2146,7 +2146,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 68287ca..97652a9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -700,7 +700,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -751,7 +751,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9aeef07..343d04f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3221,6 +3221,23 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int NUM_SOUND_EFFECTS = 16;
+ /** @hide */
+ @IntDef(prefix = { "FX_" }, value = {
+ FX_KEY_CLICK,
+ FX_FOCUS_NAVIGATION_UP,
+ FX_FOCUS_NAVIGATION_DOWN,
+ FX_FOCUS_NAVIGATION_LEFT,
+ FX_FOCUS_NAVIGATION_RIGHT,
+ FX_KEYPRESS_STANDARD,
+ FX_KEYPRESS_SPACEBAR,
+ FX_KEYPRESS_DELETE,
+ FX_KEYPRESS_RETURN,
+ FX_KEYPRESS_INVALID,
+ FX_BACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemSoundEffect {}
+
/**
* @hide Number of FX_FOCUS_NAVIGATION_REPEAT_* sound effects
*/
@@ -3296,22 +3313,11 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* NOTE: This version uses the UI settings to determine
* whether sounds are heard or not.
*/
- public void playSoundEffect(int effectType) {
+ public void playSoundEffect(@SystemSoundEffect int effectType) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
@@ -3330,24 +3336,13 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* @param userId The current user to pull sound settings from
* NOTE: This version uses the UI settings to determine
* whether sounds are heard or not.
* @hide
*/
- public void playSoundEffect(int effectType, int userId) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
@@ -3366,25 +3361,14 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* @param volume Sound effect volume.
* The volume value is a raw scalar so UI controls should be scaled logarithmically.
* If a volume of -1 is specified, the AudioManager.STREAM_MUSIC stream volume minus 3dB will be used.
* NOTE: This version is for applications that have their own
* settings panel for enabling and controlling volume.
*/
- public void playSoundEffect(int effectType, float volume) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index fdfb108..74cecdd 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -35,7 +35,6 @@
":framework-javastream-protos",
],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
jarjar_rules: "jarjar-rules-proto.txt",
@@ -125,7 +124,6 @@
"//packages/modules/Connectivity/Tethering/tests/unit",
],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 2bf807c4..0cc8c23 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -20,6 +20,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
+ method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
@@ -43,6 +44,52 @@
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
}
+ public class ConnectivitySettingsManager {
+ method public static void clearGlobalProxy(@NonNull android.content.Context);
+ method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
+ method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
+ method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method @Nullable public static String getMobileDataPreferredApps(@NonNull android.content.Context);
+ method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context);
+ method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context);
+ method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
+ method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
+ method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
+ method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
+ method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>);
+ method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
+ method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+ method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method public static void setMobileDataPreferredApps(@NonNull android.content.Context, @Nullable String);
+ method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int);
+ method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String);
+ method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int);
+ method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String);
+ method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
+ method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+ field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+ field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2
+ field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0
+ field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1
+ }
+
public final class NetworkAgentConfig implements android.os.Parcelable {
method @Nullable public String getSubscriberId();
method public boolean isBypassableVpn();
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index f3f5533..593698e 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -218,6 +218,8 @@
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
method public void onAutomaticReconnectDisabled();
method public void onBandwidthUpdateRequested();
+ method public void onNetworkCreated();
+ method public void onNetworkDisconnected();
method public void onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index a73d76e..a9f8b8d 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -3326,9 +3326,10 @@
* Register or update a network offer with ConnectivityService.
*
* ConnectivityService keeps track of offers made by the various providers and matches
- * them to networking requests made by apps or the system. The provider supplies a score
- * and the capabilities of the network it might be able to bring up ; these act as filters
- * used by ConnectivityService to only send those requests that can be fulfilled by the
+ * them to networking requests made by apps or the system. A callback identifies an offer
+ * uniquely, and later calls with the same callback update the offer. The provider supplies a
+ * score and the capabilities of the network it might be able to bring up ; these act as
+ * filters used by ConnectivityService to only send those requests that can be fulfilled by the
* provider.
*
* The provider is under no obligation to be able to bring up the network it offers at any
@@ -5426,4 +5427,23 @@
if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
return mode;
}
+
+ /**
+ * Set private DNS mode to settings.
+ *
+ * @param context The {@link Context} to set the private DNS mode.
+ * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setPrivateDnsMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String mode) {
+ if (!(mode == PRIVATE_DNS_MODE_OFF
+ || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
+ || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode);
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
index bbd8393..9a00055 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
@@ -16,16 +16,38 @@
package android.net;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.ConnectivityManager.MultipathPreference;
+import android.net.ConnectivityManager.PrivateDnsMode;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Range;
+
+import com.android.net.module.util.ProxyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.util.List;
/**
* A manager class for connectivity module settings.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public class ConnectivitySettingsManager {
private ConnectivitySettingsManager() {}
@@ -45,12 +67,16 @@
* Network activity refers to transmitting or receiving data on the network interfaces.
*
* Tracking is disabled if set to zero or negative value.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
/**
* Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
* but for Wifi network.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
@@ -58,12 +84,16 @@
/**
* Sample validity in seconds to configure for the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
"dns_resolver_sample_validity_seconds";
/**
* Success threshold in percent for use with the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
"dns_resolver_success_threshold_percent";
@@ -71,24 +101,35 @@
/**
* Minimum number of samples needed for statistics to be considered meaningful in the
* system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
/**
* Maximum number taken into account for statistics purposes in the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
+ private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
+ private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
+
/** Network switch notification settings */
/**
* The maximum number of notifications shown in 24 hours when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
"network_switch_notification_daily_limit";
/**
* The minimum time in milliseconds between notifications when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
"network_switch_notification_rate_limit_millis";
@@ -98,14 +139,18 @@
/**
* The URL used for HTTP captive portal detection upon a new connection.
* A 204 response code from the server is used for validation.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
/**
* What to do when connecting a network that presents a captive portal.
- * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+ * Must be one of the CAPTIVE_PORTAL_MODE_* constants below.
*
* The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
@@ -139,11 +184,15 @@
/**
* Host name for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
/**
* Integer host port for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
@@ -153,12 +202,16 @@
* Domains should be listed in a comma- separated list. Example of
* acceptable formats: ".domain1.com,my.domain2.com" Use
* ConnectivityManager to set/get.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
"global_http_proxy_exclusion_list";
/**
* The location PAC File for the proxy.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
@@ -171,11 +224,15 @@
* a specific provider. It may be used to store the provider name even when the
* mode changes so that temporarily disabling and re-enabling the specific
* provider mode does not necessitate retyping the provider hostname.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_MODE = "private_dns_mode";
/**
* The specific Private DNS provider name.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
@@ -185,6 +242,8 @@
* all of which require explicit user action to enable/configure. See also b/79719289.
*
* Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
@@ -194,6 +253,8 @@
* The number of milliseconds to hold on to a PendingIntent based request. This delay gives
* the receivers of the PendingIntent an opportunity to make a new network request before
* the Network satisfying the request is potentially removed.
+ *
+ * @hide
*/
public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
"connectivity_release_pending_intent_delay_ms";
@@ -205,6 +266,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
@@ -217,6 +280,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
@@ -228,14 +293,637 @@
* 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
* null: Ask the user whether to switch away from bad wifi.
* 1: Avoid bad wifi.
+ *
+ * @hide
*/
public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
/**
+ * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0;
+
+ /**
+ * Ask the user whether to switch away from bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1;
+
+ /**
+ * Avoid bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ NETWORK_AVOID_BAD_WIFI_IGNORE,
+ NETWORK_AVOID_BAD_WIFI_PROMPT,
+ NETWORK_AVOID_BAD_WIFI_AVOID,
+ })
+ public @interface NetworkAvoidBadWifi {}
+
+ /**
* User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
* overridden by the system based on device or application state. If null, the value
* specified by config_networkMeteredMultipathPreference is used.
+ *
+ * @hide
*/
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+
+ /**
+ * A list of apps that should go on cellular networks in preference even when higher-priority
+ * networks are connected.
+ *
+ * @hide
+ */
+ public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps";
+
+ /**
+ * Get mobile data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track mobile data activity.
+ */
+ @NonNull
+ public static Duration getMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set mobile data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The mobile data activity timeout.
+ */
+ public static void setMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get wifi data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track wifi data activity.
+ */
+ @NonNull
+ public static Duration getWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set wifi data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The wifi data activity timeout.
+ */
+ public static void setWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get dns resolver sample validity duration from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The {@link Duration} of sample validity duration to configure for the system DNS
+ * resolver.
+ */
+ @NonNull
+ public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds());
+ return Duration.ofSeconds(duration);
+ }
+
+ /**
+ * Set dns resolver sample validity duration to {@link Settings}. The duration must be a
+ * positive number of seconds.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The sample validity duration.
+ */
+ public static void setDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.getSeconds();
+ if (time <= 0) {
+ throw new IllegalArgumentException("Invalid duration");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time);
+ }
+
+ /**
+ * Get dns resolver success threshold percent from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The success threshold in percent for use with the system DNS resolver.
+ */
+ public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def);
+ }
+
+ /**
+ * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must
+ * be 0~100.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param percent The success threshold percent.
+ */
+ public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context,
+ @IntRange(from = 0, to = 100) int percent) {
+ if (percent < 0 || percent > 100) {
+ throw new IllegalArgumentException("Percent must be 0~100");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent);
+ }
+
+ /**
+ * Get dns resolver samples range from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link Range<Integer>} of samples needed for statistics to be considered
+ * meaningful in the system DNS resolver.
+ */
+ @NonNull
+ public static Range<Integer> getDnsResolverSampleRanges(@NonNull Context context) {
+ final int minSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
+ final int maxSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
+ return new Range<>(minSamples, maxSamples);
+ }
+
+ /**
+ * Set dns resolver samples range to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param range The samples range. The minimum number should be more than 0 and the maximum
+ * number should be less that 64.
+ */
+ public static void setDnsResolverSampleRanges(@NonNull Context context,
+ @NonNull Range<Integer> range) {
+ if (range.getLower() < 0 || range.getUpper() > 64) {
+ throw new IllegalArgumentException("Argument must be 0~64");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower());
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper());
+ }
+
+ /**
+ * Get maximum count (from {@link Settings}) of switching network notifications shown in 24
+ * hours.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The maximum count of notifications shown in 24 hours when switching networks.
+ */
+ public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def);
+ }
+
+ /**
+ * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours.
+ * The count must be at least 0.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param count The maximum count of switching network notifications shown in 24 hours.
+ */
+ public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ @IntRange(from = 0) int count) {
+ if (count < 0) {
+ throw new IllegalArgumentException("Count must be 0~10.");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
+ }
+
+ /**
+ * Get minimum duration (from {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default time if no setting value.
+ * @return The minimum duration between notifications when switching networks.
+ */
+ @NonNull
+ public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set minimum duration (to {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The minimum duration between notifications when switching networks.
+ */
+ public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Global.putInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time);
+ }
+
+ /**
+ * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The URL used for HTTP captive portal detection upon a new connection.
+ */
+ @Nullable
+ public static String getCaptivePortalHttpUrl(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL);
+ }
+
+ /**
+ * Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection.
+ * This URL should respond with a 204 response to a GET request to indicate no captive portal is
+ * present. And this URL must be HTTP as redirect responses are used to find captive portal
+ * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal
+ * detection failed and lost the connection.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param url The URL used for HTTP captive portal detection upon a new connection.
+ */
+ public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) {
+ Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url);
+ }
+
+ /**
+ * Get mode (from {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default mode if no setting value.
+ * @return The mode when connecting a network that presents a captive portal.
+ */
+ @CaptivePortalMode
+ public static int getCaptivePortalMode(@NonNull Context context,
+ @CaptivePortalMode int def) {
+ return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def);
+ }
+
+ /**
+ * Set mode (to {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The mode when connecting a network that presents a captive portal.
+ */
+ public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) {
+ if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE
+ || mode == CAPTIVE_PORTAL_MODE_PROMPT
+ || mode == CAPTIVE_PORTAL_MODE_AVOID)) {
+ throw new IllegalArgumentException("Invalid captive portal mode");
+ }
+ Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode);
+ }
+
+ /**
+ * Get the global HTTP proxy applied to the device, or null if none.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link ProxyInfo} which build from global http proxy settings.
+ */
+ @Nullable
+ public static ProxyInfo getGlobalProxy(@NonNull Context context) {
+ final String host = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST);
+ final int port = Settings.Global.getInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */);
+ final String exclusionList = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ final String pacFileUrl = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC);
+
+ if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) {
+ return null; // No global proxy.
+ }
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ return ProxyInfo.buildDirectProxy(
+ host, port, ProxyUtils.exclusionStringAsList(exclusionList));
+ } else {
+ return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
+ }
+ }
+
+ /**
+ * Set global http proxy settings from given {@link ProxyInfo}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
+ * {@link ProxyInfo#buildPacProxy(Uri)} or
+ * {@link ProxyInfo#buildDirectProxy(String, int, List)}
+ */
+ public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
+ final String host = proxyInfo.getHost();
+ final int port = proxyInfo.getPort();
+ final String exclusionList = proxyInfo.getExclusionListAsString();
+ final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ } else {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ }
+ }
+
+ /**
+ * Clear all global http proxy settings.
+ *
+ * @param context The {@link Context} to set the setting.
+ */
+ public static void clearGlobalProxy(@NonNull Context context) {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ }
+
+ /**
+ * Get specific private dns provider name from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The specific private dns provider name, or null if no setting value.
+ */
+ @Nullable
+ public static String getPrivateDnsHostname(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+ }
+
+ /**
+ * Set specific private dns provider name to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param specifier The specific private dns provider name.
+ */
+ public static void setPrivateDnsHostname(@NonNull Context context,
+ @Nullable String specifier) {
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+ }
+
+ /**
+ * Get default private dns mode from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The default private dns mode.
+ */
+ @PrivateDnsMode
+ @NonNull
+ public static String getPrivateDnsDefaultMode(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE);
+ }
+
+ /**
+ * Set default private dns mode to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_*
+ * constants.
+ */
+ public static void setPrivateDnsDefaultMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String mode) {
+ if (!(mode == PRIVATE_DNS_MODE_OFF
+ || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
+ || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode);
+ }
+
+ /**
+ * Get duration (from {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The duration to keep a PendingIntent-based request.
+ */
+ @NonNull
+ public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Secure.getInt(context.getContentResolver(),
+ CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set duration (to {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The duration to keep a PendingIntent-based request.
+ */
+ public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Secure.putInt(
+ context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time);
+ }
+
+ /**
+ * Read from {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0));
+ }
+
+ /**
+ * Read from {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the wifi data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the wifi data connection should remain active even when higher
+ * priority networks are active
+ */
+ public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0));
+ }
+
+ /**
+ * Get avoid bad wifi setting from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The setting whether to automatically switch away from wifi networks that lose
+ * internet access.
+ */
+ @NetworkAvoidBadWifi
+ public static int getNetworkAvoidBadWifi(@NonNull Context context) {
+ final String setting =
+ Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI);
+ if ("0".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_IGNORE;
+ } else if ("1".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_AVOID;
+ } else {
+ return NETWORK_AVOID_BAD_WIFI_PROMPT;
+ }
+ }
+
+ /**
+ * Set avoid bad wifi setting to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param value Whether to automatically switch away from wifi networks that lose internet
+ * access.
+ */
+ public static void setNetworkAvoidBadWifi(@NonNull Context context,
+ @NetworkAvoidBadWifi int value) {
+ final String setting;
+ if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) {
+ setting = "0";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) {
+ setting = "1";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) {
+ setting = null;
+ } else {
+ throw new IllegalArgumentException("Invalid avoid bad wifi setting");
+ }
+ Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting);
+ }
+
+ /**
+ * Get network metered multipath preference from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified
+ * by config_networkMeteredMultipathPreference is used.
+ */
+ @Nullable
+ public static String getNetworkMeteredMultipathPreference(@NonNull Context context) {
+ return Settings.Global.getString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE);
+ }
+
+ /**
+ * Set network metered multipath preference to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param preference The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value
+ * specified by config_networkMeteredMultipathPreference is used.
+ */
+ public static void setNetworkMeteredMultipathPreference(@NonNull Context context,
+ @NonNull @MultipathPreference String preference) {
+ if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
+ }
+
+ /**
+ * Get the list of apps(from {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected or null if no setting value.
+ */
+ @Nullable
+ public static String getMobileDataPreferredApps(@NonNull Context context) {
+ return Settings.Secure.getString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS);
+ }
+
+ /**
+ * Set the list of apps(to {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param list A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected.
+ */
+ public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) {
+ Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list);
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
index 1f66e18..f9d3994 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
@@ -46,4 +46,6 @@
void onRemoveKeepalivePacketFilter(int slot);
void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
void onQosCallbackUnregistered(int qosCallbackId);
+ void onNetworkCreated();
+ void onNetworkDisconnected();
}
diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
index 67d2d405..a6de173 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
@@ -22,15 +22,16 @@
* A callback registered with connectivity by network providers together with
* a NetworkOffer.
*
- * When the offer is needed to satisfy some application or system component,
- * connectivity will call onOfferNeeded on this callback. When this happens,
- * the provider should try and bring up the network.
+ * When the network for this offer is needed to satisfy some application or
+ * system component, connectivity will call onNetworkNeeded on this callback.
+ * When this happens, the provider should try and bring up the network.
*
- * When the offer is no longer needed, for example because the application has
- * withdrawn the request or if the request is being satisfied by a network
- * that this offer will never be able to beat, connectivity calls
- * onOfferUnneeded. When this happens, the provider should stop trying to
- * bring up the network, or tear it down if it has already been brought up.
+ * When the network for this offer is no longer needed, for example because
+ * the application has withdrawn the request or if the request is being
+ * satisfied by a network that this offer will never be able to beat,
+ * connectivity calls onNetworkUnneeded. When this happens, the provider
+ * should stop trying to bring up the network, or tear it down if it has
+ * already been brought up.
*
* When NetworkProvider#offerNetwork is called, the provider can expect to
* immediately receive all requests that can be fulfilled by that offer and
@@ -38,25 +39,25 @@
* request is currently outstanding, because no requests have been made that
* can be satisfied by this offer, or because all such requests are already
* satisfied by a better network.
- * onOfferNeeded can be called at any time after registration and until the
+ * onNetworkNeeded can be called at any time after registration and until the
* offer is withdrawn with NetworkProvider#unofferNetwork is called. This
* typically happens when a new network request is filed by an application,
* or when the network satisfying a request disconnects and this offer now
- * stands a chance to be the best network for it.
+ * stands a chance to supply the best network for it.
*
* @hide
*/
oneway interface INetworkOfferCallback {
/**
- * Informs the registrant that the offer is needed to fulfill this request.
+ * Called when a network for this offer is needed to fulfill this request.
* @param networkRequest the request to satisfy
* @param providerId the ID of the provider currently satisfying
* this request, or NetworkProvider.ID_NONE if none.
*/
- void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+ void onNetworkNeeded(in NetworkRequest networkRequest, int providerId);
/**
- * Informs the registrant that the offer is no longer needed to fulfill this request.
+ * Informs the registrant that the offer is no longer valuable to fulfill this request.
*/
- void onOfferUnneeded(in NetworkRequest networkRequest);
+ void onNetworkUnneeded(in NetworkRequest networkRequest);
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index f7cd4f6..6b55bb7 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -362,6 +362,22 @@
*/
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
+ * network was created and the Network object is now valid.
+ *
+ * @hide
+ */
+ public static final int CMD_NETWORK_CREATED = BASE + 22;
+
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
+ * network was destroyed.
+ *
+ * @hide
+ */
+ public static final int CMD_NETWORK_DISCONNECTED = BASE + 23;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
@@ -561,6 +577,14 @@
msg.arg1 /* QoS callback id */);
break;
}
+ case CMD_NETWORK_CREATED: {
+ onNetworkCreated();
+ break;
+ }
+ case CMD_NETWORK_DISCONNECTED: {
+ onNetworkDisconnected();
+ break;
+ }
}
}
}
@@ -701,6 +725,16 @@
mHandler.sendMessage(mHandler.obtainMessage(
CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null));
}
+
+ @Override
+ public void onNetworkCreated() {
+ mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED));
+ }
+
+ @Override
+ public void onNetworkDisconnected() {
+ mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED));
+ }
}
/**
@@ -1011,6 +1045,17 @@
}
/**
+ * Called when ConnectivityService has successfully created this NetworkAgent's native network.
+ */
+ public void onNetworkCreated() {}
+
+
+ /**
+ * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
+ */
+ public void onNetworkDisconnected() {}
+
+ /**
* Requests that the network hardware send the specified packet at the specified interval.
*
* @param slot the hardware slot on which to start the keepalive.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index d5b5c9b..d859022 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
@@ -170,10 +170,11 @@
/** @hide */
// TODO : make @SystemApi when the impl is complete
public interface NetworkOfferCallback {
- /** Called by the system when this offer is needed to satisfy some networking request. */
- void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
- /** Called by the system when this offer is no longer needed. */
- void onOfferUnneeded(@NonNull NetworkRequest request);
+ /** Called by the system when a network for this offer is needed to satisfy some
+ * networking request. */
+ void onNetworkNeeded(@NonNull NetworkRequest request, int providerId);
+ /** Called by the system when this offer is no longer valuable for this request. */
+ void onNetworkUnneeded(@NonNull NetworkRequest request);
}
private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
@@ -187,14 +188,14 @@
}
@Override
- public void onOfferNeeded(final @NonNull NetworkRequest request,
+ public void onNetworkNeeded(final @NonNull NetworkRequest request,
final int providerId) {
- mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+ mExecutor.execute(() -> callback.onNetworkNeeded(request, providerId));
}
@Override
- public void onOfferUnneeded(final @NonNull NetworkRequest request) {
- mExecutor.execute(() -> callback.onOfferUnneeded(request));
+ public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
+ mExecutor.execute(() -> callback.onNetworkUnneeded(request));
}
}
@@ -213,41 +214,41 @@
}
/**
- * Register or update an offer for network with the passed caps and score.
+ * Register or update an offer for network with the passed capabilities and score.
*
- * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+ * A NetworkProvider's role is to provide networks. This method is how a provider tells the
* connectivity stack what kind of network it may provide. The score and caps arguments act
- * as filters that the connectivity stack uses to tell when the offer is necessary. When an
- * offer might be advantageous over existing networks, the provider will receive a call to
- * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
- * should then try to bring up this network. When an offer is no longer needed, the stack
- * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+ * as filters that the connectivity stack uses to tell when the offer is valuable. When an
+ * offer might be preferred over existing networks, the provider will receive a call to
+ * the associated callback's {@link NetworkOfferCallback#onNetworkNeeded} method. The provider
+ * should then try to bring up this network. When an offer is no longer useful, the stack
+ * will inform the provider by calling {@link NetworkOfferCallback#onNetworkUnneeded}. The
* provider should stop trying to bring up such a network, or disconnect it if it already has
* one.
*
- * The stack determines what offers are needed according to what networks are currently
+ * The stack determines what offers are valuable according to what networks are currently
* available to the system, and what networking requests are made by applications. If an
- * offer looks like it could be a better choice than any existing network for any particular
- * request, that's when the stack decides the offer is needed. If the current networking
- * requests are all satisfied by networks that this offer can't possibly be a better match
- * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
- * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
- * is called.
+ * offer looks like it could connect a better network than any existing network for any
+ * particular request, that's when the stack decides the network is needed. If the current
+ * networking requests are all satisfied by networks that this offer couldn't possibly be a
+ * better match for, that's when the offer is no longer valuable. An offer starts out as
+ * unneeded ; the provider should not try to bring up the network until
+ * {@link NetworkOfferCallback#onNetworkNeeded} is called.
*
* Note that the offers are non-binding to the providers, in particular because providers
* often don't know if they will be able to bring up such a network at any given time. For
- * example, no wireless network may be in range when the offer is needed. This is fine and
- * expected ; the provider should simply continue to try to bring up the network and do so
+ * example, no wireless network may be in range when the offer would be valuable. This is fine
+ * and expected ; the provider should simply continue to try to bring up the network and do so
* if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
* with the best network currently available, or if none, keep the apps informed that no
* network can currently satisfy this request. When/if the provider can bring up the network,
* the connectivity stack will match it against requests, and inform interested apps of the
* availability of this network. This may, in turn, render the offer of some other provider
- * unneeded if all requests it used to satisfy are now better served by this network.
+ * low-value if all requests it used to satisfy are now better served by this network.
*
* A network can become unneeded for a reason like the above : whether the provider managed
* to bring up the offered network after it became needed or not, some other provider may
- * bring up a better network than this one, making this offer unneeded. A network may also
+ * bring up a better network than this one, making this network unneeded. A network may also
* become unneeded if the application making the request withdrew it (for example, after it
* is done transferring data, or if the user canceled an operation).
*
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 16a49bc..2679b62 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -42,6 +42,9 @@
* {@hide}
*/
public class NetworkUtils {
+ static {
+ System.loadLibrary("framework-connectivity-jni");
+ }
private static final String TAG = "NetworkUtils";
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 8d9a562..ae9261c 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -63,7 +63,8 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater.from(context).inflate(R.layout.main_switch_bar, this);
+ LayoutInflater.from(context).inflate(resourceId(context, "layout", "main_switch_bar"),
+ this);
setFocusable(true);
setClickable(true);
@@ -255,4 +256,8 @@
requestLayout();
}
+
+ private int resourceId(Context context, String type, String name) {
+ return context.getResources().getIdentifier(name, type, context.getPackageName());
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 8987968..a5da8b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -31,6 +31,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import com.android.settingslib.R;
@@ -198,4 +199,17 @@
}
return false;
}
+
+ /**
+ * Returns a boolean indicating whether a given package is a default browser.
+ *
+ * @param packageName a given package.
+ * @return true if the given package is default browser.
+ */
+ public static boolean isDefaultBrowser(Context context, String packageName) {
+ final String defaultBrowserPackage =
+ context.getPackageManager().getDefaultBrowserPackageNameAsUser(
+ UserHandle.myUserId());
+ return TextUtils.equals(packageName, defaultBrowserPackage);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index db9b83e..53920f0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -165,6 +165,8 @@
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 9c67e9c..4119dc9f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1881,6 +1881,12 @@
dumpSetting(s, p,
Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED,
+ SecureSettingsProto.Assist.TOUCH_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
+ SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 730f24f..d82151d 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,6 +22,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <com.android.systemui.statusbar.charging.ChargingRippleView
+ android:id="@+id/wireless_charging_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
<!-- Circle animation -->
<ImageView
android:id="@+id/wireless_charging_view"
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index d2bff18..b2bf6da 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -16,9 +16,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="120dp"
- android:minHeight="54dp"
+ android:minHeight="50dp"
android:minResizeWidth="60dp"
- android:minResizeHeight="54dp"
+ android:minResizeHeight="50dp"
android:maxResizeHeight="207dp"
android:updatePeriodMillis="60000"
android:description="@string/people_tile_description"
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index bb1d972..cfef6cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -160,11 +160,18 @@
*
* @param separator Separator between different parts of the text
*/
- private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
- boolean showMissingSim, @Nullable WifiManager wifiManager,
- TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager,
- WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor,
- @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ private CarrierTextManager(
+ Context context,
+ CharSequence separator,
+ boolean showAirplaneMode,
+ boolean showMissingSim,
+ @Nullable WifiManager wifiManager,
+ TelephonyManager telephonyManager,
+ TelephonyListenerManager telephonyListenerManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
@@ -633,12 +640,15 @@
private boolean mShowMissingSim;
@Inject
- public Builder(Context context, @Main Resources resources,
+ public Builder(
+ Context context,
+ @Main Resources resources,
@Nullable WifiManager wifiManager,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
- @Main Executor mainExecutor, @Background Executor bgExecutor,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mSeparator = resources.getString(
@@ -668,8 +678,8 @@
public CarrierTextManager build() {
return new CarrierTextManager(
mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
- mTelephonyManager, mTelephonyListenerManager,
- mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+ mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
+ mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
}
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 561ea40..568bea0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -70,7 +70,7 @@
void onThemeChanged() {
TypedArray array = mContext.obtainStyledAttributes(new int[] {
- android.R.attr.textColor
+ android.R.attr.textColorPrimary
});
ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED));
array.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 2569f7c..f7beaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -26,7 +26,6 @@
import android.util.Log;
import android.util.Slog;
import android.view.Gravity;
-import android.view.View;
import android.view.WindowManager;
/**
@@ -98,8 +97,8 @@
private final Handler mHandler;
private int mGravity;
- private View mView;
- private View mNextView;
+ private WirelessChargingLayout mView;
+ private WirelessChargingLayout mNextView;
private WindowManager mWM;
private Callback mCallback;
@@ -112,7 +111,7 @@
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
final WindowManager.LayoutParams params = mParams;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.format = PixelFormat.TRANSLUCENT;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index e8407f0..ce0b514 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -20,6 +20,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -29,8 +30,10 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.charging.ChargingRippleView;
import java.text.NumberFormat;
@@ -38,7 +41,9 @@
* @hide
*/
public class WirelessChargingLayout extends FrameLayout {
- public final static int UNKNOWN_BATTERY_LEVEL = -1;
+ public static final int UNKNOWN_BATTERY_LEVEL = -1;
+ private static final long RIPPLE_ANIMATION_DURATION = 2000;
+ private ChargingRippleView mRippleView;
public WirelessChargingLayout(Context context) {
super(context);
@@ -120,6 +125,8 @@
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
+ mRippleView = findViewById(R.id.wireless_charging_ripple);
+
if (!showTransmittingBatteryLevel) {
chargingAnimation.start();
animatorSet.start();
@@ -195,4 +202,21 @@
animatorSetTransmitting.start();
animatorSetIcon.start();
}
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mRippleView != null) {
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ mRippleView.setColor(
+ Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
+ mRippleView.setOrigin(new PointF(width / 2, height / 2));
+ mRippleView.setRadius(Math.max(width, height) * 0.5f);
+ mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
+ mRippleView.startRipple();
+ }
+
+ super.onLayout(changed, left, top, right, bottom);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 9d43e0c..c8dfde1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -209,6 +209,8 @@
private @TransitionMode int mNavigationBarMode;
private ContentResolver mContentResolver;
private boolean mAssistantAvailable;
+ private boolean mLongPressHomeEnabled;
+ private boolean mAssistantTouchGestureEnabled;
private int mDisabledFlags1;
private int mDisabledFlags2;
@@ -309,7 +311,7 @@
// Send the assistant availability upon connection
if (isConnected) {
- sendAssistantAvailability(mAssistantAvailable);
+ updateAssistantEntrypoints();
}
}
@@ -404,12 +406,7 @@
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- boolean available = mAssistManagerLazy.get()
- .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
- if (mAssistantAvailable != available) {
- sendAssistantAvailability(available);
- mAssistantAvailable = available;
- }
+ updateAssistantEntrypoints();
}
};
@@ -531,6 +528,13 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED),
+ false, mAssistContentObserver, UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
+ false, mAssistContentObserver, UserHandle.USER_ALL);
+ updateAssistantEntrypoints();
if (savedState != null) {
mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
@@ -823,7 +827,7 @@
|| mNavigationBarView.getHomeButton().getCurrentView() == null) {
return;
}
- if (mHomeButtonLongPressDurationMs.isPresent()) {
+ if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false);
mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
mNavigationBarView.getHomeButton().setOnLongClickListener(null);
@@ -845,6 +849,8 @@
pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
pw.println(" mCurrentRotation=" + mCurrentRotation);
pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
+ pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled);
+ pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled);
if (mNavigationBarView != null) {
pw.println(" mNavigationBarWindowState="
@@ -1206,9 +1212,11 @@
return true;
}
}
- mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
- mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
- });
+ if (mLongPressHomeEnabled) {
+ mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
+ });
+ }
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
@@ -1480,15 +1488,23 @@
| (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
}
- private void sendAssistantAvailability(boolean available) {
+ private void updateAssistantEntrypoints() {
+ mAssistantAvailable = mAssistManagerLazy.get()
+ .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+ mLongPressHomeEnabled = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, 1) != 0;
+ mAssistantTouchGestureEnabled = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, 1) != 0;
if (mOverviewProxyService.getProxy() != null) {
try {
- mOverviewProxyService.getProxy().onAssistantAvailable(available
+ mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable
+ && mAssistantTouchGestureEnabled
&& QuickStepContract.isGesturalMode(mNavBarMode));
} catch (RemoteException e) {
Log.w(TAG, "Unable to send assistant availability data to launcher");
}
}
+ reconfigureHomeLongClick();
}
// ----- Methods that DisplayNavigationBarController talks to -----
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 5bc1280..440c5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -175,7 +175,7 @@
/** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int appWidgetId) {
+ int appWidgetId, Uri contactUri) {
// Write relevant persisted storage.
SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
Context.MODE_PRIVATE);
@@ -186,27 +186,24 @@
widgetEditor.apply();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
- editor.putString(String.valueOf(appWidgetId), key.getShortcutId());
+ String contactUriString = contactUri == null ? EMPTY_STRING : contactUri.toString();
+ editor.putString(String.valueOf(appWidgetId), contactUriString);
// Don't overwrite existing widgets with the same key.
- Set<String> storedWidgetIds = new HashSet<>(
- sp.getStringSet(key.toString(), new HashSet<>()));
- storedWidgetIds.add(String.valueOf(appWidgetId));
- editor.putStringSet(key.toString(), storedWidgetIds);
+ addAppWidgetIdForKey(sp, editor, appWidgetId, key.toString());
+ addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
editor.apply();
}
/** Removes stored data when tile is deleted. */
public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int widgetId) {
+ int widgetId, String contactUriString) {
// Delete widgetId mapping to key.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
- Set<String> storedWidgetIds = new HashSet<>(
- sp.getStringSet(key.toString(), new HashSet<>()));
- storedWidgetIds.remove(String.valueOf(widgetId));
- editor.putStringSet(key.toString(), storedWidgetIds);
editor.remove(String.valueOf(widgetId));
+ removeAppWidgetIdForKey(sp, editor, widgetId, key.toString());
+ removeAppWidgetIdForKey(sp, editor, widgetId, contactUriString);
editor.apply();
// Delete all data specifically mapped to widgetId.
@@ -219,6 +216,23 @@
widgetEditor.apply();
}
+ private static void addAppWidgetIdForKey(SharedPreferences sp, SharedPreferences.Editor editor,
+ int widgetId, String storageKey) {
+ Set<String> storedWidgetIdsByKey = new HashSet<>(
+ sp.getStringSet(storageKey, new HashSet<>()));
+ storedWidgetIdsByKey.add(String.valueOf(widgetId));
+ editor.putStringSet(storageKey, storedWidgetIdsByKey);
+ }
+
+ private static void removeAppWidgetIdForKey(SharedPreferences sp,
+ SharedPreferences.Editor editor,
+ int widgetId, String storageKey) {
+ Set<String> storedWidgetIds = new HashSet<>(
+ sp.getStringSet(storageKey, new HashSet<>()));
+ storedWidgetIds.remove(String.valueOf(widgetId));
+ editor.putStringSet(storageKey, storedWidgetIds);
+ }
+
/** Augments a single {@link PeopleSpaceTile} with notification content, if one is present. */
public static PeopleSpaceTile augmentSingleTileFromVisibleNotifications(Context context,
PeopleSpaceTile tile, NotificationEntryManager notificationEntryManager) {
@@ -256,7 +270,7 @@
PeopleSpaceTile tile, Map<PeopleTileKey, NotificationEntry> visibleNotifications) {
PeopleTileKey key = new PeopleTileKey(
tile.getId(), getUserId(tile), tile.getPackageName());
-
+ // TODO: Match missed calls with matching Uris in addition to keys.
if (!visibleNotifications.containsKey(key)) {
if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key.toString());
return tile;
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 776e8a2..5be2d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -16,17 +16,23 @@
package com.android.systemui.people.widget;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
+
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification;
+import static com.android.systemui.people.PeopleSpaceUtils.getMessagingStyleMessages;
import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds;
import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.Person;
@@ -39,6 +45,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -54,16 +61,20 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
@@ -83,11 +94,19 @@
private SharedPreferences mSharedPrefs;
private PeopleManager mPeopleManager;
private NotificationEntryManager mNotificationEntryManager;
+ private PackageManager mPackageManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
@GuardedBy("mLock")
public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
mListeners = new HashMap<>();
+ @GuardedBy("mLock")
+ // Map of notification key mapped to widget IDs previously updated by the contact Uri field.
+ // This is required because on notification removal, the contact Uri field is stripped and we
+ // only have the notification key to determine which widget IDs should be updated.
+ private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
+ private boolean mIsForTesting;
+
@Inject
public PeopleSpaceWidgetManager(Context context) {
if (DEBUG) Log.d(TAG, "constructor");
@@ -99,6 +118,7 @@
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPeopleManager = mContext.getSystemService(PeopleManager.class);
mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
+ mPackageManager = mContext.getPackageManager();
}
/**
@@ -108,12 +128,15 @@
protected void setAppWidgetManager(
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager, PackageManager packageManager,
+ boolean isForTesting) {
mAppWidgetManager = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
mNotificationEntryManager = notificationEntryManager;
+ mPackageManager = packageManager;
+ mIsForTesting = isForTesting;
}
/**
@@ -222,6 +245,16 @@
public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction notificationAction) {
if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called");
+ if (mIsForTesting) {
+ updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction);
+ return;
+ }
+ ThreadUtils.postOnBackgroundThread(
+ () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
+ }
+
+ private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction action) {
try {
String sbnShortcutId = sbn.getShortcutId();
if (sbnShortcutId == null) {
@@ -235,23 +268,175 @@
Log.d(TAG, "No app widget ids returned");
return;
}
+ PeopleTileKey key = new PeopleTileKey(
+ sbnShortcutId,
+ sbn.getUser().getIdentifier(),
+ sbn.getPackageName());
+ if (!key.isValid()) {
+ Log.d(TAG, "Invalid key");
+ return;
+ }
synchronized (mLock) {
- PeopleTileKey key = new PeopleTileKey(
- sbnShortcutId,
- UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(),
- sbn.getPackageName());
- Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, key);
- for (String widgetIdString : storedWidgetIds) {
- int widgetId = Integer.parseInt(widgetIdString);
- if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey());
- updateStorageAndViewWithNotificationData(sbn, notificationAction, widgetId);
- }
+ // First, update People Tiles associated with the Notification's package/shortcut.
+ Set<String> tilesUpdatedByKey = getStoredWidgetIds(mSharedPrefs, key);
+ updateWidgetIdsForNotificationAction(tilesUpdatedByKey, sbn, action);
+
+ // Then, update People Tiles across other packages that use the same Uri.
+ updateTilesByUri(key, sbn, action);
}
} catch (Exception e) {
Log.e(TAG, "Exception: " + e);
}
}
+ /** Updates {@code widgetIdsToUpdate} with {@code action}. */
+ private void updateWidgetIdsForNotificationAction(Set<String> widgetIdsToUpdate,
+ StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction action) {
+ for (String widgetIdString : widgetIdsToUpdate) {
+ int widgetId = Integer.parseInt(widgetIdString);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId);
+ if (storedTile == null) {
+ if (DEBUG) Log.d(TAG, "Could not find stored tile for notification");
+ continue;
+ }
+ if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey());
+ updateStorageAndViewWithNotificationData(sbn, action, widgetId,
+ storedTile);
+ }
+ }
+
+ /**
+ * Updates tiles with matched Uris, dependent on the {@code action}.
+ *
+ * <p>If the notification was added, adds the notification based on the contact Uri within
+ * {@code sbn}.
+ * <p>If the notification was removed, removes the notification based on the in-memory map of
+ * widgets previously updated by Uri (since the contact Uri is stripped from the {@code sbn}).
+ */
+ private void updateTilesByUri(PeopleTileKey key, StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction action) {
+ if (action.equals(PeopleSpaceUtils.NotificationAction.POSTED)) {
+ Set<String> widgetIdsUpdatedByUri = supplementTilesByUri(sbn, action, key);
+ if (widgetIdsUpdatedByUri != null && !widgetIdsUpdatedByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Added due to uri: " + widgetIdsUpdatedByUri);
+ mNotificationKeyToWidgetIdsMatchedByUri.put(sbn.getKey(), widgetIdsUpdatedByUri);
+ }
+ } else {
+ // Remove the notification on any widgets where the notification was added
+ // purely based on the Uri.
+ Set<String> widgetsPreviouslyUpdatedByUri =
+ mNotificationKeyToWidgetIdsMatchedByUri.remove(sbn.getKey());
+ if (widgetsPreviouslyUpdatedByUri != null && !widgetsPreviouslyUpdatedByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Remove due to uri: " + widgetsPreviouslyUpdatedByUri);
+ updateWidgetIdsForNotificationAction(widgetsPreviouslyUpdatedByUri, sbn,
+ action);
+ }
+ }
+ }
+
+ /**
+ * Retrieves from storage any tiles with the same contact Uri as linked via the {@code sbn}.
+ * Supplements the tiles with the notification content only if they still have {@link
+ * android.Manifest.permission.READ_CONTACTS} permission.
+ */
+ @Nullable
+ private Set<String> supplementTilesByUri(StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction notificationAction, PeopleTileKey key) {
+ if (!shouldMatchNotificationByUri(sbn)) {
+ if (DEBUG) Log.d(TAG, "Should not supplement conversation");
+ return null;
+ }
+
+ // Try to get the Contact Uri from the Missed Call notification directly.
+ String contactUri = getContactUri(sbn);
+ if (contactUri == null) {
+ if (DEBUG) Log.d(TAG, "No contact uri");
+ return null;
+ }
+
+ // Supplement any tiles with the same Uri.
+ Set<String> storedWidgetIdsByUri =
+ new HashSet<>(mSharedPrefs.getStringSet(contactUri, new HashSet<>()));
+ if (storedWidgetIdsByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No tiles for contact");
+ return null;
+ }
+
+ if (mPackageManager.checkPermission(READ_CONTACTS,
+ sbn.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG) Log.d(TAG, "Notifying app missing permissions");
+ return null;
+ }
+
+ Set<String> widgetIdsUpdatedByUri = new HashSet<>();
+ for (String widgetIdString : storedWidgetIdsByUri) {
+ int widgetId = Integer.parseInt(widgetIdString);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId);
+ // Don't update a widget already updated.
+ if (key.equals(new PeopleTileKey(storedTile))) {
+ continue;
+ }
+ if (storedTile == null || mPackageManager.checkPermission(READ_CONTACTS,
+ storedTile.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG) Log.d(TAG, "Cannot supplement tile: " + storedTile.getUserName());
+ continue;
+ }
+ if (DEBUG) Log.d(TAG, "Adding notification by uri: " + sbn.getKey());
+ updateStorageAndViewWithNotificationData(sbn, notificationAction,
+ widgetId, storedTile);
+ widgetIdsUpdatedByUri.add(String.valueOf(widgetId));
+ }
+ return widgetIdsUpdatedByUri;
+ }
+
+ /**
+ * Try to retrieve a valid Uri via {@code sbn}, falling back to the {@code
+ * contactUriFromShortcut} if valid.
+ */
+ @Nullable
+ private String getContactUri(StatusBarNotification sbn) {
+ // First, try to get a Uri from the Person directly set on the Notification.
+ ArrayList<Person> people = sbn.getNotification().extras.getParcelableArrayList(
+ EXTRA_PEOPLE_LIST);
+ if (people != null && people.get(0) != null) {
+ String contactUri = people.get(0).getUri();
+ if (contactUri != null && !contactUri.isEmpty()) {
+ return contactUri;
+ }
+ }
+
+ // Then, try to get a Uri from the Person set on the Notification message.
+ List<Notification.MessagingStyle.Message> messages =
+ getMessagingStyleMessages(sbn.getNotification());
+ if (messages != null && !messages.isEmpty()) {
+ Notification.MessagingStyle.Message message = messages.get(0);
+ Person sender = message.getSenderPerson();
+ if (sender != null && sender.getUri() != null && !sender.getUri().isEmpty()) {
+ return sender.getUri();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a notification should be matched to other Tiles by Uri.
+ *
+ * <p>Currently only matches missed calls.
+ */
+ private boolean shouldMatchNotificationByUri(StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification == null) {
+ if (DEBUG) Log.d(TAG, "Notification is null");
+ return false;
+ }
+ if (!Objects.equals(notification.category, CATEGORY_MISSED_CALL)) {
+ if (DEBUG) Log.d(TAG, "Not missed call");
+ return false;
+ }
+ return true;
+ }
+
/**
* Update the tiles associated with the incoming conversation update.
*/
@@ -309,16 +494,11 @@
private void updateStorageAndViewWithNotificationData(
StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction notificationAction,
- int appWidgetId) {
- PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId);
- if (storedTile == null) {
- if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to");
- return;
- }
+ int appWidgetId, PeopleSpaceTile storedTile) {
if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId);
storedTile = augmentTileFromNotification(mContext, storedTile, sbn);
- } else {
+ } else if (storedTile.getNotificationKey().equals(sbn.getKey())) {
if (DEBUG) {
Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId);
}
@@ -440,7 +620,8 @@
synchronized (mLock) {
if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getId());
PeopleTileKey key = new PeopleTileKey(tile);
- PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId);
+ PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
+ tile.getContactUri());
}
try {
if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
@@ -496,6 +677,7 @@
// Retrieve storage needed for widget deletion.
PeopleTileKey key;
Set<String> storedWidgetIdsForKey;
+ String contactUriString;
synchronized (mLock) {
SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId),
Context.MODE_PRIVATE);
@@ -509,9 +691,11 @@
}
storedWidgetIdsForKey = new HashSet<>(
mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
+ contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
}
synchronized (mLock) {
- PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId);
+ PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId,
+ contactUriString);
}
// If another tile with the conversation is still stored, we need to keep the listener.
if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index fea521f..bdb3926 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -241,15 +241,7 @@
public void run() {
final float valFloat;
final boolean inVrMode = mIsVrModeEnabled;
- if (inVrMode) {
- valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mDefaultBacklightForVr,
- UserHandle.USER_CURRENT);
- } else {
- valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBacklight,
- UserHandle.USER_CURRENT);
- }
+ valFloat = mDisplayManager.getBrightness(mDisplayId);
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(valFloat);
mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
@@ -364,14 +356,12 @@
metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
minBacklight = mMinimumBacklightForVr;
maxBacklight = mMaximumBacklightForVr;
- settingToChange = Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT;
} else {
metric = mAutomatic
? MetricsEvent.ACTION_BRIGHTNESS_AUTO
: MetricsEvent.ACTION_BRIGHTNESS;
minBacklight = PowerManager.BRIGHTNESS_MIN;
maxBacklight = PowerManager.BRIGHTNESS_MAX;
- settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT;
}
final float valFloat = MathUtils.min(convertGammaToLinearFloat(value,
minBacklight, maxBacklight),
@@ -386,8 +376,7 @@
if (!tracking) {
AsyncTask.execute(new Runnable() {
public void run() {
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- settingToChange, valFloat, UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(mDisplayId, valFloat);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 6f80317..05af08e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -25,10 +25,8 @@
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
-import kotlin.math.max
-private const val RIPPLE_ANIMATION_DURATION: Long = 1500
-private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/**
* Expanding ripple effect that shows when charging begins.
@@ -39,17 +37,18 @@
private val defaultColor: Int = 0xffffffff.toInt()
private val ripplePaint = Paint()
+ var radius: Float = 0.0f
+ set(value) { rippleShader.radius = value }
+ var origin: PointF = PointF()
+ set(value) { rippleShader.origin = value }
+ var duration: Long = 1500
+
init {
rippleShader.color = defaultColor
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
- }
-
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- rippleShader.origin = PointF(measuredWidth / 2f, measuredHeight.toFloat())
- rippleShader.radius = max(measuredWidth, measuredHeight).toFloat()
- super.onLayout(changed, left, top, right, bottom)
+ visibility = View.GONE
}
fun startRipple() {
@@ -57,7 +56,7 @@
return // Ignore if ripple effect is already playing
}
val animator = ValueAnimator.ofFloat(0f, 1f)
- animator.duration = RIPPLE_ANIMATION_DURATION
+ animator.duration = duration
animator.addUpdateListener { animator ->
val now = animator.currentPlayTime
val phase = now / 30000f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index 5547c1e..d400205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -53,7 +53,7 @@
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.25;
- float h = l + 0.025;
+ float h = l + 0.005;
float o = abs(sin(0.1 * PI * (t + i)));
s += threshold(n + o, l, h);
}
@@ -97,7 +97,7 @@
float fadeRipple = min(fadeIn, 1.-fadeOutRipple);
float rippleAlpha = softRing(p, in_origin, radius, 0.5)
* fadeRipple * in_color.a;
- vec4 ripple = in_color * max(circle, rippleAlpha) * 0.4;
+ vec4 ripple = in_color * max(circle, rippleAlpha) * 0.3;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}"""
private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index b567ad4..2900462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.res.Configuration
+import android.graphics.PointF
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroupOverlay
@@ -31,6 +32,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.PrintWriter
+import java.lang.Integer.max
import javax.inject.Inject
/***
@@ -46,7 +48,7 @@
private val context: Context,
private val keyguardStateController: KeyguardStateController
) {
- private var pluggedIn: Boolean? = null
+ private var charging: Boolean? = null
private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled
@VisibleForTesting
var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
@@ -55,16 +57,18 @@
val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(
level: Int,
- nowPluggedIn: Boolean,
- charging: Boolean
+ pluggedIn: Boolean,
+ nowCharging: Boolean
) {
- if (!rippleEnabled) {
+ // Suppresses the ripple when it's disabled, or when the state change comes
+ // from wireless charging.
+ if (!rippleEnabled || batteryController.isWirelessCharging) {
return
}
- val wasPluggedIn = pluggedIn
- pluggedIn = nowPluggedIn
+ val wasCharging = charging
+ charging = nowCharging
// Only triggers when the keyguard is active and the device is just plugged in.
- if (wasPluggedIn == false && nowPluggedIn && keyguardStateController.isShowing) {
+ if (wasCharging == false && nowCharging && keyguardStateController.isShowing) {
rippleView.startRipple()
}
}
@@ -113,10 +117,13 @@
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
if (width != rippleView.width || height != rippleView.height) {
- rippleView.measure(
- View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
- rippleView.layout(0, 0, width, height)
+ rippleView.apply {
+ measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
+ layout(0, 0, width, height)
+ origin = PointF(width / 2f, height.toFloat())
+ radius = max(width, height).toFloat()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c83b60d..d581c4b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -184,7 +184,6 @@
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
@@ -2469,39 +2468,19 @@
protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,
long animationDelay) {
- if (mDozing || mKeyguardManager.isKeyguardLocked()) {
- // on ambient or lockscreen, hide notification panel
- WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- transmittingBatteryLevel, batteryLevel,
- new WirelessChargingAnimation.Callback() {
- @Override
- public void onAnimationStarting() {
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
- }
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ transmittingBatteryLevel, batteryLevel,
+ new WirelessChargingAnimation.Callback() {
+ @Override
+ public void onAnimationStarting() {
+ mNotificationShadeWindowController.setRequestTopUi(true, TAG);
+ }
- @Override
- public void onAnimationEnded() {
- CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- }
- }, mDozing).show(animationDelay);
- } else {
- // workspace
- WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- transmittingBatteryLevel, batteryLevel,
- new WirelessChargingAnimation.Callback() {
- @Override
- public void onAnimationStarting() {
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- }
-
- @Override
- public void onAnimationEnded() {
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- }
- }, false).show(animationDelay);
- }
+ @Override
+ public void onAnimationEnded() {
+ mNotificationShadeWindowController.setRequestTopUi(false, TAG);
+ }
+ }, false).show(animationDelay);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
index 3bc2632..95216c5 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
@@ -28,6 +28,12 @@
import javax.inject.Inject;
+/**
+ * Class for use by {@link TelephonyListenerManager} to centralize TelephonyManager Callbacks.
+ *
+ * There are more callback interfaces defined in {@link android.telephony.TelephonyCallback} that
+ * are not currently covered. Add them here if they ever become necessary.
+ */
class TelephonyCallback extends android.telephony.TelephonyCallback
implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
index 4e1acca..3111930 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
@@ -47,7 +47,9 @@
private boolean mListening = false;
@Inject
- public TelephonyListenerManager(TelephonyManager telephonyManager, @Main Executor executor,
+ public TelephonyListenerManager(
+ TelephonyManager telephonyManager,
+ @Main Executor executor,
TelephonyCallback telephonyCallback) {
mTelephonyManager = telephonyManager;
mExecutor = executor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 7090e78..d91625e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -17,11 +17,14 @@
package com.android.systemui.people.widget;
import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
@@ -55,6 +58,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -86,6 +90,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -106,6 +111,8 @@
private static final int SECOND_WIDGET_ID_WITH_SHORTCUT = 3;
private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
private static final int WIDGET_ID_WITH_KEY_IN_OPTIONS = 4;
+ private static final int WIDGET_ID_WITH_SAME_URI = 5;
+ private static final int WIDGET_ID_WITH_DIFFERENT_URI = 6;
private static final String SHORTCUT_ID = "101";
private static final String OTHER_SHORTCUT_ID = "102";
private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0";
@@ -123,10 +130,21 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID, "username", ICON, new Intent())
.setPackageName(TEST_PACKAGE_A)
- .setUserHandle(new UserHandle(1))
+ .setUserHandle(new UserHandle(0))
.setNotificationKey(NOTIFICATION_KEY + "1")
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
+ .setContactUri(URI)
+ .build();
+ private static final PeopleSpaceTile PERSON_TILE_WITH_SAME_URI =
+ new PeopleSpaceTile
+ // Different shortcut ID
+ .Builder(OTHER_SHORTCUT_ID, "username", ICON, new Intent())
+ // Different package name
+ .setPackageName(TEST_PACKAGE_B)
+ .setUserHandle(new UserHandle(0))
+ // Same contact uri.
+ .setContactUri(URI)
.build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
SHORTCUT_ID).setLongLabel("name").build();
@@ -149,6 +167,8 @@
private LauncherApps mLauncherApps;
@Mock
private NotificationEntryManager mNotificationEntryManager;
+ @Mock
+ private PackageManager mPackageManager;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -167,7 +187,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
mManager = new PeopleSpaceWidgetManager(mContext);
mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
- mLauncherApps, mNotificationEntryManager);
+ mLauncherApps, mNotificationEntryManager, mPackageManager, true);
mManager.attach(mListenerService);
mProvider = new PeopleSpaceWidgetProvider();
mProvider.setPeopleSpaceWidgetManager(mManager);
@@ -177,16 +197,10 @@
mNoMan.addListener(serviceListener);
clearStorage();
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
-
- Bundle options = new Bundle();
- options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
- when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
- .thenReturn(options);
+ addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
- when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(
- getConversationWithShortcutId(SHORTCUT_ID));
}
@Test
@@ -477,7 +491,7 @@
addSecondWidgetForPersonTile();
PeopleSpaceUtils.removeSharedPreferencesStorageForTile(
- mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT);
+ mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT, EMPTY_STRING);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false))
@@ -501,7 +515,6 @@
throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
@@ -527,7 +540,6 @@
throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
@@ -548,10 +560,267 @@
}
@Test
+ public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTile()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testRemoveMissedCallNotificationWithContentPostedIfMatchingUriTile()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+ NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0);
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(2))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotRemoveMissedCallIfMatchingUriTileMissingReadContactsPermissionWhenPosted()
+ throws Exception {
+ when(mPackageManager.checkPermission(any(),
+ eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn(
+ PERMISSION_HARD_DENIED);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+ // We should only try to remove the notification if the Missed Call was added when posted.
+ NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0);
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTileFromSender()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ Notification notificationWithPersonOnlyInSender =
+ createMessagingStyleNotificationWithoutExtras(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */
+ true).build();
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(notificationWithPersonOnlyInSender)
+ .setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
+ .build();
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(sbn)
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNoPersonsAttached()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ // Notification posted without any Person attached.
+ Notification notificationWithoutPersonObject =
+ createMessagingStyleNotificationWithoutExtras(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */
+ true).setStyle(new Notification.MessagingStyle("sender")
+ .addMessage(
+ new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10,
+ "sender"))
+ ).build();
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(notificationWithoutPersonObject)
+ .setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
+ .build();
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(sbn)
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since notification doesn't include a Person reference.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNotMatchingUriTile()
+ throws Exception {
+ clearStorage();
+ addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE_WITH_SAME_URI.toBuilder().setContactUri(
+ Uri.parse("different_uri")).build(), WIDGET_ID_WITH_DIFFERENT_URI);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_DIFFERENT_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since missing permission to read contacts.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_DIFFERENT_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_DIFFERENT_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallIfMatchingUriTileMissingReadContactsPermission()
+ throws Exception {
+ when(mPackageManager.checkPermission(any(),
+ eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn(
+ PERMISSION_HARD_DENIED);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since missing permission to read contacts.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
public void testUpdateNotificationRemovedIfExistingTile() throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
StatusBarNotification sbn = createNotification(
SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false);
@@ -574,7 +843,8 @@
}
@Test
- public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners() {
+ public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners()
+ throws Exception {
addSecondWidgetForPersonTile();
mProvider.onUpdate(mContext, mAppWidgetManager,
new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
@@ -746,19 +1016,26 @@
* Adds another widget for {@code PERSON_TILE} with widget ID: {@code
* SECOND_WIDGET_ID_WITH_SHORTCUT}.
*/
- private void addSecondWidgetForPersonTile() {
- Bundle options = new Bundle();
- options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
- when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT)))
- .thenReturn(options);
+ private void addSecondWidgetForPersonTile() throws Exception {
// Set the same Person associated on another People Tile widget ID.
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE, SECOND_WIDGET_ID_WITH_SHORTCUT);
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT,
SECOND_WIDGET_ID_WITH_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
}
+ private void addTileForWidget(PeopleSpaceTile tile, int widgetId) throws Exception {
+ setStorageForTile(tile.getId(), tile.getPackageName(), widgetId, tile.getContactUri());
+ Bundle options = new Bundle();
+ options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
+ when(mAppWidgetManager.getAppWidgetOptions(eq(widgetId)))
+ .thenReturn(options);
+ when(mIPeopleManager.getConversation(tile.getPackageName(), 0, tile.getId())).thenReturn(
+ getConversationWithShortcutId(tile.getId()));
+ when(mPackageManager.checkPermission(any(), eq(tile.getPackageName()))).thenReturn(
+ PERMISSION_GRANTED);
+ }
+
/**
* Returns a single conversation associated with {@code shortcutId}.
*/
@@ -772,7 +1049,7 @@
private ConversationChannel getConversationWithShortcutId(String shortcutId,
List<ConversationStatus> statuses) throws Exception {
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
- "name").build();
+ "name").setPerson(PERSON).build();
ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
0L, false, false, statuses);
return convo;
@@ -780,6 +1057,30 @@
private Notification createMessagingStyleNotification(String shortcutId,
boolean isMessagingStyle, boolean isMissedCall) {
+ Bundle extras = new Bundle();
+ ArrayList<Person> person = new ArrayList<Person>();
+ person.add(PERSON);
+ extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, person);
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setContentTitle("TEST_TITLE")
+ .setContentText("TEST_TEXT")
+ .setExtras(extras)
+ .setShortcutId(shortcutId);
+ if (isMessagingStyle) {
+ builder.setStyle(new Notification.MessagingStyle(PERSON)
+ .addMessage(
+ new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10,
+ PERSON))
+ );
+ }
+ if (isMissedCall) {
+ builder.setCategory(CATEGORY_MISSED_CALL);
+ }
+ return builder.build();
+ }
+
+ private Notification.Builder createMessagingStyleNotificationWithoutExtras(String shortcutId,
+ boolean isMessagingStyle, boolean isMissedCall) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentTitle("TEST_TITLE")
.setContentText("TEST_TEXT")
@@ -794,9 +1095,10 @@
if (isMissedCall) {
builder.setCategory(CATEGORY_MISSED_CALL);
}
- return builder.build();
+ return builder;
}
+
private StatusBarNotification createNotification(String shortcutId,
boolean isMessagingStyle, boolean isMissedCall) {
Notification notification = createMessagingStyleNotification(
@@ -804,6 +1106,8 @@
return new SbnBuilder()
.setNotification(notification)
.setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
.build();
}
@@ -824,11 +1128,16 @@
String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS),
Context.MODE_PRIVATE);
widgetSp4.edit().clear().commit();
+ SharedPreferences widgetSp5 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_WITH_SAME_URI),
+ Context.MODE_PRIVATE);
+ widgetSp5.edit().clear().commit();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
sp.edit().clear().commit();
}
- private void setStorageForTile(String shortcutId, String packageName, int widgetId) {
+ private void setStorageForTile(String shortcutId, String packageName, int widgetId,
+ Uri contactUri) {
SharedPreferences widgetSp = mContext.getSharedPreferences(
String.valueOf(widgetId),
Context.MODE_PRIVATE);
@@ -840,11 +1149,17 @@
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = sp.edit();
- editor.putString(String.valueOf(widgetId), shortcutId);
+ editor.putString(String.valueOf(widgetId), contactUri.toString());
+
String key = new PeopleTileKey(shortcutId, 0, packageName).toString();
Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
storedWidgetIds.add(String.valueOf(widgetId));
editor.putStringSet(key, storedWidgetIds);
+
+ Set<String> storedWidgetIdsByUri = new HashSet<>(
+ sp.getStringSet(contactUri.toString(), new HashSet<>()));
+ storedWidgetIdsByUri.add(String.valueOf(widgetId));
+ editor.putStringSet(contactUri.toString(), storedWidgetIdsByUri);
editor.apply();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index 3701b91..9ce7241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -93,8 +93,8 @@
captor.value.onBatteryLevelChanged(
unusedBatteryLevel,
- true /* plugged in */,
- false /* charging */)
+ false /* plugged in */,
+ true /* charging */)
verify(rippleView).startRipple()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
index 463b336..ac15903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
@@ -27,7 +27,6 @@
import com.android.systemui.SysuiTestCase;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,7 +35,7 @@
public class TelephonyCallbackTest extends SysuiTestCase {
private TelephonyCallback mTelephonyCallback = new TelephonyCallback();
-
+
@Test
public void testAddListener_ActiveDataSubscriptionIdListener() {
assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8f56842..8bb9ce9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3716,6 +3716,7 @@
mDnsManager.removeNetwork(nai.network);
}
mNetIdManager.releaseNetId(nai.network.getNetId());
+ nai.onNetworkDisconnected();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -8126,6 +8127,7 @@
updateCapabilitiesForNetwork(networkAgent);
}
networkAgent.created = true;
+ networkAgent.onNetworkCreated();
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index a8cbcb5..97df5bf 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -577,6 +577,28 @@
}
}
+ /**
+ * Notify the NetworkAgent that the network is successfully connected.
+ */
+ public void onNetworkCreated() {
+ try {
+ networkAgent.onNetworkCreated();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network created event", e);
+ }
+ }
+
+ /**
+ * Notify the NetworkAgent that the network is disconnected and destroyed.
+ */
+ public void onNetworkDisconnected() {
+ try {
+ networkAgent.onNetworkDisconnected();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network disconnected event", e);
+ }
+ }
+
// TODO: consider moving out of NetworkAgentInfo into its own class
private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub {
private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
new file mode 100644
index 0000000..8ce7b66
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.Display;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Saves brightness to a persistent data store, enabling each logical display to have its own
+ * brightness.
+ */
+public class BrightnessSetting {
+ private static final String TAG = "BrightnessSetting";
+
+ private static final int MSG_BRIGHTNESS_CHANGED = 1;
+ private static final Uri BRIGHTNESS_FLOAT_URI =
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
+ private final PersistentDataStore mPersistentDataStore;
+
+ private final boolean mIsDefaultDisplay;
+ private final Context mContext;
+ private final LogicalDisplay mLogicalDisplay;
+ private final Object mLock = new Object();
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_BRIGHTNESS_CHANGED) {
+ float brightnessVal = Float.intBitsToFloat(msg.arg1);
+ notifyListeners(brightnessVal);
+ }
+ }
+ };
+
+ private final ContentObserver mBrightnessSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
+ float brightness = getScreenBrightnessSettingFloat();
+ setBrightness(brightness, true);
+ }
+ }
+ };
+
+ private final CopyOnWriteArrayList<BrightnessSettingListener> mListeners =
+ new CopyOnWriteArrayList<BrightnessSettingListener>();
+
+ private float mBrightness;
+
+ BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+ @NonNull LogicalDisplay logicalDisplay,
+ @NonNull Context context) {
+ mPersistentDataStore = persistentDataStore;
+ mLogicalDisplay = logicalDisplay;
+ mContext = context;
+ mIsDefaultDisplay = mLogicalDisplay.getDisplayIdLocked() == Display.DEFAULT_DISPLAY;
+ mBrightness = mPersistentDataStore.getBrightness(
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+ if (mIsDefaultDisplay) {
+ mContext.getContentResolver().registerContentObserver(BRIGHTNESS_FLOAT_URI,
+ false, mBrightnessSettingsObserver);
+ }
+ }
+
+ /**
+ * Returns the brightness from the brightness setting
+ *
+ * @return brightness for the current display
+ */
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Registers listener for brightness setting change events.
+ */
+ public void registerListener(BrightnessSettingListener l) {
+ if (!mListeners.contains(l)) {
+ mListeners.add(l);
+ }
+ }
+
+ /**
+ * Unregisters listener for brightness setting change events.
+ *
+ * @param l listener
+ */
+ public void unregisterListener(BrightnessSettingListener l) {
+ mListeners.remove(l);
+ }
+
+ void setBrightness(float brightness) {
+ setBrightness(brightness, false);
+ }
+
+ private void setBrightness(float brightness, boolean isFromSystemSetting) {
+ if (brightness == mBrightness) {
+ return;
+ }
+ if (Float.isNaN(brightness)) {
+ Slog.w(TAG, "Attempting to set invalid brightness");
+ return;
+ }
+ synchronized (mLock) {
+
+ mBrightness = brightness;
+
+ // If it didn't come from us
+ if (mIsDefaultDisplay && !isFromSystemSetting) {
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness,
+ UserHandle.USER_CURRENT);
+ }
+ mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
+ brightness);
+ int toSend = Float.floatToIntBits(mBrightness);
+ Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, toSend, 0);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private float getScreenBrightnessSettingFloat() {
+ return Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void notifyListeners(float brightness) {
+ for (BrightnessSettingListener l : mListeners) {
+ l.onBrightnessChanged(brightness);
+ }
+ }
+
+ /**
+ * Listener for changes to system brightness.
+ */
+ public interface BrightnessSettingListener {
+
+ /**
+ * Notify that the brightness has changed.
+ */
+ void onBrightnessChanged(float brightness);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c010906..e38d91c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1955,9 +1955,12 @@
if (mBrightnessTracker == null) {
mBrightnessTracker = new BrightnessTracker(mContext, null);
}
+
+ final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
+ display, mContext);
final DisplayPowerController displayPowerController = new DisplayPowerController(
mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
- mDisplayBlanker, display, mBrightnessTracker);
+ mDisplayBlanker, display, mBrightnessTracker, brightnessSetting);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
@@ -2662,6 +2665,48 @@
}
@Override // Binder call
+ public void setBrightness(int displayId, float brightness) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ if (!isValidBrightness(brightness)) {
+ Slog.w(TAG, "Attempted to set invalid brightness" + brightness);
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ dpc.putScreenBrightnessSetting(brightness);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public float getBrightness(int displayId) {
+ float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ brightness = dpc.getScreenBrightnessSetting();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return brightness;
+ }
+
+ @Override // Binder call
public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
@@ -2809,6 +2854,13 @@
Slog.w(TAG, msg);
return false;
}
+
+ }
+
+ private static boolean isValidBrightness(float brightness) {
+ return !Float.isNaN(brightness)
+ && (brightness >= PowerManager.BRIGHTNESS_MIN)
+ && (brightness <= PowerManager.BRIGHTNESS_MAX);
}
private final class LocalService extends DisplayManagerInternal {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d1d0496..48edb73 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,13 +16,11 @@
package com.android.server.display;
-import android.Manifest;
import android.content.Context;
import android.content.Intent;
-import android.os.Binder;
+import android.hardware.display.DisplayManager;
import android.os.ShellCommand;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.view.Display;
import java.io.PrintWriter;
@@ -111,17 +109,8 @@
}
final Context context = mService.getContext();
- context.enforceCallingOrSelfPermission(
- Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
- "Permission required to set the display's brightness");
- final long token = Binder.clearCallingIdentity();
- try {
- Settings.System.putFloatForUser(context.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness,
- UserHandle.USER_CURRENT);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ dm.setBrightness(Display.DEFAULT_DISPLAY, brightness);
return 0;
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 645ca7a..4bbd338 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -263,6 +263,8 @@
highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
}
+ // We try to find a range of priorities which define a non-empty set of allowed display
+ // modes. Each time we fail we increase the lowest priority.
while (lowestConsideredPriority <= highestConsideredPriority) {
summarizeVotes(
votes, lowestConsideredPriority, highestConsideredPriority, primarySummary);
@@ -343,8 +345,15 @@
}
if (baseModeId == INVALID_DISPLAY_MODE_ID) {
- throw new IllegalStateException("Can't select a base display mode for display "
- + displayId + ". The votes are " + mVotesByDisplay.valueAt(displayId));
+ Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
+ + " back to the default mode. Display = " + displayId + ", votes = " + votes
+ + ", supported modes = " + Arrays.toString(modes));
+
+ float fps = defaultMode.getRefreshRate();
+ return new DesiredDisplayModeSpecs(defaultMode.getModeId(),
+ /*allowGroupSwitching */ false,
+ new RefreshRateRange(fps, fps),
+ new RefreshRateRange(fps, fps));
}
if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7110d3e..56ad01b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -122,6 +122,7 @@
private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
private static final int MSG_IGNORE_PROXIMITY = 8;
private static final int MSG_STOP = 9;
+ private static final int MSG_UPDATE_BRIGHTNESS = 10;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -355,13 +356,14 @@
private final HighBrightnessModeController mHbmController;
+ private final BrightnessSetting mBrightnessSetting;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
// The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
private float mInitialAutoBrightness;
-
// The controller for the automatic brightness level.
private AutomaticBrightnessController mAutomaticBrightnessController;
@@ -410,6 +412,7 @@
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+ private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
// True if this DisplayPowerController has been stopped and should no longer be running.
private boolean mStopped;
@@ -420,7 +423,7 @@
public DisplayPowerController(Context context,
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
- BrightnessTracker brightnessTracker) {
+ BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
@@ -439,7 +442,7 @@
mContext = context;
mBrightnessTracker = brightnessTracker;
-
+ mBrightnessSetting = brightnessSetting;
PowerManager pm = context.getSystemService(PowerManager.class);
final Resources resources = context.getResources();
@@ -785,6 +788,10 @@
mAutomaticBrightnessController.stop();
}
+ if (mBrightnessSetting != null) {
+ mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+ }
+
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
}
}
@@ -831,10 +838,12 @@
if (brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
+ mBrightnessSettingListener = brightnessValue -> {
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
+ mHandler.sendMessage(msg);
+ };
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
@@ -1150,7 +1159,7 @@
// before applying the low power or dim transformations so that the slider
// accurately represents the full possible range, even if they range changes what
// it means in absolute terms.
- putScreenBrightnessSetting(brightnessState);
+ putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
}
// Apply dimming by at least some minimum amount when user activity
@@ -1804,7 +1813,6 @@
private void handleSettingsChange(boolean userSwitch) {
mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
-
if (userSwitch) {
// Don't treat user switches as user initiated change.
mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
@@ -1825,10 +1833,11 @@
return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
}
- private float getScreenBrightnessSetting() {
- final float brightness = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, mScreenBrightnessDefault,
- UserHandle.USER_CURRENT);
+ float getScreenBrightnessSetting() {
+ float brightness = mBrightnessSetting.getBrightness();
+ if (Float.isNaN(brightness)) {
+ brightness = mScreenBrightnessDefault;
+ }
return clampAbsoluteBrightness(brightness);
}
@@ -1839,13 +1848,15 @@
return clampScreenBrightnessForVr(brightnessFloat);
}
- private void putScreenBrightnessSetting(float brightnessValue) {
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ void putScreenBrightnessSetting(float brightnessValue) {
+ putScreenBrightnessSetting(brightnessValue, false);
+ }
+
+ private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) {
+ if (updateCurrent) {
mCurrentScreenBrightnessSetting = brightnessValue;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue,
- UserHandle.USER_CURRENT);
}
+ mBrightnessSetting.setBrightness(brightnessValue);
}
private void putAutoBrightnessAdjustmentSetting(float adjustment) {
@@ -2175,7 +2186,7 @@
}
break;
case MSG_CONFIGURE_BRIGHTNESS:
- mBrightnessConfiguration = (BrightnessConfiguration)msg.obj;
+ mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
updatePowerState();
break;
@@ -2197,6 +2208,12 @@
case MSG_STOP:
cleanupHandlerThreadAfterStop();
break;
+
+ case MSG_UPDATE_BRIGHTNESS:
+ if (mStopped) {
+ return;
+ }
+ handleSettingsChange(false /*userSwitch*/);
}
}
}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index a62642b..c90ddf4 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -62,6 +62,7 @@
* <display-states>
* <display unique-id="XXXXXXX">
* <color-mode>0</color-mode>
+ * <brightness-value>0</brightness-value>
* </display>
* </display-states>
* <stable-device-values>
@@ -82,7 +83,7 @@
* TODO: refactor this to extract common code shared with the input manager's data store
*/
final class PersistentDataStore {
- static final String TAG = "DisplayManager";
+ static final String TAG = "DisplayManager.PersistentDataStore";
private static final String TAG_DISPLAY_MANAGER_STATE = "display-manager-state";
@@ -95,6 +96,7 @@
private static final String TAG_DISPLAY_STATES = "display-states";
private static final String TAG_DISPLAY = "display";
private static final String TAG_COLOR_MODE = "color-mode";
+ private static final String TAG_BRIGHTNESS_VALUE = "brightness-value";
private static final String ATTR_UNIQUE_ID = "unique-id";
private static final String TAG_STABLE_DEVICE_VALUES = "stable-device-values";
@@ -255,6 +257,30 @@
return false;
}
+ public float getBrightness(DisplayDevice device) {
+ if (device == null || !device.hasStableUniqueId()) {
+ return Float.NaN;
+ }
+ final DisplayState state = getDisplayState(device.getUniqueId(), false);
+ if (state == null) {
+ return Float.NaN;
+ }
+ return state.getBrightness();
+ }
+
+ public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
+ if (state.setBrightness(brightness)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
public Point getStableDisplaySize() {
loadIfNeeded();
return mStableDeviceValues.getDisplaySize();
@@ -473,6 +499,7 @@
private static final class DisplayState {
private int mColorMode;
+ private float mBrightness;
public boolean setColorMode(int colorMode) {
if (colorMode == mColorMode) {
@@ -486,14 +513,33 @@
return mColorMode;
}
+ public boolean setBrightness(float brightness) {
+ if (brightness == mBrightness) {
+ return false;
+ }
+ mBrightness = brightness;
+ return true;
+ }
+
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+
public void loadFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals(TAG_COLOR_MODE)) {
- String value = parser.nextText();
- mColorMode = Integer.parseInt(value);
+ switch (parser.getName()) {
+ case TAG_COLOR_MODE:
+ String value = parser.nextText();
+ mColorMode = Integer.parseInt(value);
+ break;
+ case TAG_BRIGHTNESS_VALUE:
+ String brightness = parser.nextText();
+ mBrightness = Float.parseFloat(brightness);
+ break;
}
}
}
@@ -502,10 +548,15 @@
serializer.startTag(null, TAG_COLOR_MODE);
serializer.text(Integer.toString(mColorMode));
serializer.endTag(null, TAG_COLOR_MODE);
+ serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+ serializer.text(Float.toString(mBrightness));
+ serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
+
}
public void dump(final PrintWriter pw, final String prefix) {
pw.println(prefix + "ColorMode=" + mColorMode);
+ pw.println(prefix + "BrightnessValue=" + mBrightness);
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 830c3ea..4d302b1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -301,7 +301,7 @@
mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
if (userId == getCurrentUserId()) {
- Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+ Log.d(TAG, "User: " + userId + "mic privacy: " + enabled);
sendMicrophoneDisableSettingUpdate(enabled);
}
});
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 3245fdf..7be47a4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -324,8 +324,11 @@
}
public void onMicrophoneDisableSettingChanged(boolean enabled) {
- sendSettingChanged(android.hardware.contexthub.V1_2.Setting.GLOBAL_MIC_DISABLE,
- enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
+ // The SensorPrivacyManager reports if microphone privacy was enabled,
+ // which translates to microphone access being disabled (and vice-versa).
+ // With this in mind, we flip the argument before piping it to CHRE.
+ sendSettingChanged(android.hardware.contexthub.V1_2.Setting.MICROPHONE,
+ enabled ? SettingValue.DISABLED : SettingValue.ENABLED);
}
private void sendSettingChanged(byte setting, byte newValue) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1b5bb95..f185464 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2622,23 +2622,19 @@
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
UserHandle.USER_CURRENT_OR_SELF);
}
- float minFloat = mPowerManager.getBrightnessConstraint(
+ float min = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float maxFloat = mPowerManager.getBrightnessConstraint(
+ float max = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction;
- float brightnessFloat = Settings.System.getFloatForUser(
- mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- mContext.getDisplay().getBrightnessDefault(),
- UserHandle.USER_CURRENT_OR_SELF);
- brightnessFloat += stepFloat;
+ float step = (max - min) / BRIGHTNESS_STEPS * direction;
+ int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
+ float brightness = mDisplayManager.getBrightness(screenDisplayId);
+ brightness += step;
// Make sure we don't go beyond the limits.
- brightnessFloat = Math.min(maxFloat, brightnessFloat);
- brightnessFloat = Math.max(minFloat, brightnessFloat);
+ brightness = Math.min(max, brightness);
+ brightness = Math.max(min, brightness);
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat,
- UserHandle.USER_CURRENT_OR_SELF);
+ mDisplayManager.setBrightness(screenDisplayId, brightness);
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 28940b3..8481961 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -21,6 +21,7 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_PRIORITIZE;
import static android.app.AlarmManager.FLAG_STANDALONE;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.RTC;
@@ -62,6 +63,7 @@
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -443,6 +445,12 @@
TEST_CALLING_UID);
}
+ private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) {
+ mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
+ FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE, null);
+ }
+
private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi,
boolean unrestricted) {
final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE;
@@ -579,6 +587,7 @@
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40);
setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45);
setDeviceConfigLong(KEY_MIN_WINDOW, 50);
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55);
assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL);
@@ -589,6 +598,7 @@
assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
assertEquals(45, mService.mConstants.LISTENER_TIMEOUT);
assertEquals(50, mService.mConstants.MIN_WINDOW);
+ assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY);
}
@Test
@@ -1586,6 +1596,89 @@
}
@Test
+ public void prioritizedAlarmsInBatterySaver() throws Exception {
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
+ when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+ final long minDelay = 5;
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay);
+
+ final long firstTrigger = mNowElapsedTest + 4;
+ final AtomicInteger alarmsFired = new AtomicInteger(0);
+ final int numAlarms = 10;
+ for (int i = 0; i < numAlarms; i++) {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+ new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback)
+ throws RemoteException {
+ alarmsFired.incrementAndGet();
+ }
+ });
+ }
+ assertEquals(firstTrigger, mTestTimer.getElapsed());
+ mNowElapsedTest = firstTrigger;
+ mTestTimer.expire();
+ while (alarmsFired.get() < numAlarms) {
+ assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals(numAlarms, alarmsFired.get());
+ }
+
+ @Test
+ public void prioritizedAlarmsInDeviceIdle() throws Exception {
+ doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+ final long minDelay = 5;
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay);
+
+ final long idleUntil = mNowElapsedTest + 1000;
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil, getNewMockPendingIntent());
+ assertNotNull(mService.mPendingIdleUntil);
+
+ final long firstTrigger = mNowElapsedTest + 4;
+ final AtomicInteger alarmsFired = new AtomicInteger(0);
+ final int numAlarms = 10;
+ for (int i = 0; i < numAlarms; i++) {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+ new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback)
+ throws RemoteException {
+ alarmsFired.incrementAndGet();
+ }
+ });
+ }
+ assertEquals(firstTrigger, mTestTimer.getElapsed());
+ mNowElapsedTest = firstTrigger;
+ mTestTimer.expire();
+ while (alarmsFired.get() < numAlarms) {
+ assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals(numAlarms, alarmsFired.get());
+
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ });
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ });
+ assertEquals(idleUntil - 3, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+
+ assertEquals(idleUntil, mTestTimer.getElapsed());
+ }
+
+ @Test
public void dispatchOrder() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5761958..9513c6e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -70,6 +70,7 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD"/>
<uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/>
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 15ada89..c8099e2 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -464,6 +464,40 @@
}
@Test
+ public void testStaleAppRequestSize() {
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector);
+ Display.Mode[] modes = new Display.Mode[] {
+ new Display.Mode(1, 1280, 720, 60),
+ };
+ Display.Mode defaultMode = modes[0];
+
+ // Inject supported modes
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Inject default mode
+ SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
+ defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
+ director.injectDefaultModeByDisplay(defaultModesByDisplay);
+
+ // Inject votes
+ SparseArray<Vote> votes = new SparseArray<>();
+ votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080));
+ votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(50, 50));
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(DISPLAY_ID, votes);
+ director.injectVotesByDisplay(votesByDisplay);
+
+ director.setShouldAlwaysRespectAppRequestedMode(true);
+
+ // We should return the only available mode
+ DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+ assertThat(specs.baseModeId).isEqualTo(defaultMode.getModeId());
+ }
+
+ @Test
public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index b25bc99..dfb229d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -136,7 +136,7 @@
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
- @Presubmit
+ @FlakyTest
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 95b1d3c..be0357e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -153,7 +153,7 @@
testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
- @Presubmit
+ @FlakyTest
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index a9888b1..a3d1395 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -24,6 +25,7 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -56,6 +58,12 @@
}
}
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index a19a95d..62b9b81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -80,6 +80,24 @@
super.navBarLayerRotatesAndScales()
}
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() {
+ super.statusBarLayerRotatesScales()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index dcc64c9..38af8a7d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -24,6 +25,7 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -61,6 +63,18 @@
}
}
+ @FlakyTest
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 6d2a923..f037f08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -79,7 +80,7 @@
testSpec.navBarLayerIsAlwaysVisible()
}
- @Presubmit
+ @FlakyTest
@Test
open fun navBarLayerRotatesAndScales() {
testSpec.navBarLayerRotatesAndScales(
@@ -98,14 +99,14 @@
testSpec.statusBarLayerIsAlwaysVisible()
}
- @Presubmit
+ @FlakyTest
@Test
open fun statusBarLayerRotatesScales() {
testSpec.statusBarLayerRotatesScales(
testSpec.config.startRotation, testSpec.config.endRotation)
}
- @Presubmit
+ @FlakyTest
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
@@ -121,7 +122,7 @@
}
}
- @Presubmit
+ @FlakyTest
@Test
open fun noUncoveredRegions() {
testSpec.noUncoveredRegions(testSpec.config.startRotation,
@@ -134,7 +135,7 @@
testSpec.focusDoesNotChange()
}
- @Presubmit
+ @FlakyTest
@Test
open fun appLayerRotates_StartingPos() {
testSpec.assertLayersStart {
@@ -142,7 +143,7 @@
}
}
- @Presubmit
+ @FlakyTest
@Test
open fun appLayerRotates_EndingPos() {
testSpec.assertLayersEnd {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 97c65eb..8b072c4 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -83,10 +83,10 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
- final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
- final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
@@ -590,7 +590,7 @@
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
- netId == 100 ? mNcWifi : mNcCell);
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
}
void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 52975e4..50aaaee 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -63,10 +63,10 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
- final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
- final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
@@ -475,7 +475,7 @@
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
- netId == 100 ? mNcWifi : mNcCell);
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
}
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {