Merge "Switch from android.text.format.Time"
diff --git a/Android.bp b/Android.bp
index b20b085..b2ed0de 100644
--- a/Android.bp
+++ b/Android.bp
@@ -248,10 +248,6 @@
name: "framework-minus-apex",
defaults: ["framework-defaults"],
javac_shard_size: 150,
- required: [
- "framework-platform-compat-config",
- "libcore-platform-compat-config",
- ],
}
java_library {
@@ -262,6 +258,11 @@
"framework-minus-apex",
"jobscheduler-framework",
],
+ required: [
+ "framework-platform-compat-config",
+ "libcore-platform-compat-config",
+ "services-platform-compat-config",
+ ],
sdk_version: "core_platform",
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index 4c11947..1bb9e96 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -18,13 +18,20 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -32,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.job.ConstantsProto;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -55,6 +63,9 @@
/** Delay alarm tag for logging purposes */
private final String DELAY_TAG = "*job.delay*";
+ private final Handler mHandler;
+ private final TcConstants mTcConstants;
+
private long mNextJobExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
@@ -70,6 +81,14 @@
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
mChainedAttributionEnabled = mService.isChainedAttributionEnabled();
+
+ mHandler = new Handler(mContext.getMainLooper());
+ mTcConstants = new TcConstants(mHandler);
+ }
+
+ @Override
+ public void onSystemServicesReady() {
+ mTcConstants.start(mContext.getContentResolver());
}
/**
@@ -294,8 +313,7 @@
} else {
if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
if (DEBUG) {
- Slog.i(TAG,
- "Skipping " + job + " because delay won't make it ready.");
+ Slog.i(TAG, "Skipping " + job + " because delay won't make it ready.");
}
continue;
}
@@ -354,7 +372,8 @@
/**
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
* delay will expire.
- * This alarm <b>will</b> wake up the phone.
+ * This alarm <b>will not</b> wake up the phone if
+ * {@link TcConstants#USE_NON_WAKEUP_ALARM_FOR_DELAY} is true.
*/
private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
@@ -362,8 +381,11 @@
return;
}
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
- mNextDelayExpiredElapsedMillis, ws);
+ final int alarmType =
+ mTcConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY
+ ? AlarmManager.ELAPSED_REALTIME : AlarmManager.ELAPSED_REALTIME_WAKEUP;
+ updateAlarmWithListenerLocked(DELAY_TAG, alarmType,
+ mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis, ws);
}
/**
@@ -377,16 +399,16 @@
return;
}
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
- mNextJobExpiredElapsedMillis, ws);
+ updateAlarmWithListenerLocked(DEADLINE_TAG, AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mDeadlineExpiredListener, mNextJobExpiredElapsedMillis, ws);
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
}
- private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
- long alarmTimeElapsed, WorkSource ws) {
+ private void updateAlarmWithListenerLocked(String tag, @AlarmManager.AlarmType int alarmType,
+ OnAlarmListener listener, long alarmTimeElapsed, WorkSource ws) {
ensureAlarmServiceLocked();
if (alarmTimeElapsed == Long.MAX_VALUE) {
mAlarmService.cancel(listener);
@@ -394,7 +416,7 @@
if (DEBUG) {
Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
}
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
+ mAlarmService.set(alarmType, alarmTimeElapsed,
AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
}
}
@@ -422,9 +444,77 @@
};
@VisibleForTesting
- void recheckAlarmsLocked() {
- checkExpiredDeadlinesAndResetAlarm();
- checkExpiredDelaysAndResetAlarm();
+ class TcConstants extends ContentObserver {
+ private ContentResolver mResolver;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ private static final String KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY =
+ "use_non_wakeup_delay_alarm";
+
+ private static final boolean DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY = true;
+
+ /**
+ * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
+ * ready now.
+ */
+ public boolean USE_NON_WAKEUP_ALARM_FOR_DELAY = DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY;
+
+ /**
+ * Creates a content observer.
+ *
+ * @param handler The handler to run {@link #onChange} on, or null if none.
+ */
+ TcConstants(Handler handler) {
+ super(handler);
+ }
+
+ private void start(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS), false, this);
+ onChange(true, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String constants = Settings.Global.getString(
+ mResolver, Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS);
+
+ try {
+ mParser.setString(constants);
+ } catch (Exception e) {
+ // Failed to parse the settings string, log this and move on with defaults.
+ Slog.e(TAG, "Bad jobscheduler time controller settings", e);
+ }
+
+ USE_NON_WAKEUP_ALARM_FOR_DELAY = mParser.getBoolean(
+ KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY);
+ // Intentionally not calling checkExpiredDelaysAndResetAlarm() here. There's no need to
+ // iterate through the entire list again for this constant change. The next delay alarm
+ // that is set will make use of the new constant value.
+ }
+
+ private void dump(IndentingPrintWriter pw) {
+ pw.println();
+ pw.println("TimeController:");
+ pw.increaseIndent();
+ pw.printPair(KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY,
+ USE_NON_WAKEUP_ALARM_FOR_DELAY).println();
+ pw.decreaseIndent();
+ }
+
+ private void dump(ProtoOutputStream proto) {
+ final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER);
+ proto.write(ConstantsProto.TimeController.USE_NON_WAKEUP_ALARM_FOR_DELAY,
+ USE_NON_WAKEUP_ALARM_FOR_DELAY);
+ proto.end(tcToken);
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ TcConstants getTcConstants() {
+ return mTcConstants;
}
@Override
@@ -501,4 +591,14 @@
proto.end(mToken);
proto.end(token);
}
+
+ @Override
+ public void dumpConstants(IndentingPrintWriter pw) {
+ mTcConstants.dump(pw);
+ }
+
+ @Override
+ public void dumpConstants(ProtoOutputStream proto) {
+ mTcConstants.dump(proto);
+ }
}
diff --git a/api/current.txt b/api/current.txt
index 593710314..652f23f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13460,6 +13460,7 @@
public final class GestureLibraries {
method public static android.gesture.GestureLibrary fromFile(String);
method public static android.gesture.GestureLibrary fromFile(java.io.File);
+ method @NonNull public static android.gesture.GestureLibrary fromFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
method public static android.gesture.GestureLibrary fromPrivateFile(android.content.Context, String);
method public static android.gesture.GestureLibrary fromRawResource(android.content.Context, @RawRes int);
}
@@ -17073,6 +17074,7 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd
+ field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe
field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7
field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0
field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1
@@ -42240,6 +42242,7 @@
method public int speak(CharSequence, int, android.os.Bundle, String);
method @Deprecated public int speak(String, int, java.util.HashMap<java.lang.String,java.lang.String>);
method public int stop();
+ method public int synthesizeToFile(@NonNull CharSequence, @NonNull android.os.Bundle, @NonNull android.os.ParcelFileDescriptor, @NonNull String);
method public int synthesizeToFile(CharSequence, android.os.Bundle, java.io.File, String);
method @Deprecated public int synthesizeToFile(String, java.util.HashMap<java.lang.String,java.lang.String>, String);
field public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
diff --git a/api/system-current.txt b/api/system-current.txt
index 76d017c..ed2616a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -29,6 +29,7 @@
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
+ field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
field public static final String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
@@ -192,6 +193,7 @@
field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
+ field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
@@ -513,6 +515,8 @@
}
public class Notification implements android.os.Parcelable {
+ method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean);
+ method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions();
field public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
field public static final String CATEGORY_CAR_INFORMATION = "car_information";
field public static final String CATEGORY_CAR_WARNING = "car_warning";
@@ -521,6 +525,10 @@
field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400
}
+ public static final class Notification.MessagingStyle.Message {
+ method @Nullable public static android.app.Notification.MessagingStyle.Message getMessageFromBundle(@NonNull android.os.Bundle);
+ }
+
public static final class Notification.TvExtender implements android.app.Notification.Extender {
ctor public Notification.TvExtender();
ctor public Notification.TvExtender(android.app.Notification);
@@ -543,6 +551,7 @@
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
method public org.json.JSONObject toJson() throws org.json.JSONException;
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ field public static final int USER_LOCKED_SOUND = 32; // 0x20
}
public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -6648,6 +6657,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.SnoozeCriterion> CREATOR;
}
+ public class StatusBarNotification implements android.os.Parcelable {
+ method public boolean isAppGroup();
+ }
+
}
package android.service.oemlock {
@@ -6767,6 +6780,20 @@
}
+package android.service.storage {
+
+ public abstract class ExternalStorageService extends android.app.Service {
+ ctor public ExternalStorageService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
+ method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull String, @NonNull String) throws java.io.IOException;
+ field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
+ field public static final int FLAG_SESSION_TYPE_FUSE = 1; // 0x1
+ field public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService";
+ }
+
+}
+
package android.service.textclassifier {
public abstract class TextClassifierService extends android.app.Service {
diff --git a/api/test-current.txt b/api/test-current.txt
index e9615111..1b2c59d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -619,10 +619,13 @@
method public int describeContents();
method public static android.content.AutofillOptions forWhitelistingItself();
method public boolean isAugmentedAutofillEnabled(@NonNull android.content.Context);
+ method public boolean isAutofillDisabledLocked(@NonNull android.content.ComponentName);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.AutofillOptions> CREATOR;
+ field public long appDisabledExpiration;
field public boolean augmentedAutofillEnabled;
field public final boolean compatModeEnabled;
+ field @Nullable public android.util.ArrayMap<java.lang.String,java.lang.Long> disabledActivities;
field public final int loggingLevel;
field @Nullable public android.util.ArraySet<android.content.ComponentName> whitelistedActivitiesForAugmentedAutofill;
}
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 0570c3a..8dcb865 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -664,7 +664,7 @@
nanosleep(&spec, nullptr);
}
clock_gettime(CLOCK_REALTIME, &spec);
- timestampNs = (spec.tv_sec) * 1000 + spec.tv_nsec;
+ timestampNs = int64_t(spec.tv_sec) * 1000 + spec.tv_nsec;
} while (file_exists_locked(timestampNs));
return timestampNs;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 372eab2..ac53118 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3278,6 +3278,7 @@
* @hide
*/
@Nullable
+ @SystemApi
public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
if (actions == null) {
return null;
@@ -3304,7 +3305,8 @@
*
* @hide
*/
- public List<Notification.Action> getContextualActions() {
+ @SystemApi
+ public @NonNull List<Notification.Action> getContextualActions() {
if (actions == null) return Collections.emptyList();
List<Notification.Action> contextualActions = new ArrayList<>();
@@ -7730,7 +7732,8 @@
* @hide
*/
@Nullable
- public static Message getMessageFromBundle(Bundle bundle) {
+ @SystemApi
+ public static Message getMessageFromBundle(@NonNull Bundle bundle) {
try {
if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
return null;
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3effd11..93e4ddc 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -110,6 +110,7 @@
/**
* @hide
*/
+ @SystemApi
public static final int USER_LOCKED_SOUND = 0x00000020;
/**
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index ed2b991..f9b96c5 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -242,7 +242,7 @@
mUiAutomationConnection.connect(mClient, flags);
mFlags = flags;
} catch (RemoteException re) {
- throw new RuntimeException("Error while connecting UiAutomation", re);
+ throw new RuntimeException("Error while connecting " + this, re);
}
synchronized (mLock) {
@@ -255,7 +255,7 @@
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
- throw new RuntimeException("Error while connecting UiAutomation");
+ throw new RuntimeException("Error while connecting " + this);
}
try {
mLock.wait(remainingTimeMillis);
@@ -290,7 +290,7 @@
synchronized (mLock) {
if (mIsConnecting) {
throw new IllegalStateException(
- "Cannot call disconnect() while connecting!");
+ "Cannot call disconnect() while connecting " + this);
}
throwIfNotConnectedLocked();
mConnectionId = CONNECTION_ID_UNDEFINED;
@@ -299,7 +299,7 @@
// Calling out without a lock held.
mUiAutomationConnection.disconnect();
} catch (RemoteException re) {
- throw new RuntimeException("Error while disconnecting UiAutomation", re);
+ throw new RuntimeException("Error while disconnecting " + this, re);
} finally {
mRemoteCallbackThread.quit();
mRemoteCallbackThread = null;
@@ -1184,19 +1184,29 @@
return result;
}
+ @Override
+ public String toString() {
+ final StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
+ stringBuilder.append("[id=").append(mConnectionId);
+ stringBuilder.append(", flags=").append(mFlags);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
private boolean isConnectedLocked() {
return mConnectionId != CONNECTION_ID_UNDEFINED;
}
private void throwIfConnectedLocked() {
if (mConnectionId != CONNECTION_ID_UNDEFINED) {
- throw new IllegalStateException("UiAutomation not connected!");
+ throw new IllegalStateException("UiAutomation not connected, " + this);
}
}
private void throwIfNotConnectedLocked() {
if (!isConnectedLocked()) {
- throw new IllegalStateException("UiAutomation not connected!");
+ throw new IllegalStateException("UiAutomation not connected, " + this);
}
}
@@ -1220,6 +1230,9 @@
mConnectionId = connectionId;
mLock.notifyAll();
}
+ if (Build.IS_DEBUGGABLE) {
+ Log.v(LOG_TAG, "Init " + UiAutomation.this);
+ }
}
@Override
diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java
index 8fb9501..082663e 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -21,6 +21,8 @@
import android.app.ActivityThread;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.autofill.AutofillManager;
@@ -62,6 +64,18 @@
@Nullable
public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill;
+ /**
+ * The package disable expiration by autofill service.
+ */
+ public long appDisabledExpiration;
+
+ /**
+ * The disabled Activities of the package. key is component name string, value is when they
+ * will be enabled.
+ */
+ @Nullable
+ public ArrayMap<String, Long> disabledActivities;
+
public AutofillOptions(int loggingLevel, boolean compatModeEnabled) {
this.loggingLevel = loggingLevel;
this.compatModeEnabled = compatModeEnabled;
@@ -82,6 +96,27 @@
}
/**
+ * Returns if autofill is disabled by service to the given activity.
+ */
+ public boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
+ final long elapsedTime = SystemClock.elapsedRealtime();
+ final String component = componentName.flattenToString();
+ // Check app first.
+ if (appDisabledExpiration >= elapsedTime) return true;
+
+ // Then check activities.
+ if (disabledActivities != null) {
+ final Long expiration = disabledActivities.get(component);
+ if (expiration != null) {
+ if (expiration >= elapsedTime) return true;
+ disabledActivities.remove(component);
+ }
+ }
+ appDisabledExpiration = 0;
+ return false;
+ }
+
+ /**
* @hide
*/
@TestApi
@@ -110,7 +145,8 @@
@Override
public String toString() {
return "AutofillOptions [loggingLevel=" + loggingLevel + ", compatMode=" + compatModeEnabled
- + ", augmentedAutofillEnabled=" + augmentedAutofillEnabled + "]";
+ + ", augmentedAutofillEnabled=" + augmentedAutofillEnabled
+ + ", appDisabledExpiration=" + appDisabledExpiration + "]";
}
/** @hide */
@@ -122,6 +158,11 @@
pw.print(", whitelistedActivitiesForAugmentedAutofill=");
pw.print(whitelistedActivitiesForAugmentedAutofill);
}
+ pw.print(", appDisabledExpiration="); pw.print(appDisabledExpiration);
+ if (disabledActivities != null) {
+ pw.print(", disabledActivities=");
+ pw.print(disabledActivities);
+ }
}
@Override
@@ -135,6 +176,16 @@
parcel.writeBoolean(compatModeEnabled);
parcel.writeBoolean(augmentedAutofillEnabled);
parcel.writeArraySet(whitelistedActivitiesForAugmentedAutofill);
+ parcel.writeLong(appDisabledExpiration);
+ final int size = disabledActivities != null ? disabledActivities.size() : 0;
+ parcel.writeInt(size);
+ if (size > 0) {
+ for (int i = 0; i < size; i++) {
+ final String key = disabledActivities.keyAt(i);
+ parcel.writeString(key);
+ parcel.writeLong(disabledActivities.get(key));
+ }
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<AutofillOptions> CREATOR =
@@ -148,6 +199,14 @@
options.augmentedAutofillEnabled = parcel.readBoolean();
options.whitelistedActivitiesForAugmentedAutofill =
(ArraySet<ComponentName>) parcel.readArraySet(null);
+ options.appDisabledExpiration = parcel.readLong();
+ final int size = parcel.readInt();
+ if (size > 0) {
+ options.disabledActivities = new ArrayMap<>();
+ for (int i = 0; i < size; i++) {
+ options.disabledActivities.put(parcel.readString(), parcel.readLong());
+ }
+ }
return options;
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 4ea3726..f297c06 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -17,6 +17,7 @@
package android.content;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_ERRORED;
@@ -645,9 +646,11 @@
}
boolean checkUser(int pid, int uid, Context context) {
- return UserHandle.getUserId(uid) == context.getUserId()
- || mSingleUser
- || context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
+ if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
+ return true;
+ }
+ return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED
+ || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
== PERMISSION_GRANTED;
}
@@ -1030,10 +1033,12 @@
/** @hide */
public final void setTransportLoggingEnabled(boolean enabled) {
- if (enabled) {
- mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this);
- } else {
- mTransport.mInterface = this;
+ if (mTransport != null) {
+ if (enabled) {
+ mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this);
+ } else {
+ mTransport.mInterface = this;
+ }
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bb5ced5..2c53faa 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -38,6 +38,7 @@
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
+import android.compat.IPlatformCompat;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -3228,6 +3229,7 @@
ROLE_SERVICE,
//@hide ROLE_CONTROLLER_SERVICE,
CAMERA_SERVICE,
+ //@hide: PLATFORM_COMPAT_SERVICE,
PRINT_SERVICE,
CONSUMER_IR_SERVICE,
//@hide: TRUST_SERVICE,
@@ -4597,6 +4599,13 @@
public static final String STATS_MANAGER = "stats";
/**
+ * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+ * {@link IPlatformCompat} IBinder for communicating with the platform compat service.
+ * @hide
+ */
+ public static final String PLATFORM_COMPAT_SERVICE = "platform_compat";
+
+ /**
* Service to capture a bugreport.
* @see #getSystemService(String)
* @see android.os.BugreportManager
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index 73b8a48..1609f53 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -74,7 +74,10 @@
}
/**
- * Returns a list of all currently available rollbacks.
+ * Returns a list of all currently available rollbacks. This includes ones for very recently
+ * installed packages (even if onFinished has not yet been called). As a result, packages that
+ * very recently failed to install may also be included, but those rollbacks will fail with
+ * 'rollback not available'.
*
* @throws SecurityException if the caller does not have appropriate permissions.
*/
diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java
index 611d9ab..5e31ce6 100644
--- a/core/java/android/gesture/GestureLibraries.java
+++ b/core/java/android/gesture/GestureLibraries.java
@@ -16,14 +16,16 @@
package android.gesture;
+import android.annotation.NonNull;
import android.annotation.RawRes;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
import static android.gesture.GestureConstants.*;
import android.content.Context;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -41,6 +43,11 @@
return new FileGestureLibrary(path);
}
+ @NonNull
+ public static GestureLibrary fromFileDescriptor(@NonNull ParcelFileDescriptor pfd) {
+ return new FileGestureLibrary(pfd.getFileDescriptor());
+ }
+
public static GestureLibrary fromPrivateFile(Context context, String name) {
return fromFile(context.getFileStreamPath(name));
}
@@ -50,55 +57,83 @@
}
private static class FileGestureLibrary extends GestureLibrary {
+ // Either a file or an fd is used
private final File mPath;
+ private final FileDescriptor mFd;
public FileGestureLibrary(File path) {
mPath = path;
+ mFd = null;
}
+ public FileGestureLibrary(FileDescriptor fd) {
+ mPath = null;
+ mFd = fd;
+ }
+
+ /**
+ * <p>If this GestureLibrary was created using a FileDescriptor,
+ * this method will always return false.
+ */
@Override
public boolean isReadOnly() {
- return !mPath.canWrite();
+ if (mPath != null) {
+ return !mPath.canWrite();
+ }
+ return false;
}
public boolean save() {
if (!mStore.hasChanged()) return true;
+ boolean result = false;
- final File file = mPath;
+ if (mPath != null) {
+ final File file = mPath;
- final File parentFile = file.getParentFile();
- if (!parentFile.exists()) {
- if (!parentFile.mkdirs()) {
- return false;
+ final File parentFile = file.getParentFile();
+ if (!parentFile.exists()) {
+ if (!parentFile.mkdirs()) {
+ return false;
+ }
+ }
+
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ file.createNewFile();
+ mStore.save(new FileOutputStream(file), true);
+ result = true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
+ }
+ } else {
+ try {
+ mStore.save(new FileOutputStream(mFd), true);
+ result = true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library", e);
}
}
-
- boolean result = false;
- try {
- //noinspection ResultOfMethodCallIgnored
- file.createNewFile();
- mStore.save(new FileOutputStream(file), true);
- result = true;
- } catch (FileNotFoundException e) {
- Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
- } catch (IOException e) {
- Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
- }
-
return result;
}
public boolean load() {
boolean result = false;
- final File file = mPath;
- if (file.exists() && file.canRead()) {
+ if (mPath != null) {
+ final File file = mPath;
+ if (file.exists() && file.canRead()) {
+ try {
+ mStore.load(new FileInputStream(file), true);
+ result = true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ }
+ }
+ } else {
try {
- mStore.load(new FileInputStream(file), true);
+ mStore.load(new FileInputStream(mFd), true);
result = true;
- } catch (FileNotFoundException e) {
- Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
} catch (IOException e) {
- Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ Log.d(LOG_TAG, "Could not load the gesture library", e);
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 1142a07..fb6b231 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -100,9 +100,12 @@
/**
* @hide
*/
- public static final int DISMISSED_REASON_POSITIVE = 1;
+ public static final int DISMISSED_REASON_CONFIRMED = 1;
/**
+ * Dialog is done animating away after user clicked on the button set via
+ * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor,
+ * DialogInterface.OnClickListener)}.
* @hide
*/
public static final int DISMISSED_REASON_NEGATIVE = 2;
@@ -112,6 +115,25 @@
*/
public static final int DISMISSED_REASON_USER_CANCEL = 3;
+ /**
+ * Authenticated, confirmation not required. Dialog animated away.
+ * @hide
+ */
+ public static final int DISMISSED_REASON_CONFIRM_NOT_REQUIRED = 4;
+
+ /**
+ * Error message shown on SystemUI. When BiometricService receives this, the UI is already
+ * gone.
+ * @hide
+ */
+ public static final int DISMISSED_REASON_ERROR = 5;
+
+ /**
+ * Dialog dismissal requested by BiometricService.
+ * @hide
+ */
+ public static final int DISMISSED_REASON_SERVER_REQUESTED = 6;
+
private static class ButtonInfo {
Executor executor;
DialogInterface.OnClickListener listener;
@@ -362,7 +384,7 @@
@Override
public void onDialogDismissed(int reason) throws RemoteException {
// Check the reason and invoke OnClickListener(s) if necessary
- if (reason == DISMISSED_REASON_POSITIVE) {
+ if (reason == DISMISSED_REASON_CONFIRMED) {
mPositiveButtonInfo.executor.execute(() -> {
mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
});
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
index 180daaf..ca6114e 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
@@ -27,8 +27,8 @@
// Notify BiometricService that authentication was successful. If user confirmation is required,
// the auth token must be submitted into KeyStore.
void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token);
- // Notify BiometricService that an error has occurred.
- void onAuthenticationFailed(int cookie, boolean requireConfirmation);
+ // Notify BiometricService authentication was rejected.
+ void onAuthenticationFailed();
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
void onError(int cookie, int error, String message);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1b31792..fbcc785 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1955,6 +1955,7 @@
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -1973,6 +1974,7 @@
* @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
* @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
* @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index a0170da..2fa6125 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -996,6 +996,14 @@
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13;
+ /**
+ * <p>The camera device is only accessible by Android's system components and privileged
+ * applications. Processes need to have the android.permission.SYSTEM_CAMERA in
+ * addition to android.permission.CAMERA in order to connect to this camera device.</p>
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index 0fb93e5..beff0f7 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -211,10 +211,12 @@
@Override
public void onProgramListUpdated(ProgramList.Chunk chunk) {
- synchronized (mLock) {
- if (mProgramList == null) return;
- mProgramList.apply(Objects.requireNonNull(chunk));
- }
+ mHandler.post(() -> {
+ synchronized (mLock) {
+ if (mProgramList == null) return;
+ mProgramList.apply(Objects.requireNonNull(chunk));
+ }
+ });
}
@Override
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 0513fee..356b344 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.app.Dialog;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -50,6 +51,7 @@
final int mWindowType;
final int mGravity;
final boolean mTakesFocus;
+ final boolean mAutomotiveHideNavBarForKeyboard;
private final Rect mBounds = new Rect();
@Retention(SOURCE)
@@ -134,6 +136,8 @@
mWindowType = windowType;
mGravity = gravity;
mTakesFocus = takesFocus;
+ mAutomotiveHideNavBarForKeyboard = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
initDockWindow();
}
@@ -247,6 +251,11 @@
windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
+ if (isAutomotive() && mAutomotiveHideNavBarForKeyboard) {
+ windowSetFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ windowModFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ }
+
getWindow().setFlags(windowSetFlags, windowModFlags);
}
@@ -338,6 +347,10 @@
mWindowState = newState;
}
+ private boolean isAutomotive() {
+ return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
private static String stateToString(@SoftInputWindowState int state) {
switch (state) {
case SoftInputWindowState.TOKEN_PENDING:
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index e4f88c5..77fd946 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -20,6 +20,7 @@
import android.annotation.SystemService;
import android.content.Context;
import android.gsi.GsiProgress;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
/**
@@ -52,22 +53,39 @@
/** The DynamicSystemManager.Session represents a started session for the installation. */
public class Session {
private Session() {}
+
/**
- * Write a chunk of the DynamicSystem system image
+ * Set the file descriptor that points to a ashmem which will be used
+ * to fetch data during the submitFromAshmem.
*
- * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
- * error.
+ * @param ashmem fd that points to a ashmem
+ * @param size size of the ashmem file
*/
@RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
- public boolean write(byte[] buf) {
+ public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
try {
- return mService.write(buf);
+ return mService.setAshmem(ashmem, size);
} catch (RemoteException e) {
throw new RuntimeException(e.toString());
}
}
/**
+ * Submit bytes to the DSU partition from the ashmem previously set with
+ * setAshmem.
+ *
+ * @param size Number of bytes
+ * @return true on success, false otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean submitFromAshmem(int size) {
+ try {
+ return mService.submitFromAshmem(size);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ /**
* Finish write and make device to boot into the it after reboot.
*
* @return {@code true} if the call succeeds. {@code false} if there is any native runtime
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 2f4ab2d..a6de170 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -79,10 +79,20 @@
boolean setEnable(boolean enable, boolean oneShot);
/**
- * Write a chunk of the DynamicSystem system image
+ * Set the file descriptor that points to a ashmem which will be used
+ * to fetch data during the submitFromAshmem.
*
- * @return true if the call succeeds
+ * @param fd fd that points to a ashmem
+ * @param size size of the ashmem file
*/
- boolean write(in byte[] buf);
+ boolean setAshmem(in ParcelFileDescriptor fd, long size);
+ /**
+ * Submit bytes to the DSU partition from the ashmem previously set with
+ * setAshmem.
+ *
+ * @param bytes number of bytes that can be read from stream.
+ * @return true on success, false otherwise.
+ */
+ boolean submitFromAshmem(long bytes);
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4f7c8c5..bc03e51 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -706,16 +706,11 @@
synchronized (sLock) {
for (int i = 0; i < sListeners.size(); i++) {
if (namespace.equals(sListeners.valueAt(i).first)) {
- final int j = i;
- sListeners.valueAt(i).second.execute(new Runnable() {
- @Override
- public void run() {
- Map<String, String> propertyMap = new HashMap(1);
- propertyMap.put(name, value);
- sListeners.keyAt(j)
- .onPropertiesChanged(new Properties(namespace, propertyMap));
- }
-
+ final OnPropertiesChangedListener listener = sListeners.keyAt(i);
+ sListeners.valueAt(i).second.execute(() -> {
+ Map<String, String> propertyMap = new ArrayMap<>(1);
+ propertyMap.put(name, value);
+ listener.onPropertiesChanged(new Properties(namespace, propertyMap));
});
}
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2299aad..a959913f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -3592,10 +3592,23 @@
}
/** @hide */
+ public static Uri scanFile(ContentProviderClient client, File file) {
+ return scan(client, SCAN_FILE_CALL, file, false);
+ }
+
+ /** @hide */
private static Uri scan(Context context, String method, File file,
boolean originatedFromShell) {
final ContentResolver resolver = context.getContentResolver();
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
+ return scan(client, method, file, originatedFromShell);
+ }
+ }
+
+ /** @hide */
+ private static Uri scan(ContentProviderClient client, String method, File file,
+ boolean originatedFromShell) {
+ try {
final Bundle in = new Bundle();
in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9abb64f..e486235 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,16 +16,19 @@
package android.provider;
-import static android.provider.SettingsValidators.ANY_INTEGER_VALIDATOR;
-import static android.provider.SettingsValidators.ANY_STRING_VALIDATOR;
-import static android.provider.SettingsValidators.BOOLEAN_VALIDATOR;
-import static android.provider.SettingsValidators.COMPONENT_NAME_VALIDATOR;
-import static android.provider.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
-import static android.provider.SettingsValidators.LOCALE_VALIDATOR;
-import static android.provider.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
-import static android.provider.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR;
-import static android.provider.SettingsValidators.PACKAGE_NAME_VALIDATOR;
-import static android.provider.SettingsValidators.URI_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.TILE_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR;
import android.Manifest;
import android.annotation.IntDef;
@@ -80,7 +83,12 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.provider.SettingsValidators.Validator;
+import android.provider.settings.validators.ComponentNameListValidator;
+import android.provider.settings.validators.DiscreteValueValidator;
+import android.provider.settings.validators.InclusiveFloatRangeValidator;
+import android.provider.settings.validators.InclusiveIntegerRangeValidator;
+import android.provider.settings.validators.PackageNameListValidator;
+import android.provider.settings.validators.Validator;
import android.speech.tts.TextToSpeech;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
@@ -3149,7 +3157,7 @@
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+ new InclusiveIntegerRangeValidator(0, 3);
/**
* END_BUTTON_BEHAVIOR value for "go home".
@@ -3351,7 +3359,7 @@
"bluetooth_discoverability";
private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
+ new InclusiveIntegerRangeValidator(0, 2);
/**
* Bluetooth discoverability timeout. If this value is nonzero, then
@@ -3495,7 +3503,7 @@
public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
private static final Validator PEAK_REFRESH_RATE_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(24f, Float.MAX_VALUE);
+ new InclusiveFloatRangeValidator(24f, Float.MAX_VALUE);
/**
* The amount of time in milliseconds before the device goes to sleep or begins
@@ -3524,7 +3532,7 @@
public static final String SCREEN_BRIGHTNESS_FOR_VR = "screen_brightness_for_vr";
private static final Validator SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 255);
+ new InclusiveIntegerRangeValidator(0, 255);
/**
* Control whether to enable automatic brightness mode.
@@ -3542,7 +3550,7 @@
public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(-1, 1);
+ new InclusiveFloatRangeValidator(-1, 1);
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
@@ -3676,7 +3684,7 @@
"haptic_feedback_intensity";
private static final Validator VIBRATION_INTENSITY_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+ new InclusiveIntegerRangeValidator(0, 3);
/**
* Ringer volume. This is used internally, changing this value will not
@@ -3766,7 +3774,7 @@
public static final String MASTER_BALANCE = "master_balance";
private static final Validator MASTER_BALANCE_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(-1.f, 1.f);
+ new InclusiveFloatRangeValidator(-1.f, 1.f);
/**
* Whether the notifications should use the ring volume (value of 1) or
@@ -4004,7 +4012,7 @@
/** @hide */
public static final Validator TIME_12_24_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"12", "24", null});
+ new DiscreteValueValidator(new String[] {"12", "24", null});
/**
* Date format string
@@ -4090,7 +4098,7 @@
/** @hide */
public static final Validator USER_ROTATION_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+ new InclusiveIntegerRangeValidator(0, 3);
/**
* Control whether the rotation lock toggle in the System UI should be hidden.
@@ -4179,7 +4187,7 @@
/** @hide */
public static final Validator TTY_MODE_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+ new InclusiveIntegerRangeValidator(0, 3);
/**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
@@ -4381,7 +4389,7 @@
/** @hide */
public static final Validator SIP_CALL_OPTIONS_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(
+ new DiscreteValueValidator(
new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
/**
@@ -4428,7 +4436,7 @@
/** @hide */
public static final Validator POINTER_SPEED_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(-7, 7);
+ new InclusiveFloatRangeValidator(-7, 7);
/**
* Whether lock-to-app will be triggered by long-press on recents.
@@ -6352,7 +6360,7 @@
public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face";
private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR =
- SettingsValidators.JSON_OBJECT_VALIDATOR;
+ JSON_OBJECT_VALIDATOR;
/**
* Indicates which clock face to show on lock screen and AOD while docked.
@@ -6509,7 +6517,7 @@
"enabled_accessibility_services";
private static final Validator ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(":");
+ new ComponentNameListValidator(":");
/**
* List of the accessibility services to which the user has granted
@@ -6521,7 +6529,7 @@
"touch_exploration_granted_accessibility_services";
private static final Validator TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(":");
+ new ComponentNameListValidator(":");
/**
* Whether the Global Actions Panel is enabled.
@@ -6696,7 +6704,7 @@
"accessibility_display_magnification_scale";
private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE);
+ new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE);
/**
* Unused mangnification setting
@@ -6780,7 +6788,7 @@
"accessibility_captioning_preset";
private static final Validator ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"-1", "0", "1", "2",
+ new DiscreteValueValidator(new String[]{"-1", "0", "1", "2",
"3", "4"});
/**
@@ -6824,7 +6832,7 @@
"accessibility_captioning_edge_type";
private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2"});
+ new DiscreteValueValidator(new String[]{"0", "1", "2"});
/**
* Integer property that specifes the edge color for captions as a
@@ -6870,7 +6878,7 @@
"accessibility_captioning_typeface";
private static final Validator ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"DEFAULT",
+ new DiscreteValueValidator(new String[]{"DEFAULT",
"MONOSPACE", "SANS_SERIF", "SERIF"});
/**
@@ -6882,7 +6890,7 @@
"accessibility_captioning_font_scale";
private static final Validator ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR =
- new SettingsValidators.InclusiveFloatRangeValidator(0.5f, 2.0f);
+ new InclusiveFloatRangeValidator(0.5f, 2.0f);
/**
* Setting that specifies whether display color inversion is enabled.
@@ -6923,7 +6931,7 @@
"accessibility_display_daltonizer";
private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(
+ new DiscreteValueValidator(
new String[] {"-1", "0", "11", "12", "13"});
/**
@@ -7112,8 +7120,7 @@
*/
public static final String TTS_DEFAULT_LOCALE = "tts_default_locale";
- private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR =
- new SettingsValidators.TTSListValidator();
+ private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR = TTS_LIST_VALIDATOR;
/**
* Space delimited list of plugin packages that are enabled.
@@ -7121,7 +7128,7 @@
public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins";
private static final Validator TTS_ENABLED_PLUGINS_VALIDATOR =
- new SettingsValidators.PackageNameListValidator(" ");
+ new PackageNameListValidator(" ");
/**
* @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON}
@@ -7315,7 +7322,7 @@
"preferred_tty_mode";
private static final Validator PREFERRED_TTY_MODE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2", "3"});
+ new DiscreteValueValidator(new String[]{"0", "1", "2", "3"});
/**
* Whether the enhanced voice privacy mode is enabled.
@@ -7630,7 +7637,7 @@
public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
private static final Validator INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"1", "2"});
+ new DiscreteValueValidator(new String[]{"1", "2"});
/**
* INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
@@ -7851,7 +7858,7 @@
public static final String UI_NIGHT_MODE = "ui_night_mode";
private static final Validator UI_NIGHT_MODE_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
+ new InclusiveIntegerRangeValidator(0, 2);
/**
* Whether screensavers are enabled.
@@ -7871,7 +7878,7 @@
public static final String SCREENSAVER_COMPONENTS = "screensaver_components";
private static final Validator SCREENSAVER_COMPONENTS_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(",");
+ new ComponentNameListValidator(",");
/**
* If screensavers are enabled, whether the screensaver should be automatically launched
@@ -8023,7 +8030,7 @@
"enabled_notification_assistant";
private static final Validator ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(":");
+ new ComponentNameListValidator(":");
/**
* Read only list of the service components that the current user has explicitly allowed to
@@ -8038,7 +8045,7 @@
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
private static final Validator ENABLED_NOTIFICATION_LISTENERS_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(":");
+ new ComponentNameListValidator(":");
/**
* Read only list of the packages that the current user has explicitly allowed to
@@ -8053,7 +8060,7 @@
"enabled_notification_policy_access_packages";
private static final Validator ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR =
- new SettingsValidators.PackageNameListValidator(":");
+ new PackageNameListValidator(":");
/**
* Defines whether managed profile ringtones should be synced from it's parent profile
@@ -8350,16 +8357,6 @@
BOOLEAN_VALIDATOR;
/**
- * Whether or not the face unlock education screen has been shown to the user.
- * @hide
- */
- public static final String FACE_UNLOCK_EDUCATION_INFO_DISPLAYED =
- "face_unlock_education_info_displayed";
-
- private static final Validator FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR =
- BOOLEAN_VALIDATOR;
-
- /**
* Whether or not debugging is enabled.
* @hide
*/
@@ -8446,7 +8443,7 @@
public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
+ new InclusiveIntegerRangeValidator(0, 2);
/**
* Control the color temperature of Night Display, represented in Kelvin.
@@ -8507,7 +8504,7 @@
public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
private static final Validator ENABLED_VR_LISTENERS_VALIDATOR =
- new SettingsValidators.ComponentNameListValidator(":");
+ new ComponentNameListValidator(":");
/**
* Behavior of the display while in VR mode.
@@ -8519,7 +8516,7 @@
public static final String VR_DISPLAY_MODE = "vr_display_mode";
private static final Validator VR_DISPLAY_MODE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1"});
+ new DiscreteValueValidator(new String[]{"0", "1"});
/**
* Lower the display persistence while the system is in VR mode.
@@ -8638,8 +8635,7 @@
*/
public static final String QS_TILES = "sysui_qs_tiles";
- private static final Validator QS_TILES_VALIDATOR =
- new SettingsValidators.TileListValidator();
+ private static final Validator QS_TILES_VALIDATOR = TILE_LIST_VALIDATOR;
/**
* Specifies whether the web action API is enabled.
@@ -8705,8 +8701,7 @@
*/
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
- private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR =
- new SettingsValidators.TileListValidator();
+ private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR = TILE_LIST_VALIDATOR;
/**
* Whether the Lockdown button should be shown in the power menu.
@@ -8867,7 +8862,7 @@
"theme_customization_overlay_packages";
private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
- SettingsValidators.JSON_OBJECT_VALIDATOR;
+ JSON_OBJECT_VALIDATOR;
/**
* Navigation bar mode.
@@ -8879,7 +8874,7 @@
public static final String NAVIGATION_MODE =
"navigation_mode";
private static final Validator NAVIGATION_MODE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"});
+ new DiscreteValueValidator(new String[] {"0", "1", "2"});
/**
* Controls whether aware is enabled.
@@ -9182,8 +9177,6 @@
VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
- VALIDATORS.put(FACE_UNLOCK_EDUCATION_INFO_DISPLAYED,
- FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
@@ -10583,22 +10576,6 @@
BOOLEAN_VALIDATOR;
/**
- * Whether to notify the user of carrier networks.
- * <p>
- * If not connected and the scan results have a carrier network, we will
- * put this notification up. If we attempt to connect to a network or
- * the carrier network(s) disappear, we remove the notification. When we
- * show the notification, we will not show it again for
- * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
- * @hide
- */
- public static final String WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON =
- "wifi_carrier_networks_available_notification_on";
-
- private static final Validator WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
- BOOLEAN_VALIDATOR;
-
- /**
* {@hide}
*/
public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
@@ -10733,7 +10710,7 @@
"network_recommendations_enabled";
private static final Validator NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"-1", "0", "1"});
+ new DiscreteValueValidator(new String[] {"-1", "0", "1"});
/**
* Which package name to use for network recommendations. If null, network recommendations
@@ -11058,12 +11035,6 @@
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
- * The min time between wifi disable and wifi enable
- * @hide
- */
- public static final String WIFI_REENABLE_DELAY_MS = "wifi_reenable_delay";
-
- /**
* Timeout for ephemeral networks when all known BSSIDs go out of range. We will disconnect
* from an ephemeral network if there is no BSSID for that network with a non-null score that
* has been seen in this time period.
@@ -12590,7 +12561,7 @@
public static final String EMERGENCY_TONE = "emergency_tone";
private static final Validator EMERGENCY_TONE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"});
+ new DiscreteValueValidator(new String[] {"0", "1", "2"});
/**
* CDMA only settings
@@ -12620,7 +12591,7 @@
"enable_automatic_system_server_heap_dumps";
private static final Validator ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+ new DiscreteValueValidator(new String[] {"0", "1"});
/**
* See RIL_PreferredNetworkType in ril.h
@@ -12814,7 +12785,7 @@
"low_power_sticky_auto_disable_level";
private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+ new InclusiveIntegerRangeValidator(0, 100);
/**
* Whether sticky battery saver should be deactivated once the battery level has reached the
@@ -12826,7 +12797,7 @@
"low_power_sticky_auto_disable_enabled";
private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+ new DiscreteValueValidator(new String[] {"0", "1"});
/**
* Battery level [1-100] at which low power mode automatically turns on.
@@ -12841,7 +12812,7 @@
public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+ new InclusiveIntegerRangeValidator(0, 100);
/**
* Whether battery saver is currently set to trigger based on percentage, dynamic power
@@ -12854,7 +12825,7 @@
public static final String AUTOMATIC_POWER_SAVE_MODE = "automatic_power_save_mode";
private static final Validator AUTOMATIC_POWER_SAVE_MODE_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+ new DiscreteValueValidator(new String[] {"0", "1"});
/**
* The setting that backs the disable threshold for the setPowerSavingsWarning api in
@@ -12867,7 +12838,7 @@
public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD =
"dynamic_power_savings_disable_threshold";
private static final Validator DYNAMIC_POWER_SAVINGS_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+ new InclusiveIntegerRangeValidator(0, 100);
/**
* The setting which backs the setDynamicPowerSaveHint api in PowerManager.
@@ -13017,7 +12988,7 @@
public static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output";
private static final Validator ENCODED_SURROUND_OUTPUT_VALIDATOR =
- new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2", "3"});
+ new DiscreteValueValidator(new String[] {"0", "1", "2", "3"});
/**
* Surround sounds formats that are enabled when ENCODED_SURROUND_OUTPUT is set to
@@ -13768,7 +13739,7 @@
public static final String POWER_BUTTON_LONG_PRESS =
"power_button_long_press";
private static final Validator POWER_BUTTON_LONG_PRESS_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+ new InclusiveIntegerRangeValidator(0, 5);
/**
* Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
@@ -13779,7 +13750,7 @@
public static final String POWER_BUTTON_VERY_LONG_PRESS =
"power_button_very_long_press";
private static final Validator POWER_BUTTON_VERY_LONG_PRESS_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(0, 1);
+ new InclusiveIntegerRangeValidator(0, 1);
/**
* Settings to backup. This is here so that it's in the same place as the settings
@@ -13814,7 +13785,6 @@
NETWORK_RECOMMENDATIONS_ENABLED,
WIFI_WAKEUP_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
USE_OPEN_WIFI_PACKAGE,
WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
EMERGENCY_TONE,
@@ -13883,8 +13853,6 @@
VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
- VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
VALIDATORS.put(WIFI_SCAN_THROTTLE_ENABLED, WIFI_SCAN_THROTTLE_ENABLED_VALIDATOR);
VALIDATORS.put(APP_AUTO_RESTRICTION_ENABLED, APP_AUTO_RESTRICTION_ENABLED_VALIDATOR);
VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR);
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
deleted file mode 100644
index e4cf9fd..0000000
--- a/core/java/android/provider/SettingsValidators.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.internal.util.ArrayUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Locale;
-
-/**
- * This class provides both interface for validation and common validators
- * used to ensure Settings have meaningful values.
- *
- * @hide
- */
-public class SettingsValidators {
-
- public static final Validator BOOLEAN_VALIDATOR =
- new DiscreteValueValidator(new String[] {"0", "1"});
-
- public static final Validator ANY_STRING_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- return true;
- }
- };
-
- public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- try {
- return Integer.parseInt(value) >= 0;
- } catch (NumberFormatException e) {
- return false;
- }
- }
- };
-
- public static final Validator ANY_INTEGER_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- try {
- Integer.parseInt(value);
- return true;
- } catch (NumberFormatException e) {
- return false;
- }
- }
- };
-
- public static final Validator URI_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- try {
- Uri.decode(value);
- return true;
- } catch (IllegalArgumentException e) {
- return false;
- }
- }
- };
-
- /**
- * Does not allow a setting to have a null {@link ComponentName}. Use {@link
- * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a
- * nullable {@link ComponentName}.
- */
- public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- return value != null && ComponentName.unflattenFromString(value) != null;
- }
- };
-
- /**
- * Allows a setting to have a null {@link ComponentName}.
- */
- public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- return value == null || COMPONENT_NAME_VALIDATOR.validate(value);
- }
- };
-
- public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- return value != null && isStringPackageName(value);
- }
-
- private boolean isStringPackageName(String value) {
- // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
- // and underscores ('_'). However, individual package name parts may only
- // start with letters.
- // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
- if (value == null) {
- return false;
- }
- String[] subparts = value.split("\\.");
- boolean isValidPackageName = true;
- for (String subpart : subparts) {
- isValidPackageName &= isSubpartValidForPackageName(subpart);
- if (!isValidPackageName) break;
- }
- return isValidPackageName;
- }
-
- private boolean isSubpartValidForPackageName(String subpart) {
- if (subpart.length() == 0) return false;
- boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
- for (int i = 1; i < subpart.length(); i++) {
- isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
- || (subpart.charAt(i) == '_'));
- if (!isValidSubpart) break;
- }
- return isValidSubpart;
- }
- };
-
- public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
- private static final int MAX_IPV6_LENGTH = 45;
-
- @Override
- public boolean validate(@Nullable String value) {
- if (value == null) {
- return false;
- }
- return value.length() <= MAX_IPV6_LENGTH;
- }
- };
-
- public static final Validator LOCALE_VALIDATOR = new Validator() {
- @Override
- public boolean validate(@Nullable String value) {
- if (value == null) {
- return false;
- }
- Locale[] validLocales = Locale.getAvailableLocales();
- for (Locale locale : validLocales) {
- if (value.equals(locale.toString())) {
- return true;
- }
- }
- return false;
- }
- };
-
- /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */
- public static final Validator JSON_OBJECT_VALIDATOR = (value) -> {
- if (TextUtils.isEmpty(value)) {
- return false;
- }
- try {
- new JSONObject(value);
- return true;
- } catch (JSONException e) {
- return false;
- }
- };
-
- public interface Validator {
- /**
- * Returns whether the input value is valid. Subclasses should handle the case where the
- * input value is {@code null}.
- */
- boolean validate(@Nullable String value);
- }
-
- public static final class DiscreteValueValidator implements Validator {
- private final String[] mValues;
-
- public DiscreteValueValidator(String[] values) {
- mValues = values;
- }
-
- @Override
- public boolean validate(@Nullable String value) {
- return ArrayUtils.contains(mValues, value);
- }
- }
-
- public static final class InclusiveIntegerRangeValidator implements Validator {
- private final int mMin;
- private final int mMax;
-
- public InclusiveIntegerRangeValidator(int min, int max) {
- mMin = min;
- mMax = max;
- }
-
- @Override
- public boolean validate(@Nullable String value) {
- try {
- final int intValue = Integer.parseInt(value);
- return intValue >= mMin && intValue <= mMax;
- } catch (NumberFormatException e) {
- return false;
- }
- }
- }
-
- public static final class InclusiveFloatRangeValidator implements Validator {
- private final float mMin;
- private final float mMax;
-
- public InclusiveFloatRangeValidator(float min, float max) {
- mMin = min;
- mMax = max;
- }
-
- @Override
- public boolean validate(@Nullable String value) {
- try {
- final float floatValue = Float.parseFloat(value);
- return floatValue >= mMin && floatValue <= mMax;
- } catch (NumberFormatException | NullPointerException e) {
- return false;
- }
- }
- }
-
- public static final class ComponentNameListValidator implements Validator {
- private final String mSeparator;
-
- public ComponentNameListValidator(String separator) {
- mSeparator = separator;
- }
-
- @Override
- public boolean validate(@Nullable String value) {
- if (value == null) {
- return false;
- }
- String[] elements = value.split(mSeparator);
- for (String element : elements) {
- if (!COMPONENT_NAME_VALIDATOR.validate(element)) {
- return false;
- }
- }
- return true;
- }
- }
-
- public static final class PackageNameListValidator implements Validator {
- private final String mSeparator;
-
- public PackageNameListValidator(String separator) {
- mSeparator = separator;
- }
-
- @Override
- public boolean validate(@Nullable String value) {
- if (value == null) {
- return false;
- }
- String[] elements = value.split(mSeparator);
- for (String element : elements) {
- if (!PACKAGE_NAME_VALIDATOR.validate(element)) {
- return false;
- }
- }
- return true;
- }
- }
-
- private abstract static class ListValidator implements Validator {
- public boolean validate(@Nullable String value) {
- if (!isEntryValid(value)) {
- return false;
- }
- String[] items = value.split(",");
- for (String item : items) {
- if (!isItemValid(item)) {
- return false;
- }
- }
- return true;
- }
-
- protected abstract boolean isEntryValid(String entry);
-
- protected abstract boolean isItemValid(String item);
- }
-
- /** Ensure a restored value is a string in the format the text-to-speech system handles */
- public static final class TTSListValidator extends ListValidator {
- protected boolean isEntryValid(String entry) {
- return entry != null && entry.length() > 0;
- }
-
- protected boolean isItemValid(String item) {
- String[] parts = item.split(":");
- // Replaces any old language separator (-) with the new one (_)
- return ((parts.length == 2)
- && (parts[0].length() > 0)
- && ANY_STRING_VALIDATOR.validate(parts[0])
- && LOCALE_VALIDATOR.validate(parts[1].replace('-', '_')));
- }
- }
-
- /** Ensure a restored value is suitable to be used as a tile name */
- public static final class TileListValidator extends ListValidator {
- protected boolean isEntryValid(String entry) {
- return entry != null;
- }
-
- protected boolean isItemValid(String item) {
- return item.length() > 0 && ANY_STRING_VALIDATOR.validate(item);
- }
- }
-}
diff --git a/core/java/android/provider/settings/validators/ComponentNameListValidator.java b/core/java/android/provider/settings/validators/ComponentNameListValidator.java
new file mode 100644
index 0000000..b6b867a
--- /dev/null
+++ b/core/java/android/provider/settings/validators/ComponentNameListValidator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+
+/**
+ * Validate a list of compoments.
+ *
+ * @hide
+ */
+public final class ComponentNameListValidator extends ListValidator {
+ public ComponentNameListValidator(String separator) {
+ super(separator);
+ }
+
+ @Override
+ protected boolean isEntryValid(String entry) {
+ return entry != null;
+ }
+
+ @Override
+ protected boolean isItemValid(String item) {
+ return COMPONENT_NAME_VALIDATOR.validate(item);
+ }
+}
diff --git a/core/java/android/provider/settings/validators/DiscreteValueValidator.java b/core/java/android/provider/settings/validators/DiscreteValueValidator.java
new file mode 100644
index 0000000..183651f
--- /dev/null
+++ b/core/java/android/provider/settings/validators/DiscreteValueValidator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Validate a value exists in an array of known good values
+ *
+ * @hide
+ */
+public final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ @Override
+ public boolean validate(@Nullable String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+}
diff --git a/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java
new file mode 100644
index 0000000..38400ac
--- /dev/null
+++ b/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Validate a float value lies within a given (boundary inclusive) range.
+ *
+ * @hide
+ */
+public final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(@Nullable String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException | NullPointerException e) {
+ return false;
+ }
+ }
+}
diff --git a/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
new file mode 100644
index 0000000..e53c252
--- /dev/null
+++ b/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Validate an integer value lies within a given (boundary inclusive) range.
+ *
+ * @hide
+ */
+public final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(@Nullable String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
diff --git a/core/java/android/provider/settings/validators/ListValidator.java b/core/java/android/provider/settings/validators/ListValidator.java
new file mode 100644
index 0000000..a6001d2
--- /dev/null
+++ b/core/java/android/provider/settings/validators/ListValidator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Validate the elements in a list.
+ *
+ * @hide
+ */
+abstract class ListValidator implements Validator {
+
+ private String mListSplitRegex;
+
+ ListValidator(String listSplitRegex) {
+ mListSplitRegex = listSplitRegex;
+ }
+
+ public boolean validate(@Nullable String value) {
+ if (!isEntryValid(value)) {
+ return false;
+ }
+ String[] items = value.split(",");
+ for (String item : items) {
+ if (!isItemValid(item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected abstract boolean isEntryValid(String entry);
+
+ protected abstract boolean isItemValid(String item);
+}
+
diff --git a/core/java/android/provider/settings/validators/PackageNameListValidator.java b/core/java/android/provider/settings/validators/PackageNameListValidator.java
new file mode 100644
index 0000000..bc7fc13
--- /dev/null
+++ b/core/java/android/provider/settings/validators/PackageNameListValidator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+
+/**
+ * Validate a list of package names.
+ *
+ * @hide
+ */
+public final class PackageNameListValidator extends ListValidator {
+ public PackageNameListValidator(String separator) {
+ super(separator);
+ }
+
+ @Override
+ protected boolean isEntryValid(String entry) {
+ return entry != null;
+ }
+
+ @Override
+ protected boolean isItemValid(String item) {
+ return PACKAGE_NAME_VALIDATOR.validate(item);
+ }
+}
diff --git a/core/java/android/provider/settings/validators/SettingsValidators.java b/core/java/android/provider/settings/validators/SettingsValidators.java
new file mode 100644
index 0000000..562c638
--- /dev/null
+++ b/core/java/android/provider/settings/validators/SettingsValidators.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Locale;
+
+/**
+ * This class provides both interface for validation and common validators
+ * used to ensure Settings have meaningful values.
+ *
+ * @hide
+ */
+public class SettingsValidators {
+
+ public static final Validator BOOLEAN_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ public static final Validator ANY_STRING_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ return true;
+ }
+ };
+
+ public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ public static final Validator ANY_INTEGER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ try {
+ Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ public static final Validator URI_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ /**
+ * Does not allow a setting to have a null {@link ComponentName}. Use {@link
+ * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a
+ * nullable {@link ComponentName}.
+ */
+ public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ return value != null && ComponentName.unflattenFromString(value) != null;
+ }
+ };
+
+ /**
+ * Allows a setting to have a null {@link ComponentName}.
+ */
+ public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ return value == null || COMPONENT_NAME_VALIDATOR.validate(value);
+ }
+ };
+
+ public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ return value != null && isStringPackageName(value);
+ }
+
+ private boolean isStringPackageName(String value) {
+ // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
+ // and underscores ('_'). However, individual package name parts may only
+ // start with letters.
+ // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
+ if (value == null) {
+ return false;
+ }
+ String[] subparts = value.split("\\.");
+ boolean isValidPackageName = true;
+ for (String subpart : subparts) {
+ isValidPackageName &= isSubpartValidForPackageName(subpart);
+ if (!isValidPackageName) break;
+ }
+ return isValidPackageName;
+ }
+
+ private boolean isSubpartValidForPackageName(String subpart) {
+ if (subpart.length() == 0) return false;
+ boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
+ for (int i = 1; i < subpart.length(); i++) {
+ isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
+ || (subpart.charAt(i) == '_'));
+ if (!isValidSubpart) break;
+ }
+ return isValidSubpart;
+ }
+ };
+
+ public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
+ private static final int MAX_IPV6_LENGTH = 45;
+
+ @Override
+ public boolean validate(@Nullable String value) {
+ if (value == null) {
+ return false;
+ }
+ return value.length() <= MAX_IPV6_LENGTH;
+ }
+ };
+
+ public static final Validator LOCALE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ if (value == null) {
+ return false;
+ }
+ Locale[] validLocales = Locale.getAvailableLocales();
+ for (Locale locale : validLocales) {
+ if (value.equals(locale.toString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */
+ public static final Validator JSON_OBJECT_VALIDATOR = (value) -> {
+ if (TextUtils.isEmpty(value)) {
+ return false;
+ }
+ try {
+ new JSONObject(value);
+ return true;
+ } catch (JSONException e) {
+ return false;
+ }
+ };
+
+ public static final Validator TTS_LIST_VALIDATOR = new TTSListValidator();
+
+ public static final Validator TILE_LIST_VALIDATOR = new TileListValidator();
+}
diff --git a/core/java/android/provider/settings/validators/TTSListValidator.java b/core/java/android/provider/settings/validators/TTSListValidator.java
new file mode 100644
index 0000000..6c73471
--- /dev/null
+++ b/core/java/android/provider/settings/validators/TTSListValidator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
+
+/**
+ * Ensure a restored value is a string in the format the text-to-speech system handles
+ *
+ * @hide
+ */
+final class TTSListValidator extends ListValidator {
+
+ TTSListValidator() {
+ super(",");
+ }
+
+ protected boolean isEntryValid(String entry) {
+ return entry != null && entry.length() > 0;
+ }
+
+ protected boolean isItemValid(String item) {
+ String[] parts = item.split(":");
+ // Replaces any old language separator (-) with the new one (_)
+ return ((parts.length == 2)
+ && (parts[0].length() > 0)
+ && ANY_STRING_VALIDATOR.validate(parts[0])
+ && LOCALE_VALIDATOR.validate(parts[1].replace('-', '_')));
+ }
+}
diff --git a/core/java/android/provider/settings/validators/TileListValidator.java b/core/java/android/provider/settings/validators/TileListValidator.java
new file mode 100644
index 0000000..c696442
--- /dev/null
+++ b/core/java/android/provider/settings/validators/TileListValidator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+
+/**
+ * Ensure a restored value is suitable to be used as a tile name
+ *
+ * @hide
+ */
+final class TileListValidator extends ListValidator {
+ TileListValidator() {
+ super(",");
+ }
+
+ protected boolean isEntryValid(String entry) {
+ return entry != null;
+ }
+
+ protected boolean isItemValid(String item) {
+ return item.length() > 0 && ANY_STRING_VALIDATOR.validate(item);
+ }
+}
diff --git a/core/java/android/provider/settings/validators/Validator.java b/core/java/android/provider/settings/validators/Validator.java
new file mode 100644
index 0000000..393a03d
--- /dev/null
+++ b/core/java/android/provider/settings/validators/Validator.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Interface for a settings value validator.
+ *
+ * @hide
+ */
+public interface Validator {
+ /**
+ * Returns whether the input value is valid. Subclasses should handle the case where the
+ * input value is {@code null}.
+ */
+ boolean validate(@Nullable String value);
+}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 905c781..205df7e 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -17,6 +17,7 @@
package android.service.notification;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Notification;
import android.app.NotificationManager;
@@ -172,6 +173,7 @@
*
* @hide
*/
+ @SystemApi
public boolean isAppGroup() {
if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
return true;
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
new file mode 100644
index 0000000..cc8116d0
--- /dev/null
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 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.service.storage;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service to handle filesystem I/O from other apps.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE} permission,
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action.
+ * For example:</p>
+ * <pre>
+ * <service android:name=".ExternalStorageServiceImpl"
+ * android:exported="true"
+ * android:priority="100"
+ * android:permission="android.permission.BIND_EXTERNAL_STORAGE_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.storage.ExternalStorageService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ * @hide
+ */
+@SystemApi
+public abstract class ExternalStorageService extends Service {
+ /**
+ * The Intent action that a service must respond to. Add it as an intent filter in the
+ * manifest declaration of the implementing service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService";
+ /**
+ * Whether the session associated with the device file descriptor when calling
+ * {@link #onStartSession} is a FUSE session.
+ */
+ public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0;
+
+ /**
+ * Whether the upper file system path specified when calling {@link #onStartSession}
+ * should be indexed.
+ */
+ public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1;
+
+ /**
+ * {@link Bundle} key for a {@link String} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_SESSION_ID =
+ "android.service.storage.extra.session_id";
+ /**
+ * {@link Bundle} key for a {@link ParcelableException} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_ERROR =
+ "android.service.storage.extra.error";
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_SESSION_"},
+ value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SessionFlag {}
+
+ private final ExternalStorageServiceWrapper mWrapper = new ExternalStorageServiceWrapper();
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+ /**
+ * Called when the system starts a session associated with {@code deviceFd}
+ * identified by {@code sessionId} to handle filesystem I/O for other apps. The type of
+ * session and other attributes are passed in {@code flag}.
+ *
+ * <p> I/O is received as requests originating from {@code upperFileSystemPath} on
+ * {@code deviceFd}. Implementors should handle the I/O by responding to these requests
+ * using the data on the {@code lowerFileSystemPath}.
+ *
+ * <p> Additional calls to start a session for the same {@code sessionId} while the session
+ * is still starting or already started should have no effect.
+ */
+ public abstract void onStartSession(@NonNull String sessionId, @SessionFlag int flag,
+ @NonNull ParcelFileDescriptor deviceFd, @NonNull String upperFileSystemPath,
+ @NonNull String lowerFileSystemPath) throws IOException;
+
+ /**
+ * Called when the system ends the session identified by {@code sessionId}. Implementors should
+ * stop handling filesystem I/O and clean up resources from the ended session.
+ *
+ * <p> Additional calls to end a session for the same {@code sessionId} while the session
+ * is still ending or has not started should have no effect.
+ */
+ public abstract void onEndSession(@NonNull String sessionId) throws IOException;
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ return mWrapper;
+ }
+
+ private class ExternalStorageServiceWrapper extends IExternalStorageService.Stub {
+ @Override
+ public void startSession(String sessionId, @SessionFlag int flag,
+ ParcelFileDescriptor deviceFd, String upperPath, String lowerPath,
+ RemoteCallback callback) throws RemoteException {
+ mHandler.post(() -> {
+ try {
+ onStartSession(sessionId, flag, deviceFd, upperPath, lowerPath);
+ sendResult(sessionId, null /* throwable */, callback);
+ } catch (Throwable t) {
+ sendResult(sessionId, t, callback);
+ }
+ });
+ }
+
+ @Override
+ public void endSession(String sessionId, RemoteCallback callback) throws RemoteException {
+ mHandler.post(() -> {
+ try {
+ onEndSession(sessionId);
+ sendResult(sessionId, null /* throwable */, callback);
+ } catch (Throwable t) {
+ sendResult(sessionId, t, callback);
+ }
+ });
+ }
+
+ private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_SESSION_ID, sessionId);
+ if (throwable != null) {
+ bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+ }
+ callback.sendResult(bundle);
+ }
+ }
+}
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
new file mode 100644
index 0000000..ae46f1f
--- /dev/null
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.service.storage;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+oneway interface IExternalStorageService
+{
+ void startSession(@utf8InCpp String sessionId, int type, in ParcelFileDescriptor deviceFd,
+ @utf8InCpp String upperPath, @utf8InCpp String lowerPath, in RemoteCallback callback);
+ void endSession(@utf8InCpp String sessionId, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 30c4e90..9108c33 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -460,6 +460,7 @@
return;
}
try {
+ Slog.w(LOG_TAG, "Request fail: " + error);
callback.onFailure();
} catch (RemoteException e) {
Slog.d(LOG_TAG, "Error calling callback");
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 100774c..44446ad0 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -16,6 +16,7 @@
package android.speech.tts;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RawRes;
import android.annotation.SdkConstant;
@@ -858,23 +859,20 @@
}
// Post connection case
- runActionNoReconnect(new Action<Void>() {
- @Override
- public Void run(ITextToSpeechService service) throws RemoteException {
- service.setCallback(getCallerIdentity(), null);
- service.stop(getCallerIdentity());
- mServiceConnection.disconnect();
- // Context#unbindService does not result in a call to
- // ServiceConnection#onServiceDisconnected. As a result, the
- // service ends up being destroyed (if there are no other open
- // connections to it) but the process lives on and the
- // ServiceConnection continues to refer to the destroyed service.
- //
- // This leads to tons of log spam about SynthThread being dead.
- mServiceConnection = null;
- mCurrentEngine = null;
- return null;
- }
+ runActionNoReconnect((ITextToSpeechService service) -> {
+ service.setCallback(getCallerIdentity(), null);
+ service.stop(getCallerIdentity());
+ mServiceConnection.disconnect();
+ // Context#unbindService does not result in a call to
+ // ServiceConnection#onServiceDisconnected. As a result, the
+ // service ends up being destroyed (if there are no other open
+ // connections to it) but the process lives on and the
+ // ServiceConnection continues to refer to the destroyed service.
+ //
+ // This leads to tons of log spam about SynthThread being dead.
+ mServiceConnection = null;
+ mCurrentEngine = null;
+ return null;
}, null, "shutdown", false);
}
@@ -1105,17 +1103,14 @@
final int queueMode,
final Bundle params,
final String utteranceId) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- Uri utteranceUri = mUtterances.get(text);
- if (utteranceUri != null) {
- return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
- getParams(params), utteranceId);
- } else {
- return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
- utteranceId);
- }
+ return runAction((ITextToSpeechService service) -> {
+ Uri utteranceUri = mUtterances.get(text);
+ if (utteranceUri != null) {
+ return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
+ getParams(params), utteranceId);
+ } else {
+ return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
+ utteranceId);
}
}, ERROR, "speak");
}
@@ -1178,16 +1173,13 @@
*/
public int playEarcon(final String earcon, final int queueMode,
final Bundle params, final String utteranceId) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- Uri earconUri = mEarcons.get(earcon);
- if (earconUri == null) {
- return ERROR;
- }
- return service.playAudio(getCallerIdentity(), earconUri, queueMode,
- getParams(params), utteranceId);
+ return runAction((ITextToSpeechService service) -> {
+ Uri earconUri = mEarcons.get(earcon);
+ if (earconUri == null) {
+ return ERROR;
}
+ return service.playAudio(getCallerIdentity(), earconUri, queueMode,
+ getParams(params), utteranceId);
}, ERROR, "playEarcon");
}
@@ -1242,12 +1234,9 @@
*/
public int playSilentUtterance(final long durationInMs, final int queueMode,
final String utteranceId) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- return service.playSilence(getCallerIdentity(), durationInMs,
- queueMode, utteranceId);
- }
+ return runAction((ITextToSpeechService service) -> {
+ return service.playSilence(getCallerIdentity(), durationInMs,
+ queueMode, utteranceId);
}, ERROR, "playSilentUtterance");
}
@@ -1302,26 +1291,23 @@
*/
@Deprecated
public Set<String> getFeatures(final Locale locale) {
- return runAction(new Action<Set<String>>() {
- @Override
- public Set<String> run(ITextToSpeechService service) throws RemoteException {
- String[] features = null;
- try {
- features = service.getFeaturesForLanguage(
- locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
- } catch(MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " +
- "country code for locale: " + locale, e);
- return null;
- }
-
- if (features != null) {
- final Set<String> featureSet = new HashSet<String>();
- Collections.addAll(featureSet, features);
- return featureSet;
- }
+ return runAction((ITextToSpeechService service) -> {
+ String[] features = null;
+ try {
+ features = service.getFeaturesForLanguage(
+ locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 "
+ + "country code for locale: " + locale, e);
return null;
}
+
+ if (features != null) {
+ final Set<String> featureSet = new HashSet<String>();
+ Collections.addAll(featureSet, features);
+ return featureSet;
+ }
+ return null;
}, null, "getFeatures");
}
@@ -1334,11 +1320,8 @@
* @return {@code true} if the TTS engine is speaking.
*/
public boolean isSpeaking() {
- return runAction(new Action<Boolean>() {
- @Override
- public Boolean run(ITextToSpeechService service) throws RemoteException {
- return service.isSpeaking();
- }
+ return runAction((ITextToSpeechService service) -> {
+ return service.isSpeaking();
}, false, "isSpeaking");
}
@@ -1349,11 +1332,8 @@
* @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int stop() {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- return service.stop(getCallerIdentity());
- }
+ return runAction((ITextToSpeechService service) -> {
+ return service.stop(getCallerIdentity());
}, ERROR, "stop");
}
@@ -1447,13 +1427,10 @@
*/
@Deprecated
public Locale getDefaultLanguage() {
- return runAction(new Action<Locale>() {
- @Override
- public Locale run(ITextToSpeechService service) throws RemoteException {
- String[] defaultLanguage = service.getClientDefaultLanguage();
+ return runAction((ITextToSpeechService service) -> {
+ String[] defaultLanguage = service.getClientDefaultLanguage();
- return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
- }
+ return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
}, null, "getDefaultLanguage");
}
@@ -1474,83 +1451,80 @@
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
public int setLanguage(final Locale loc) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- if (loc == null) {
- return LANG_NOT_SUPPORTED;
- }
- String language = null, country = null;
- try {
- language = loc.getISO3Language();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
- return LANG_NOT_SUPPORTED;
- }
-
- try {
- country = loc.getISO3Country();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
- return LANG_NOT_SUPPORTED;
- }
-
- String variant = loc.getVariant();
-
- // As of API level 21, setLanguage is implemented using setVoice.
- // (which, in the default implementation, will call loadLanguage on the service
- // interface).
-
- // Sanitize locale using isLanguageAvailable.
- int result = service.isLanguageAvailable(language, country, variant);
- if (result >= LANG_AVAILABLE) {
- // Get the default voice for the locale.
- String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
- if (TextUtils.isEmpty(voiceName)) {
- Log.w(TAG, "Couldn't find the default voice for " + language + "-" +
- country + "-" + variant);
- return LANG_NOT_SUPPORTED;
- }
-
- // Load it.
- if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) {
- Log.w(TAG, "The service claimed " + language + "-" + country + "-"
- + variant + " was available with voice name " + voiceName
- + " but loadVoice returned ERROR");
- return LANG_NOT_SUPPORTED;
- }
-
- // Set the language/country/variant of the voice, so #getLanguage will return
- // the currently set voice locale when called.
- Voice voice = getVoice(service, voiceName);
- if (voice == null) {
- Log.w(TAG, "getDefaultVoiceNameFor returned " + voiceName + " for locale "
- + language + "-" + country + "-" + variant
- + " but getVoice returns null");
- return LANG_NOT_SUPPORTED;
- }
- String voiceLanguage = "";
- try {
- voiceLanguage = voice.getLocale().getISO3Language();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
- voice.getLocale(), e);
- }
-
- String voiceCountry = "";
- try {
- voiceCountry = voice.getLocale().getISO3Country();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
- voice.getLocale(), e);
- }
- mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName);
- mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage);
- mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry);
- mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
- }
- return result;
+ return runAction((ITextToSpeechService service) -> {
+ if (loc == null) {
+ return LANG_NOT_SUPPORTED;
}
+ String language = null, country = null;
+ try {
+ language = loc.getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ try {
+ country = loc.getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ String variant = loc.getVariant();
+
+ // As of API level 21, setLanguage is implemented using setVoice.
+ // (which, in the default implementation, will call loadLanguage on the service
+ // interface).
+
+ // Sanitize locale using isLanguageAvailable.
+ int result = service.isLanguageAvailable(language, country, variant);
+ if (result >= LANG_AVAILABLE) {
+ // Get the default voice for the locale.
+ String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
+ if (TextUtils.isEmpty(voiceName)) {
+ Log.w(TAG, "Couldn't find the default voice for " + language + "-"
+ + country + "-" + variant);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ // Load it.
+ if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) {
+ Log.w(TAG, "The service claimed " + language + "-" + country + "-"
+ + variant + " was available with voice name " + voiceName
+ + " but loadVoice returned ERROR");
+ return LANG_NOT_SUPPORTED;
+ }
+
+ // Set the language/country/variant of the voice, so #getLanguage will return
+ // the currently set voice locale when called.
+ Voice voice = getVoice(service, voiceName);
+ if (voice == null) {
+ Log.w(TAG, "getDefaultVoiceNameFor returned " + voiceName + " for locale "
+ + language + "-" + country + "-" + variant
+ + " but getVoice returns null");
+ return LANG_NOT_SUPPORTED;
+ }
+ String voiceLanguage = "";
+ try {
+ voiceLanguage = voice.getLocale().getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: "
+ + voice.getLocale(), e);
+ }
+
+ String voiceCountry = "";
+ try {
+ voiceCountry = voice.getLocale().getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: "
+ + voice.getLocale(), e);
+ }
+ mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName);
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
+ }
+ return result;
}, LANG_NOT_SUPPORTED, "setLanguage");
}
@@ -1582,16 +1556,13 @@
*/
@Deprecated
public Locale getLanguage() {
- return runAction(new Action<Locale>() {
- @Override
- public Locale run(ITextToSpeechService service) {
- /* No service call, but we're accessing mParams, hence need for
- wrapping it as an Action instance */
- String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
- String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
- String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
- return new Locale(lang, country, variant);
- }
+ return runAction((ITextToSpeechService service) -> {
+ /* No service call, but we're accessing mParams, hence need for
+ wrapping it as an Action instance */
+ String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
+ String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
+ String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
+ return new Locale(lang, country, variant);
}, null, "getLanguage");
}
@@ -1599,19 +1570,16 @@
* Query the engine about the set of available languages.
*/
public Set<Locale> getAvailableLanguages() {
- return runAction(new Action<Set<Locale>>() {
- @Override
- public Set<Locale> run(ITextToSpeechService service) throws RemoteException {
- List<Voice> voices = service.getVoices();
- if (voices == null) {
- return new HashSet<Locale>();
- }
- HashSet<Locale> locales = new HashSet<Locale>();
- for (Voice voice : voices) {
- locales.add(voice.getLocale());
- }
- return locales;
+ return runAction((ITextToSpeechService service) -> {
+ List<Voice> voices = service.getVoices();
+ if (voices == null) {
+ return new HashSet<Locale>();
}
+ HashSet<Locale> locales = new HashSet<Locale>();
+ for (Voice voice : voices) {
+ locales.add(voice.getLocale());
+ }
+ return locales;
}, null, "getAvailableLanguages");
}
@@ -1625,12 +1593,9 @@
* @see Voice
*/
public Set<Voice> getVoices() {
- return runAction(new Action<Set<Voice>>() {
- @Override
- public Set<Voice> run(ITextToSpeechService service) throws RemoteException {
- List<Voice> voices = service.getVoices();
- return (voices != null) ? new HashSet<Voice>(voices) : new HashSet<Voice>();
- }
+ return runAction((ITextToSpeechService service) -> {
+ List<Voice> voices = service.getVoices();
+ return (voices != null) ? new HashSet<Voice>(voices) : new HashSet<Voice>();
}, null, "getVoices");
}
@@ -1645,36 +1610,33 @@
* @see Voice
*/
public int setVoice(final Voice voice) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- int result = service.loadVoice(getCallerIdentity(), voice.getName());
- if (result == SUCCESS) {
- mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName());
+ return runAction((ITextToSpeechService service) -> {
+ int result = service.loadVoice(getCallerIdentity(), voice.getName());
+ if (result == SUCCESS) {
+ mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName());
- // Set the language/country/variant, so #getLanguage will return the voice
- // locale when called.
- String language = "";
- try {
- language = voice.getLocale().getISO3Language();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
- voice.getLocale(), e);
- }
-
- String country = "";
- try {
- country = voice.getLocale().getISO3Country();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
- voice.getLocale(), e);
- }
- mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
- mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
- mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
+ // Set the language/country/variant, so #getLanguage will return the voice
+ // locale when called.
+ String language = "";
+ try {
+ language = voice.getLocale().getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: "
+ + voice.getLocale(), e);
}
- return result;
+
+ String country = "";
+ try {
+ country = voice.getLocale().getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: "
+ + voice.getLocale(), e);
+ }
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
}
+ return result;
}, LANG_NOT_SUPPORTED, "setVoice");
}
@@ -1689,15 +1651,12 @@
* @see Voice
*/
public Voice getVoice() {
- return runAction(new Action<Voice>() {
- @Override
- public Voice run(ITextToSpeechService service) throws RemoteException {
- String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, "");
- if (TextUtils.isEmpty(voiceName)) {
- return null;
- }
- return getVoice(service, voiceName);
+ return runAction((ITextToSpeechService service) -> {
+ String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, "");
+ if (TextUtils.isEmpty(voiceName)) {
+ return null;
}
+ return getVoice(service, voiceName);
}, null, "getVoice");
}
@@ -1730,45 +1689,42 @@
* on error.
*/
public Voice getDefaultVoice() {
- return runAction(new Action<Voice>() {
- @Override
- public Voice run(ITextToSpeechService service) throws RemoteException {
+ return runAction((ITextToSpeechService service) -> {
- String[] defaultLanguage = service.getClientDefaultLanguage();
+ String[] defaultLanguage = service.getClientDefaultLanguage();
- if (defaultLanguage == null || defaultLanguage.length == 0) {
- Log.e(TAG, "service.getClientDefaultLanguage() returned empty array");
- return null;
- }
- String language = defaultLanguage[0];
- String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : "";
- String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : "";
-
- // Sanitize the locale using isLanguageAvailable.
- int result = service.isLanguageAvailable(language, country, variant);
- if (result < LANG_AVAILABLE) {
- // The default language is not supported.
- return null;
- }
-
- // Get the default voice name
- String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
- if (TextUtils.isEmpty(voiceName)) {
- return null;
- }
-
- // Find it
- List<Voice> voices = service.getVoices();
- if (voices == null) {
- return null;
- }
- for (Voice voice : voices) {
- if (voice.getName().equals(voiceName)) {
- return voice;
- }
- }
+ if (defaultLanguage == null || defaultLanguage.length == 0) {
+ Log.e(TAG, "service.getClientDefaultLanguage() returned empty array");
return null;
}
+ String language = defaultLanguage[0];
+ String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : "";
+ String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : "";
+
+ // Sanitize the locale using isLanguageAvailable.
+ int result = service.isLanguageAvailable(language, country, variant);
+ if (result < LANG_AVAILABLE) {
+ // The default language is not supported.
+ return null;
+ }
+
+ // Get the default voice name
+ String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
+ if (TextUtils.isEmpty(voiceName)) {
+ return null;
+ }
+
+ // Find it
+ List<Voice> voices = service.getVoices();
+ if (voices == null) {
+ return null;
+ }
+ for (Voice voice : voices) {
+ if (voice.getName().equals(voiceName)) {
+ return voice;
+ }
+ }
+ return null;
}, null, "getDefaultVoice");
}
@@ -1784,31 +1740,55 @@
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
public int isLanguageAvailable(final Locale loc) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- String language = null, country = null;
+ return runAction((ITextToSpeechService service) -> {
+ String language = null, country = null;
- try {
- language = loc.getISO3Language();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
- return LANG_NOT_SUPPORTED;
- }
-
- try {
- country = loc.getISO3Country();
- } catch (MissingResourceException e) {
- Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
- return LANG_NOT_SUPPORTED;
- }
-
- return service.isLanguageAvailable(language, country, loc.getVariant());
+ try {
+ language = loc.getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
}
+
+ try {
+ country = loc.getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ return service.isLanguageAvailable(language, country, loc.getVariant());
}, LANG_NOT_SUPPORTED, "isLanguageAvailable");
}
/**
+ * Synthesizes the given text to a ParcelFileDescriptor using the specified parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}).
+ *
+ * @param text The text that should be synthesized. No longer than
+ * {@link #getMaxSpeechInputLength()} characters.
+ * @param params Parameters for the request. Can be null.
+ * Engine specific parameters may be passed in but the parameter keys
+ * must be prefixed by the name of the engine they are intended for. For example
+ * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the engine
+ * named "com.svox.pico" if it is being used.
+ * @param fileDescriptor ParcelFileDescriptor to write the generated audio data to.
+ * @param utteranceId An unique identifier for this request.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
+ */
+ public int synthesizeToFile(@NonNull final CharSequence text, @NonNull final Bundle params,
+ @NonNull final ParcelFileDescriptor fileDescriptor, @NonNull final String utteranceId) {
+ return runAction((ITextToSpeechService service) -> {
+ return service.synthesizeToFileDescriptor(getCallerIdentity(), text,
+ fileDescriptor, getParams(params), utteranceId);
+ }, ERROR, "synthesizeToFile");
+ }
+
+ /**
* Synthesizes the given text to a file using the specified parameters.
* This method is asynchronous, i.e. the method just adds the request to the queue of TTS
* requests and then returns. The synthesis might not have finished (or even started!) at the
@@ -1829,33 +1809,26 @@
*/
public int synthesizeToFile(final CharSequence text, final Bundle params,
final File file, final String utteranceId) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- ParcelFileDescriptor fileDescriptor;
- int returnValue;
- try {
- if(file.exists() && !file.canWrite()) {
- Log.e(TAG, "Can't write to " + file);
- return ERROR;
- }
- fileDescriptor = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_WRITE_ONLY |
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE);
- returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
- fileDescriptor, getParams(params), utteranceId);
- fileDescriptor.close();
- return returnValue;
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Opening file " + file + " failed", e);
- return ERROR;
- } catch (IOException e) {
- Log.e(TAG, "Closing file " + file + " failed", e);
- return ERROR;
- }
- }
- }, ERROR, "synthesizeToFile");
+ if (file.exists() && !file.canWrite()) {
+ Log.e(TAG, "Can't write to " + file);
+ return ERROR;
+ }
+ try (
+ ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE);
+ ) {
+ int returnValue = synthesizeToFile(text, params, fileDescriptor, utteranceId);
+ fileDescriptor.close();
+ return returnValue;
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Opening file " + file + " failed", e);
+ return ERROR;
+ } catch (IOException e) {
+ Log.e(TAG, "Closing file " + file + " failed", e);
+ return ERROR;
+ }
}
/**
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index e94b800..b00a938 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -32,6 +32,9 @@
import libcore.icu.RelativeDateTimeFormatter;
import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.Formatter;
@@ -502,17 +505,21 @@
* @return true if the supplied when is today else false
*/
public static boolean isToday(long when) {
- Time time = new Time();
- time.set(when);
+ return isSameDate(when, System.currentTimeMillis());
+ }
- int thenYear = time.year;
- int thenMonth = time.month;
- int thenMonthDay = time.monthDay;
+ private static boolean isSameDate(long oneMillis, long twoMillis) {
+ ZoneId zoneId = ZoneId.systemDefault();
- time.set(System.currentTimeMillis());
- return (thenYear == time.year)
- && (thenMonth == time.month)
- && (thenMonthDay == time.monthDay);
+ Instant oneInstant = Instant.ofEpochMilli(oneMillis);
+ LocalDateTime oneLocalDateTime = LocalDateTime.ofInstant(oneInstant, zoneId);
+
+ Instant twoInstant = Instant.ofEpochMilli(twoMillis);
+ LocalDateTime twoLocalDateTime = LocalDateTime.ofInstant(twoInstant, zoneId);
+
+ return (oneLocalDateTime.getYear() == twoLocalDateTime.getYear())
+ && (oneLocalDateTime.getMonthValue() == twoLocalDateTime.getMonthValue())
+ && (oneLocalDateTime.getDayOfMonth() == twoLocalDateTime.getDayOfMonth());
}
/**
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index d899dde..3f44e48 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -48,6 +48,8 @@
* you have no control over this shrinking -- if you set a capacity and then remove an
* item, it may reduce the capacity to better match the current size. In the future an
* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
*/
public final class ArrayMap<K, V> implements Map<K, V> {
private static final boolean DEBUG = false;
@@ -103,15 +105,21 @@
static Object[] mTwiceBaseCache;
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
static int mTwiceBaseCacheSize;
+ /**
+ * Separate locks for each cache since each can be accessed independently of the other without
+ * risk of a deadlock.
+ */
+ private static final Object sBaseCacheLock = new Object();
+ private static final Object sTwiceBaseCacheLock = new Object();
- final boolean mIdentityHashCode;
+ private final boolean mIdentityHashCode;
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
int[] mHashes;
@UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
Object[] mArray;
@UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
int mSize;
- MapCollections<K, V> mCollections;
+ private MapCollections<K, V> mCollections;
private static int binarySearchHashes(int[] hashes, int N, int hash) {
try {
@@ -209,31 +217,57 @@
throw new UnsupportedOperationException("ArrayMap is immutable");
}
if (size == (BASE_SIZE*2)) {
- synchronized (ArrayMap.class) {
+ synchronized (sTwiceBaseCacheLock) {
if (mTwiceBaseCache != null) {
final Object[] array = mTwiceBaseCache;
mArray = array;
- mTwiceBaseCache = (Object[])array[0];
- mHashes = (int[])array[1];
- array[0] = array[1] = null;
- mTwiceBaseCacheSize--;
- if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
- + " now have " + mTwiceBaseCacheSize + " entries");
- return;
+ try {
+ mTwiceBaseCache = (Object[]) array[0];
+ mHashes = (int[]) array[1];
+ if (mHashes != null) {
+ array[0] = array[1] = null;
+ mTwiceBaseCacheSize--;
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving 2x cache " + mHashes
+ + " now have " + mTwiceBaseCacheSize + " entries");
+ }
+ return;
+ }
+ } catch (ClassCastException e) {
+ }
+ // Whoops! Someone trampled the array (probably due to not protecting
+ // their access with a lock). Our cache is corrupt; report and give up.
+ Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+ + " [1]=" + array[1]);
+ mTwiceBaseCache = null;
+ mTwiceBaseCacheSize = 0;
}
}
} else if (size == BASE_SIZE) {
- synchronized (ArrayMap.class) {
+ synchronized (sBaseCacheLock) {
if (mBaseCache != null) {
final Object[] array = mBaseCache;
mArray = array;
- mBaseCache = (Object[])array[0];
- mHashes = (int[])array[1];
- array[0] = array[1] = null;
- mBaseCacheSize--;
- if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
- + " now have " + mBaseCacheSize + " entries");
- return;
+ try {
+ mBaseCache = (Object[]) array[0];
+ mHashes = (int[]) array[1];
+ if (mHashes != null) {
+ array[0] = array[1] = null;
+ mBaseCacheSize--;
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving 1x cache " + mHashes
+ + " now have " + mBaseCacheSize + " entries");
+ }
+ return;
+ }
+ } catch (ClassCastException e) {
+ }
+ // Whoops! Someone trampled the array (probably due to not protecting
+ // their access with a lock). Our cache is corrupt; report and give up.
+ Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+ + " [1]=" + array[1]);
+ mBaseCache = null;
+ mBaseCacheSize = 0;
}
}
}
@@ -242,10 +276,14 @@
mArray = new Object[size<<1];
}
+ /**
+ * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+ * words, don't pass mHashes or mArray in directly.
+ */
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
if (hashes.length == (BASE_SIZE*2)) {
- synchronized (ArrayMap.class) {
+ synchronized (sTwiceBaseCacheLock) {
if (mTwiceBaseCacheSize < CACHE_SIZE) {
array[0] = mTwiceBaseCache;
array[1] = hashes;
@@ -259,7 +297,7 @@
}
}
} else if (hashes.length == BASE_SIZE) {
- synchronized (ArrayMap.class) {
+ synchronized (sBaseCacheLock) {
if (mBaseCacheSize < CACHE_SIZE) {
array[0] = mBaseCache;
array[1] = hashes;
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 3fa914f..44c5af2 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -23,6 +23,7 @@
import java.lang.reflect.Array;
import java.util.Collection;
+import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -46,6 +47,8 @@
* you have no control over this shrinking -- if you set a capacity and then remove an
* item, it may reduce the capacity to better match the current size. In the future an
* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
*/
public final class ArraySet<E> implements Collection<E>, Set<E> {
private static final boolean DEBUG = false;
@@ -72,15 +75,30 @@
static int sBaseCacheSize;
static Object[] sTwiceBaseCache;
static int sTwiceBaseCacheSize;
+ /**
+ * Separate locks for each cache since each can be accessed independently of the other without
+ * risk of a deadlock.
+ */
+ private static final Object sBaseCacheLock = new Object();
+ private static final Object sTwiceBaseCacheLock = new Object();
- final boolean mIdentityHashCode;
+ private final boolean mIdentityHashCode;
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
int[] mHashes;
@UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
Object[] mArray;
@UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
int mSize;
- MapCollections<E, E> mCollections;
+ private MapCollections<E, E> mCollections;
+
+ private int binarySearch(int[] hashes, int hash) {
+ try {
+ return ContainerHelpers.binarySearch(hashes, mSize, hash);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
private int indexOf(Object key, int hash) {
@@ -91,7 +109,7 @@
return ~0;
}
- int index = ContainerHelpers.binarySearch(mHashes, N, hash);
+ int index = binarySearch(mHashes, hash);
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
@@ -130,7 +148,7 @@
return ~0;
}
- int index = ContainerHelpers.binarySearch(mHashes, N, 0);
+ int index = binarySearch(mHashes, 0);
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
@@ -163,20 +181,22 @@
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
private void allocArrays(final int size) {
if (size == (BASE_SIZE * 2)) {
- synchronized (ArraySet.class) {
+ synchronized (sTwiceBaseCacheLock) {
if (sTwiceBaseCache != null) {
final Object[] array = sTwiceBaseCache;
try {
mArray = array;
sTwiceBaseCache = (Object[]) array[0];
mHashes = (int[]) array[1];
- array[0] = array[1] = null;
- sTwiceBaseCacheSize--;
- if (DEBUG) {
- Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
- + sTwiceBaseCacheSize + " entries");
- }
- return;
+ if (mHashes != null) {
+ array[0] = array[1] = null;
+ sTwiceBaseCacheSize--;
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
+ + sTwiceBaseCacheSize + " entries");
+ }
+ return;
+ }
} catch (ClassCastException e) {
}
// Whoops! Someone trampled the array (probably due to not protecting
@@ -188,20 +208,22 @@
}
}
} else if (size == BASE_SIZE) {
- synchronized (ArraySet.class) {
+ synchronized (sBaseCacheLock) {
if (sBaseCache != null) {
final Object[] array = sBaseCache;
try {
mArray = array;
sBaseCache = (Object[]) array[0];
mHashes = (int[]) array[1];
- array[0] = array[1] = null;
- sBaseCacheSize--;
- if (DEBUG) {
- Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
- + " entries");
+ if (mHashes != null) {
+ array[0] = array[1] = null;
+ sBaseCacheSize--;
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have "
+ + sBaseCacheSize + " entries");
+ }
+ return;
}
- return;
} catch (ClassCastException e) {
}
// Whoops! Someone trampled the array (probably due to not protecting
@@ -218,10 +240,14 @@
mArray = new Object[size];
}
+ /**
+ * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+ * words, don't pass mHashes or mArray in directly.
+ */
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
if (hashes.length == (BASE_SIZE * 2)) {
- synchronized (ArraySet.class) {
+ synchronized (sTwiceBaseCacheLock) {
if (sTwiceBaseCacheSize < CACHE_SIZE) {
array[0] = sTwiceBaseCache;
array[1] = hashes;
@@ -237,7 +263,7 @@
}
}
} else if (hashes.length == BASE_SIZE) {
- synchronized (ArraySet.class) {
+ synchronized (sBaseCacheLock) {
if (sBaseCacheSize < CACHE_SIZE) {
array[0] = sBaseCache;
array[1] = hashes;
@@ -308,10 +334,16 @@
@Override
public void clear() {
if (mSize != 0) {
- freeArrays(mHashes, mArray, mSize);
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ final int osize = mSize;
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
+ freeArrays(ohashes, oarray, osize);
+ }
+ if (mSize != 0) {
+ throw new ConcurrentModificationException();
}
}
@@ -320,6 +352,7 @@
* items.
*/
public void ensureCapacity(int minimumCapacity) {
+ final int oSize = mSize;
if (mHashes.length < minimumCapacity) {
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
@@ -330,6 +363,9 @@
}
freeArrays(ohashes, oarray, mSize);
}
+ if (mSize != oSize) {
+ throw new ConcurrentModificationException();
+ }
}
/**
@@ -400,11 +436,10 @@
*
* @param value the object to add.
* @return {@code true} if this set is modified, {@code false} otherwise.
- * @throws ClassCastException
- * when the class of the object is inappropriate for this set.
*/
@Override
public boolean add(E value) {
+ final int oSize = mSize;
final int hash;
int index;
if (value == null) {
@@ -419,9 +454,9 @@
}
index = ~index;
- if (mSize >= mHashes.length) {
- final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
- : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
+ if (oSize >= mHashes.length) {
+ final int n = oSize >= (BASE_SIZE * 2) ? (oSize + (oSize >> 1))
+ : (oSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
@@ -429,21 +464,29 @@
final Object[] oarray = mArray;
allocArrays(n);
+ if (oSize != mSize) {
+ throw new ConcurrentModificationException();
+ }
+
if (mHashes.length > 0) {
- if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
+ if (DEBUG) Log.d(TAG, "add: copy 0-" + oSize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
- freeArrays(ohashes, oarray, mSize);
+ freeArrays(ohashes, oarray, oSize);
}
- if (index < mSize) {
+ if (index < oSize) {
if (DEBUG) {
- Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
+ Log.d(TAG, "add: move " + index + "-" + (oSize - index) + " to " + (index + 1));
}
- System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
- System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
+ System.arraycopy(mHashes, index, mHashes, index + 1, oSize - index);
+ System.arraycopy(mArray, index, mArray, index + 1, oSize - index);
+ }
+
+ if (oSize != mSize || index >= mHashes.length) {
+ throw new ConcurrentModificationException();
}
mHashes[index] = hash;
@@ -458,6 +501,7 @@
* @hide
*/
public void append(E value) {
+ final int oSize = mSize;
final int index = mSize;
final int hash = value == null ? 0
: (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
@@ -476,6 +520,11 @@
add(value);
return;
}
+
+ if (oSize != mSize) {
+ throw new ConcurrentModificationException();
+ }
+
mSize = index + 1;
mHashes[index] = hash;
mArray[index] = value;
@@ -492,6 +541,9 @@
if (N > 0) {
System.arraycopy(array.mHashes, 0, mHashes, 0, N);
System.arraycopy(array.mArray, 0, mArray, 0, N);
+ if (0 != mSize) {
+ throw new ConcurrentModificationException();
+ }
mSize = N;
}
} else {
@@ -549,12 +601,14 @@
// Check if exception should be thrown outside of the critical path.
throw new ArrayIndexOutOfBoundsException(index);
}
+ final int oSize = mSize;
final Object old = mArray[index];
- if (mSize <= 1) {
+ if (oSize <= 1) {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
clear();
} else {
+ final int nSize = oSize - 1;
if (shouldShrink()) {
// Shrunk enough to reduce size of arrays.
final int n = getNewShrunkenSize();
@@ -565,31 +619,33 @@
final Object[] oarray = mArray;
allocArrays(n);
- mSize--;
if (index > 0) {
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index);
}
- if (index < mSize) {
+ if (index < nSize) {
if (DEBUG) {
- Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
+ Log.d(TAG, "remove: copy from " + (index + 1) + "-" + nSize
+ " to " + index);
}
- System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
- System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
+ System.arraycopy(ohashes, index + 1, mHashes, index, nSize - index);
+ System.arraycopy(oarray, index + 1, mArray, index, nSize - index);
}
} else {
- mSize--;
- if (index < mSize) {
+ if (index < nSize) {
if (DEBUG) {
- Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
+ Log.d(TAG, "remove: move " + (index + 1) + "-" + nSize + " to " + index);
}
- System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
- System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
+ System.arraycopy(mHashes, index + 1, mHashes, index, nSize - index);
+ System.arraycopy(mArray, index + 1, mArray, index, nSize - index);
}
- mArray[mSize] = null;
+ mArray[nSize] = null;
}
+ if (oSize != mSize) {
+ throw new ConcurrentModificationException();
+ }
+ mSize = nSize;
}
return (E) old;
}
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 8b5659b..3a1df3e 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -60,9 +60,19 @@
}
public synchronized void dump(PrintWriter pw) {
+ dump("", pw);
+ }
+
+ /**
+ * Dumps the content of local log to print writer with each log entry predeced with indent
+ *
+ * @param indent indent that precedes each log entry
+ * @param pw printer writer to write into
+ */
+ public synchronized void dump(String indent, PrintWriter pw) {
Iterator<String> itr = mLog.iterator();
while (itr.hasNext()) {
- pw.println(itr.next());
+ pw.printf("%s%s\n", indent, itr.next());
}
}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index f8b38e9..07cecd3 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -62,9 +62,9 @@
}
/**
- * Tries to return a frozen ICU time zone that would have had the specified offset
- * and DST value at the specified moment in the specified country.
- * Returns null if no suitable zone could be found.
+ * Returns a frozen ICU time zone that has / would have had the specified offset and DST value
+ * at the specified moment in the specified country. Returns null if no suitable zone could be
+ * found.
*/
private static android.icu.util.TimeZone getIcuTimeZone(
int offset, boolean dst, long when, String country) {
@@ -73,8 +73,15 @@
}
android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
- return TimeZoneFinder.getInstance()
- .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
+ CountryTimeZones countryTimeZones =
+ TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+ if (countryTimeZones == null) {
+ return null;
+ }
+
+ CountryTimeZones.OffsetResult offsetResult =
+ countryTimeZones.lookupByOffsetWithBias(offset, dst, when, bias);
+ return offsetResult != null ? offsetResult.mTimeZone : null;
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 42275a3..d7d7e21 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -856,6 +856,8 @@
mScrollY = record.mScrollY;
mMaxScrollX = record.mMaxScrollX;
mMaxScrollY = record.mMaxScrollY;
+ mScrollDeltaX = record.mScrollDeltaX;
+ mScrollDeltaY = record.mScrollDeltaY;
mAddedCount = record.mAddedCount;
mRemovedCount = record.mRemovedCount;
mClassName = record.mClassName;
@@ -882,6 +884,8 @@
mScrollY = 0;
mMaxScrollX = 0;
mMaxScrollY = 0;
+ mScrollDeltaX = UNDEFINED;
+ mScrollDeltaY = UNDEFINED;
mAddedCount = UNDEFINED;
mRemovedCount = UNDEFINED;
mClassName = null;
@@ -921,6 +925,8 @@
append(builder, "ScrollY", mScrollY);
append(builder, "MaxScrollX", mMaxScrollX);
append(builder, "MaxScrollY", mMaxScrollY);
+ append(builder, "ScrollDeltaX", mScrollDeltaX);
+ append(builder, "ScrollDeltaY", mScrollDeltaY);
append(builder, "AddedCount", mAddedCount);
append(builder, "RemovedCount", mRemovedCount);
append(builder, "ParcelableData", mParcelableData);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1f7ae0e..3bbd321 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -43,6 +43,7 @@
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
@@ -1737,6 +1738,26 @@
final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
final ComponentName componentName = client.autofillClientGetComponentName();
+
+ if (!mEnabledForAugmentedAutofillOnly && mOptions != null
+ && mOptions.isAutofillDisabledLocked(componentName)) {
+ if (mOptions.isAugmentedAutofillEnabled(mContext)) {
+ if (sDebug) {
+ Log.d(TAG, "startSession(" + componentName + "): disabled by service but "
+ + "whitelisted for augmented autofill");
+ flags |= FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
+ }
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "startSession(" + componentName + "): ignored because "
+ + "disabled by service and not whitelisted for augmented autofill");
+ }
+ setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, null);
+ client.autofillClientResetableStateAvailable();
+ return;
+ }
+ }
+
mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, componentName,
@@ -2067,6 +2088,7 @@
mServiceClientCleaner.clean();
mServiceClientCleaner = null;
}
+ notifyReenableAutofill();
}
}
sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
@@ -2403,6 +2425,37 @@
}
}
+ private void notifyDisableAutofill(long disableDuration, ComponentName componentName) {
+ synchronized (mLock) {
+ if (mOptions == null) {
+ return;
+ }
+ long expiration = SystemClock.elapsedRealtime() + disableDuration;
+ // Protect it against overflow
+ if (expiration < 0) {
+ expiration = Long.MAX_VALUE;
+ }
+ if (componentName != null) {
+ if (mOptions.disabledActivities == null) {
+ mOptions.disabledActivities = new ArrayMap<>();
+ }
+ mOptions.disabledActivities.put(componentName.flattenToString(), expiration);
+ } else {
+ mOptions.appDisabledExpiration = expiration;
+ }
+ }
+ }
+
+ void notifyReenableAutofill() {
+ synchronized (mLock) {
+ if (mOptions == null) {
+ return;
+ }
+ mOptions.appDisabledExpiration = 0;
+ mOptions.disabledActivities = null;
+ }
+ }
+
private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
if (sVerbose) {
Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
@@ -3181,6 +3234,15 @@
}
@Override
+ public void notifyDisableAutofill(long disableDuration, ComponentName componentName)
+ throws RemoteException {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.notifyDisableAutofill(disableDuration, componentName));
+ }
+ }
+
+ @Override
public void dispatchUnhandledKey(int sessionId, AutofillId id, KeyEvent fullScreen) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 85def7f..84949c8 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -18,6 +18,7 @@
import java.util.List;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Rect;
@@ -111,4 +112,8 @@
*/
void getAugmentedAutofillClient(in IResultReceiver result);
+ /**
+ * Notifies disables autofill for the app or activity.
+ */
+ void notifyDisableAutofill(long disableDuration, in ComponentName componentName);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index cee7943..1e7440b 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -480,6 +480,8 @@
return;
}
+ mNextFlushForTextChanged = false;
+
final int numberEvents = mEvents.size();
final String reasonString = getFlushReasonAsString(reason);
if (sDebug) {
@@ -495,10 +497,6 @@
try {
mHandler.removeMessages(MSG_FLUSH);
- if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) {
- mNextFlushForTextChanged = false;
- }
-
final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions);
} catch (RemoteException e) {
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index a97c330..66f7504 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -309,7 +309,7 @@
}
public void onFailure() {
- Log.e(LOG_TAG, "Request failed.", null);
+ Log.e(LOG_TAG, "Request failed at " + mName, null);
mLatch.countDown();
}
diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java
index 18ec334..ac145b1 100644
--- a/core/java/android/webkit/PermissionRequest.java
+++ b/core/java/android/webkit/PermissionRequest.java
@@ -32,7 +32,7 @@
* avoid unintentionally granting requests for new permissions, you should pass the
* specific permissions you intend to grant to {@link #grant(String[]) grant()},
* and avoid writing code like this example:
- * <pre>
+ * <pre class="prettyprint">
* permissionRequest.grant(permissionRequest.getResources()) // This is wrong!!!
* </pre>
* See the WebView's release notes for information about new protected resources.
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 95fe963..4db6308 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -519,15 +519,17 @@
* may not be supported and applications wishing to support these sources
* or more advanced file operations should build their own Intent.
*
- * <pre>
- * How to use:
- * 1. Build an intent using {@link #createIntent}
- * 2. Fire the intent using {@link android.app.Activity#startActivityForResult}.
- * 3. Check for ActivityNotFoundException and take a user friendly action if thrown.
- * 4. Listen the result using {@link android.app.Activity#onActivityResult}
- * 5. Parse the result using {@link #parseResult} only if media capture was not requested.
- * 6. Send the result using filePathCallback of {@link WebChromeClient#onShowFileChooser}
- * </pre>
+ * <p>How to use:
+ * <ol>
+ * <li>Build an intent using {@link #createIntent}</li>
+ * <li>Fire the intent using {@link android.app.Activity#startActivityForResult}.</li>
+ * <li>Check for ActivityNotFoundException and take a user friendly action if thrown.</li>
+ * <li>Listen the result using {@link android.app.Activity#onActivityResult}</li>
+ * <li>Parse the result using {@link #parseResult} only if media capture was not
+ * requested.</li>
+ * <li>Send the result using filePathCallback of {@link
+ * WebChromeClient#onShowFileChooser}</li>
+ * </ol>
*
* @return an Intent that supports basic file chooser sources.
*/
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a35659e..e4b5eaa 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -762,7 +762,7 @@
* encoded. If the data is base64 encoded, the value of the encoding
* parameter must be {@code "base64"}. HTML can be encoded with {@link
* android.util.Base64#encodeToString(byte[],int)} like so:
- * <pre>
+ * <pre class="prettyprint">
* String unencodedHtml =
* "<html><body>'%28' is the code for '('</body></html>";
* String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
@@ -1854,7 +1854,7 @@
* important security note below for implications.
* <p> Note that injected objects will not appear in JavaScript until the page is next
* (re)loaded. JavaScript should be enabled before injecting the object. For example:
- * <pre>
+ * <pre class="prettyprint">
* class JsObject {
* {@literal @}JavascriptInterface
* public String toString() { return "injectedObject"; }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 00206fc..faeecda 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -164,6 +164,8 @@
public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+
+ private boolean mIsAppPredictorComponentAvailable;
private AppPredictor mAppPredictor;
private AppPredictor.Callback mAppPredictorCallback;
private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
@@ -617,6 +619,9 @@
.addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
.addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
+ // This is the only place this value is being set. Effectively final.
+ mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
+
AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
if (appPredictor != null) {
mDirectShareAppTargetCache = new HashMap<>();
@@ -707,6 +712,32 @@
}
/**
+ * Returns true if app prediction service is defined and the component exists on device.
+ */
+ private boolean isAppPredictionServiceAvailable() {
+ final String appPredictionServiceName =
+ getString(R.string.config_defaultAppPredictionService);
+ if (appPredictionServiceName == null) {
+ return false;
+ }
+ final ComponentName appPredictionComponentName =
+ ComponentName.unflattenFromString(appPredictionServiceName);
+ if (appPredictionComponentName == null) {
+ return false;
+ }
+
+ // Check if the app prediction component actually exists on the device.
+ Intent intent = new Intent();
+ intent.setComponent(appPredictionComponentName);
+ if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
+ Log.e(TAG, "App prediction service is defined, but does not exist: "
+ + appPredictionServiceName);
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Check if the profile currently used is a work profile.
* @return true if it is work profile, false if it is parent profile (or no work profile is
* set up)
@@ -1693,6 +1724,9 @@
@Nullable
private AppPredictor getAppPredictor() {
+ if (!mIsAppPredictorComponentAvailable) {
+ return null;
+ }
if (mAppPredictor == null
&& getPackageManager().getAppPredictionServicePackageName() != null) {
final IntentFilter filter = getTargetIntentFilter();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 88d0e85..8503ab8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4750,11 +4750,11 @@
final long uptime = mClocks.uptimeMillis();
boolean updateHistory = false;
- if (isScreenDoze(state)) {
+ if (isScreenDoze(state) && !isScreenDoze(oldState)) {
mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.startRunningLocked(elapsedRealtime);
updateHistory = true;
- } else if (isScreenDoze(oldState)) {
+ } else if (isScreenDoze(oldState) && !isScreenDoze(state)) {
mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
updateHistory = true;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index cf0394d..9441825 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,7 +153,7 @@
// Used to show the dialog when BiometricService starts authentication
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
- boolean requireConfirmation, int userId);
+ boolean requireConfirmation, int userId, String opPackageName);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 85ae18e..4c3a177 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -101,7 +101,7 @@
// Used to show the dialog when BiometricService starts authentication
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
- boolean requireConfirmation, int userId);
+ boolean requireConfirmation, int userId, String opPackageName);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fb68bc7..4c54539 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -339,6 +339,8 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "android/graphics/apex/android_region.cpp",
+
"android_graphics_Canvas.cpp",
"android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
@@ -386,10 +388,12 @@
local_include_dirs: [
"include", // NEEDED FOR ANDROID RUNTIME
"android/graphics",
+ "android/graphics/apex/include",
],
export_include_dirs: [
".",
+ "android/graphics/apex/include",
],
include_dirs: [
@@ -417,6 +421,8 @@
target: {
android: {
srcs: [ // sources that depend on android only libraries
+ "android/graphics/apex/android_canvas.cpp",
+
"android_view_TextureLayer.cpp",
"android_view_ThreadedRenderer.cpp",
"android/graphics/BitmapRegionDecoder.cpp",
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index 90ca3bb..1fb5fe3 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -360,8 +360,4 @@
NELEM(gRegionIterMethods));
}
-SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj) {
- return GetSkRegion(env, regionObj);
-}
-
} // namespace android
diff --git a/core/jni/android/graphics/Region.h b/core/jni/android/graphics/Region.h
deleted file mode 100644
index 2e8e109..0000000
--- a/core/jni/android/graphics/Region.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#ifndef _ANDROID_GRAPHICS_REGION_H_
-#define _ANDROID_GRAPHICS_REGION_H_
-
-#include "jni.h"
-#include "SkRegion.h"
-
-namespace android {
-
-/* Gets the underlying SkRegion from a Region object. */
-extern SkRegion* android_graphics_Region_getSkRegion(JNIEnv* env, jobject regionObj);
-
-} // namespace android
-
-#endif // _ANDROID_GRAPHICS_REGION_H_
diff --git a/core/jni/android/graphics/apex/android_canvas.cpp b/core/jni/android/graphics/apex/android_canvas.cpp
new file mode 100644
index 0000000..7a4495f
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_canvas.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/graphics/canvas.h"
+
+#include "GraphicsJNI.h"
+
+#include <hwui/Canvas.h>
+#include <utils/Color.h>
+
+#include <SkBitmap.h>
+
+using namespace android;
+
+static inline Canvas* toCanvas(ACanvas* aCanvas) {
+ return reinterpret_cast<Canvas*>(aCanvas);
+}
+
+static inline ACanvas* toACanvas(Canvas* canvas) {
+ return reinterpret_cast<ACanvas*>(canvas);
+}
+
+bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat) {
+ ANativeWindow_Buffer buffer { 0, 0, 0, bufferFormat, nullptr, {0} };
+ const SkColorType colorType = uirenderer::ANativeWindowToImageInfo(buffer, nullptr).colorType();
+ return kUnknown_SkColorType != colorType;
+}
+
+ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) {
+ return toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj));
+}
+
+void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace) {
+ SkBitmap bitmap;
+ if (buffer != nullptr && buffer->width > 0 && buffer->height > 0) {
+ sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace));
+ SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
+ ssize_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
+ bitmap.setInfo(imageInfo, rowBytes);
+ bitmap.setPixels(buffer->bits);
+ }
+
+ toCanvas(canvas)->setBitmap(bitmap);
+}
+
+void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) {
+ //TODO update Canvas to take antialias param
+ toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ SkClipOp::kIntersect);
+}
+
+void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) {
+ //TODO update Canvas to take antialias param
+ toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ SkClipOp::kDifference);
+}
diff --git a/core/jni/android/graphics/apex/android_region.cpp b/core/jni/android/graphics/apex/android_region.cpp
new file mode 100644
index 0000000..2030e7e
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_region.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/graphics/region.h"
+
+#include "GraphicsJNI.h"
+
+#include <SkRegion.h>
+
+static inline SkRegion::Iterator* ARegionIter_to_SkRegionIter(ARegionIterator* iterator) {
+ return reinterpret_cast<SkRegion::Iterator*>(iterator);
+}
+
+static inline ARegionIterator* SkRegionIter_to_ARegionIter(SkRegion::Iterator* iterator) {
+ return reinterpret_cast<ARegionIterator*>(iterator);
+}
+
+ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject regionObj) {
+ SkRegion* region = GraphicsJNI::getNativeRegion(env, regionObj);
+ return (!region) ? nullptr : SkRegionIter_to_ARegionIter(new SkRegion::Iterator(*region));
+}
+
+void ARegionIterator_releaseIterator(ARegionIterator* iterator) {
+ delete ARegionIter_to_SkRegionIter(iterator);
+}
+
+bool ARegionIterator_isComplex(ARegionIterator* iterator) {
+ return ARegionIter_to_SkRegionIter(iterator)->rgn()->isComplex();
+}
+
+bool ARegionIterator_isDone(ARegionIterator* iterator) {
+ return ARegionIter_to_SkRegionIter(iterator)->done();
+}
+
+void ARegionIterator_next(ARegionIterator* iterator) {
+ ARegionIter_to_SkRegionIter(iterator)->next();
+}
+
+ARect ARegionIterator_getRect(ARegionIterator* iterator) {
+ const SkIRect& rect = ARegionIter_to_SkRegionIter(iterator)->rect();
+ return { rect.fLeft, rect.fTop, rect.fRight, rect.fBottom };
+}
+
+ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator) {
+ const SkIRect& bounds = ARegionIter_to_SkRegionIter(iterator)->rgn()->getBounds();
+ return { bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom };
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/canvas.h b/core/jni/android/graphics/apex/include/android/graphics/canvas.h
new file mode 100644
index 0000000..c35a7d6
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/canvas.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#ifndef ANDROID_GRAPHICS_CANVAS_H
+#define ANDROID_GRAPHICS_CANVAS_H
+
+#include <android/native_window.h>
+#include <android/rect.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+
+/**
+* Opaque handle for a native graphics canvas.
+*/
+typedef struct ACanvas ACanvas;
+
+// One of AHardwareBuffer_Format.
+bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat);
+
+/**
+ * Returns a native handle to a Java android.graphics.Canvas
+ *
+ * @param env
+ * @param canvas
+ * @return ACanvas* that is only valid for the life of the jobject.
+ */
+ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvas);
+
+/**
+ * Updates the canvas to render into the pixels in the provided buffer
+ *
+ * @param canvas
+ * @param buffer The buffer that will provide the backing store for this canvas. The buffer must
+ * remain valid until the this method is called again with either another active
+ * buffer or nullptr. If nullptr is given the canvas will release the previous buffer
+ * and set an empty backing store.
+ * @param dataspace
+ */
+void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace);
+
+/**
+ * Clips operations on the canvas to the intersection of the current clip and the provided clipRect.
+ */
+void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false);
+
+/**
+ * Clips operations on the canvas to the difference of the current clip and the provided clipRect.
+ */
+void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false);
+
+__END_DECLS
+#endif // ANDROID_GRAPHICS_CANVAS_H
\ No newline at end of file
diff --git a/core/jni/android/graphics/apex/include/android/graphics/region.h b/core/jni/android/graphics/apex/include/android/graphics/region.h
new file mode 100644
index 0000000..961067a
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/region.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#ifndef ANDROID_GRAPHICS_REGION_H
+#define ANDROID_GRAPHICS_REGION_H
+
+#include <android/rect.h>
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+
+/**
+* Opaque handle for a native graphics region iterator.
+*/
+typedef struct ARegionIterator ARegionIterator;
+
+/**
+ * Returns a iterator for a Java android.graphics.Region
+ *
+ * @param env
+ * @param region
+ * @return ARegionIterator that must be closed and must not live longer than the life
+ * of the jobject. It returns nullptr if the region is not a valid object.
+ */
+ARegionIterator* ARegionIterator_acquireIterator(JNIEnv* env, jobject region);
+
+void ARegionIterator_releaseIterator(ARegionIterator* iterator);
+
+bool ARegionIterator_isComplex(ARegionIterator* iterator);
+
+bool ARegionIterator_isDone(ARegionIterator* iterator);
+
+void ARegionIterator_next(ARegionIterator* iterator);
+
+ARect ARegionIterator_getRect(ARegionIterator* iterator);
+
+ARect ARegionIterator_getTotalBounds(ARegionIterator* iterator);
+
+__END_DECLS
+
+#ifdef __cplusplus
+namespace android {
+namespace graphics {
+ class RegionIterator {
+ public:
+ RegionIterator(JNIEnv* env, jobject region)
+ : mIterator(ARegionIterator_acquireIterator(env, region)) {}
+ ~RegionIterator() { ARegionIterator_releaseIterator(mIterator); }
+
+ bool isValid() const { return mIterator != nullptr; }
+ bool isComplex() { return ARegionIterator_isComplex(mIterator); }
+ bool isDone() { return ARegionIterator_isDone(mIterator); }
+ void next() { ARegionIterator_next(mIterator); }
+ ARect getRect() { return ARegionIterator_getRect(mIterator); }
+ ARect getTotalBounds() const { return ARegionIterator_getTotalBounds(mIterator); }
+ private:
+ ARegionIterator* mIterator;
+ };
+}; // namespace graphics
+}; // namespace android
+
+#endif // __cplusplus
+#endif // ANDROID_GRAPHICS_REGION_H
\ No newline at end of file
diff --git a/core/jni/android_graphics_GraphicBuffer.cpp b/core/jni/android_graphics_GraphicBuffer.cpp
index bb9254c..43d22eb 100644
--- a/core/jni/android_graphics_GraphicBuffer.cpp
+++ b/core/jni/android_graphics_GraphicBuffer.cpp
@@ -21,11 +21,6 @@
#include <inttypes.h>
#include "android_os_Parcel.h"
-#include "android/graphics/GraphicsJNI.h"
-
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_graphics_GraphicBuffer.h>
-
#include <binder/Parcel.h>
#include <log/log.h>
@@ -33,10 +28,10 @@
#include <ui/GraphicBuffer.h>
#include <ui/PixelFormat.h>
-#include <hwui/Bitmap.h>
-
-#include <SkCanvas.h>
-#include <SkBitmap.h>
+#include <android/native_window.h>
+#include <android/graphics/canvas.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
+#include <private/android/AHardwareBufferHelpers.h>
#include <private/gui/ComposerService.h>
@@ -146,23 +141,8 @@
// Canvas management
// ----------------------------------------------------------------------------
-static inline SkColorType convertPixelFormat(int32_t format) {
- switch (format) {
- case PIXEL_FORMAT_RGBA_8888:
- return kN32_SkColorType;
- case PIXEL_FORMAT_RGBX_8888:
- return kN32_SkColorType;
- case PIXEL_FORMAT_RGBA_FP16:
- return kRGBA_F16_SkColorType;
- case PIXEL_FORMAT_RGB_565:
- return kRGB_565_SkColorType;
- default:
- return kUnknown_SkColorType;
- }
-}
-
static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject,
- jlong wrapperHandle, jobject canvas, jobject dirtyRect) {
+ jlong wrapperHandle, jobject canvasObj, jobject dirtyRect) {
GraphicBufferWrapper* wrapper =
reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle);
@@ -191,24 +171,16 @@
return JNI_FALSE;
}
- ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
+ ANativeWindow_Buffer nativeBuffer;
+ nativeBuffer.width = buffer->getWidth();
+ nativeBuffer.height = buffer->getHeight();
+ nativeBuffer.stride = buffer->getStride();
+ nativeBuffer.format = AHardwareBuffer_convertFromPixelFormat(buffer->getPixelFormat());
+ nativeBuffer.bits = bits;
- SkBitmap bitmap;
- bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
- convertPixelFormat(buffer->getPixelFormat()),
- kPremul_SkAlphaType),
- bytesCount);
-
- if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
- bitmap.setPixels(bits);
- } else {
- bitmap.setPixels(NULL);
- }
-
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
- nativeCanvas->setBitmap(bitmap);
- nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom,
- SkClipOp::kIntersect);
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, &nativeBuffer, ADATASPACE_UNKNOWN);
+ ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom});
if (dirtyRect) {
INVOKEV(dirtyRect, gRectClassInfo.set,
@@ -219,13 +191,13 @@
}
static jboolean android_graphics_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobject,
- jlong wrapperHandle, jobject canvas) {
+ jlong wrapperHandle, jobject canvasObj) {
+ // release the buffer from the canvas
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
GraphicBufferWrapper* wrapper =
reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle);
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
- nativeCanvas->setBitmap(SkBitmap());
-
if (wrapper) {
status_t status = wrapper->get()->unlock();
return status == 0 ? JNI_TRUE : JNI_FALSE;
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 98680fa1e..64caf33 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -22,7 +22,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
-#include <android/graphics/Region.h>
+#include <android/graphics/region.h>
#include <gui/SurfaceControl.h>
#include <ui/Region.h>
@@ -128,10 +128,9 @@
jobject regionObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.touchableRegion);
if (regionObj) {
- SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
- for (SkRegion::Iterator it(*region); !it.done(); it.next()) {
- const SkIRect& rect = it.rect();
- mInfo.addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
+ for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
+ ARect rect = it.getRect();
+ mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
}
env->DeleteLocalRef(regionObj);
}
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 7e3b083..8553a2c 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -210,8 +210,7 @@
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- InputChannel* inputChannel = new InputChannel();
- inputChannel->read(*parcel);
+ sp<InputChannel> inputChannel = InputChannel::read(*parcel);
NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index d65e252..4c2e91f 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -20,15 +20,16 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
-#include "android_os_Parcel.h"
-#include "android/graphics/GraphicsJNI.h"
-
#include "core_jni_helpers.h"
+
+#include <android/graphics/canvas.h>
#include <android_runtime/android_graphics_GraphicBuffer.h>
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/Log.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include "android_os_Parcel.h"
#include <binder/Parcel.h>
#include <gui/Surface.h>
@@ -39,11 +40,6 @@
#include <ui/Rect.h>
#include <ui/Region.h>
-#include <SkCanvas.h>
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkRegion.h>
-
#include <utils/misc.h>
#include <utils/Log.h>
@@ -232,37 +228,21 @@
dirtyRectPtr = &dirtyRect;
}
- ANativeWindow_Buffer outBuffer;
- status_t err = surface->lock(&outBuffer, dirtyRectPtr);
+ ANativeWindow_Buffer buffer;
+ status_t err = surface->lock(&buffer, dirtyRectPtr);
if (err < 0) {
const char* const exception = (err == NO_MEMORY) ?
- OutOfResourcesException :
- "java/lang/IllegalArgumentException";
+ OutOfResourcesException : IllegalArgumentException;
jniThrowException(env, exception, NULL);
return 0;
}
- SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
- convertPixelFormat(outBuffer.format),
- outBuffer.format == PIXEL_FORMAT_RGBX_8888
- ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
-
- SkBitmap bitmap;
- ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
- bitmap.setInfo(info, bpr);
- if (outBuffer.width > 0 && outBuffer.height > 0) {
- bitmap.setPixels(outBuffer.bits);
- } else {
- // be safe with an empty bitmap.
- bitmap.setPixels(NULL);
- }
-
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
- nativeCanvas->setBitmap(bitmap);
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, &buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));
if (dirtyRectPtr) {
- nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
- dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
+ ACanvas_clipRect(canvas, {dirtyRect.left, dirtyRect.top,
+ dirtyRect.right, dirtyRect.bottom});
}
if (dirtyRectObj) {
@@ -288,8 +268,8 @@
}
// detach the canvas from the surface
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
- nativeCanvas->setBitmap(SkBitmap());
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
// unlock surface
status_t err = surface->unlockAndPost();
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5cbf81c..67f52f4 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -20,9 +20,9 @@
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "android_hardware_input_InputWindowHandle.h"
-#include "android/graphics/Region.h"
#include "core_jni_helpers.h"
+#include <android/graphics/region.h>
#include <android_runtime/AndroidRuntime.h>
#include <android-base/chrono_utils.h>
#include <nativehelper/JNIHelp.h>
@@ -425,20 +425,19 @@
static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject regionObj) {
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
- if (!region) {
+ graphics::RegionIterator iterator(env, regionObj);
+ if (!iterator.isValid()) {
doThrowIAE(env);
return;
}
- const SkIRect& b(region->getBounds());
- Region reg(Rect(b.fLeft, b.fTop, b.fRight, b.fBottom));
- if (region->isComplex()) {
- SkRegion::Iterator it(*region);
- while (!it.done()) {
- const SkIRect& r(it.rect());
- reg.addRectUnchecked(r.fLeft, r.fTop, r.fRight, r.fBottom);
- it.next();
+ ARect bounds = iterator.getTotalBounds();
+ Region reg({bounds.left, bounds.top, bounds.right, bounds.bottom});
+ if (iterator.isComplex()) {
+ while (!iterator.isDone()) {
+ ARect rect = iterator.getRect();
+ reg.addRectUnchecked(rect.left, rect.top, rect.right, rect.bottom);
+ iterator.next();
}
}
@@ -497,11 +496,8 @@
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- std::vector<uint8_t> byteData(parcel->dataSize());
- memcpy(byteData.data(), parcel->data(), parcel->dataSize());
-
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- transaction->setMetadata(ctrl, id, std::move(byteData));
+ transaction->setMetadata(ctrl, id, *parcel);
}
static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 15319ad..1f69c8b 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -25,11 +25,7 @@
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkImage.h>
-
-#include "android/graphics/GraphicsJNI.h"
+#include <android/graphics/canvas.h>
#include "core_jni_helpers.h"
@@ -70,33 +66,6 @@
// Native layer
// ----------------------------------------------------------------------------
-// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
-static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
- SkColorType colorType = kUnknown_SkColorType;
- SkAlphaType alphaType = kOpaque_SkAlphaType;
- switch (buffer.format) {
- case WINDOW_FORMAT_RGBA_8888:
- colorType = kN32_SkColorType;
- alphaType = kPremul_SkAlphaType;
- break;
- case WINDOW_FORMAT_RGBX_8888:
- colorType = kN32_SkColorType;
- alphaType = kOpaque_SkAlphaType;
- break;
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- colorType = kRGBA_F16_SkColorType;
- alphaType = kPremul_SkAlphaType;
- break;
- case WINDOW_FORMAT_RGB_565:
- colorType = kRGB_565_SkColorType;
- alphaType = kOpaque_SkAlphaType;
- break;
- default:
- break;
- }
- return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType);
-}
-
/**
* This is a private API, and this implementation is also provided in the NDK.
* However, the NDK links against android_runtime, which means that using the
@@ -134,14 +103,12 @@
}
static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject,
- jlong nativeWindow, jobject canvas, jobject dirtyRect) {
+ jlong nativeWindow, jobject canvasObj, jobject dirtyRect) {
if (!nativeWindow) {
return JNI_FALSE;
}
- ANativeWindow_Buffer buffer;
-
Rect rect(Rect::EMPTY_RECT);
if (dirtyRect) {
rect.left = GET_INT(dirtyRect, gRectClassInfo.left);
@@ -152,25 +119,14 @@
rect.set(Rect(0x3FFF, 0x3FFF));
}
+ ANativeWindow_Buffer outBuffer;
sp<ANativeWindow> window((ANativeWindow*) nativeWindow);
- int32_t status = native_window_lock(window.get(), &buffer, &rect);
+ int32_t status = native_window_lock(window.get(), &outBuffer, &rect);
if (status) return JNI_FALSE;
- ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format);
-
- SkBitmap bitmap;
- bitmap.setInfo(convertPixelFormat(buffer), bytesCount);
-
- if (buffer.width > 0 && buffer.height > 0) {
- bitmap.setPixels(buffer.bits);
- } else {
- bitmap.setPixels(NULL);
- }
-
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
- nativeCanvas->setBitmap(bitmap);
- nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom,
- SkClipOp::kIntersect);
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, &outBuffer, ANativeWindow_getBuffersDataSpace(window.get()));
+ ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom});
if (dirtyRect) {
INVOKEV(dirtyRect, gRectClassInfo.set,
@@ -181,10 +137,11 @@
}
static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject,
- jlong nativeWindow, jobject canvas) {
+ jlong nativeWindow, jobject canvasObj) {
- Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
- nativeCanvas->setBitmap(SkBitmap());
+ // release the buffer from the canvas
+ ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
+ ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
if (nativeWindow) {
sp<ANativeWindow> window((ANativeWindow*) nativeWindow);
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 0070694..f2ca0a4 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -995,7 +995,7 @@
optional SettingProto display_certification_on = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto display_wps_config = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto networks_available_notification_on = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto carrier_networks_available_notification_on = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 7; reserved "carrier_networks_available_notification_on";
optional SettingProto networks_available_repeat_delay = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto country_code = 9;
optional SettingProto framework_scan_interval_ms = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -1013,7 +1013,7 @@
optional SettingProto watchdog_poor_network_test_enabled = 22 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto suspend_optimizations_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto verbose_logging_enabled = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
- reserved 25; // connected_mac_randomization_enabled
+ reserved 25; reserved "connected_mac_randomization_enabled";
optional SettingProto max_dhcp_retry_count = 26 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto mobile_data_transition_wakelock_timeout_ms = 27 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Controls whether WiFi configurations created by a Device Owner app should
@@ -1025,7 +1025,7 @@
optional SettingProto device_owner_configs_lockdown = 28 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto frequency_band = 29 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto p2p_device_name = 30;
- optional SettingProto reenable_delay_ms = 31 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 31; reserved "reenable_delay_ms";
optional SettingProto ephemeral_out_of_range_timeout_ms = 32 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto on_when_proxy_disconnected = 33 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto bounce_delay_override_ms = 34 [ (android.privacy).dest = DEST_AUTOMATIC ];
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index a1ff9b9..d54b6b0 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -367,10 +367,8 @@
optional int32 id = 3;
optional bool is_proc = 4;
optional bool has_activities = 5;
- oneof ss_kb {
- int64 pss_kb = 6;
- int64 rss_kb = 9;
- }
+ optional int64 pss_kb = 6;
+ optional int64 rss_kb = 9;
optional int64 swap_pss_kb = 7;
repeated MemItem sub_items = 8;
}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index aac144c..79167ab 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -308,6 +308,8 @@
// Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
// ready now.
reserved 1; // skip_not_ready_jobs
+ // Whether or not TimeController will use a non-wakeup alarm for delay constraints.
+ optional bool use_non_wakeup_alarm_for_delay = 2;
}
optional TimeController time_controller = 25;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6a20484..9738759 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1264,6 +1264,15 @@
android:description="@string/permdesc_camera"
android:protectionLevel="dangerous|instant" />
+ <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+ system only camera devices.
+ <p>Protection level: system|signature
+ @hide -->
+ <permission android:name="android.permission.SYSTEM_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_systemCamera"
+ android:description="@string/permdesc_systemCamera"
+ android:protectionLevel="system|signature" />
<!-- ====================================================================== -->
<!-- Permissions for accessing the device sensors -->
@@ -4458,12 +4467,12 @@
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
- android:protectionLevel="signature|textClassifier" />
+ android:protectionLevel="signature|appPredictor" />
<!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
@hide -->
<permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
- android:protectionLevel="signature|textClassifier" />
+ android:protectionLevel="signature|appPredictor" />
<!-- @SystemApi Allows an application to read the runtime profiles of other apps.
@hide <p>Not for use by third-party applications. -->
@@ -4524,6 +4533,13 @@
<permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to
+ ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @hide Permission that allows configuring appops.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MANAGE_APPOPS"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 40c2cbe..ab8a2b0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1703,16 +1703,16 @@
<!-- Add packages here -->
</string-array>
- <!-- The set of system packages on device that are queryable regardless of the contents of their
- manifest. -->
+ <!-- The set of system packages on device that are queryable by any app regardless of the
+ contents of its manifest. -->
<string-array name="config_forceQueryablePackages" translatable="false">
<item>com.android.settings</item>
<item>com.android.providers.settings</item>
<!-- Add packages here -->
</string-array>
- <!-- If true, will force all packages on any system partition as queryable regardless of the
- contents of their manifest. -->
+ <!-- If true, will force all packages on any system partition as queryable by any app regardless
+ of the contents of its manifest. -->
<bool name="config_forceSystemPackagesQueryable">false</bool>
<!-- Component name of the default wallpaper. This will be ImageWallpaper if not
@@ -4286,4 +4286,9 @@
<!-- The list of packages to automatically opt out of refresh rates higher than 60hz because
of known compatibility issues. -->
<string-array name="config_highRefreshRateBlacklist"></string-array>
+
+ <!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to
+ create additional screen real estate outside beyond the keyboard. Note that the user needs
+ to have a confirmed way to dismiss the keyboard when desired. -->
+ <bool name="config_automotiveHideNavBarForKeyboard">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d1d7bf5..2b97bf8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1173,6 +1173,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permdesc_systemCamera">This privileged | system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_vibrate">control vibration</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3377,8 +3382,6 @@
<!-- Notification title for a nearby open wireless network.-->
<string name="wifi_available_title">Connect to open Wi\u2011Fi network</string>
- <!-- Notification title for a nearby carrier wireless network.-->
- <string name="wifi_available_carrier_network_title">Connect to carrier Wi\u2011Fi network</string>
<!-- Notification title when the system is connecting to the specified network. The network name is specified in the notification content. -->
<string name="wifi_available_title_connecting">Connecting to Wi\u2011Fi network</string>
<!-- Notification title when the system has connected to the network. The network name is specified in the notification content. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f7ae453..c37a457 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2070,7 +2070,6 @@
<java-symbol type="plurals" name="wifi_available" />
<java-symbol type="plurals" name="wifi_available_detailed" />
<java-symbol type="string" name="wifi_available_title" />
- <java-symbol type="string" name="wifi_available_carrier_network_title" />
<java-symbol type="string" name="wifi_available_title_connecting" />
<java-symbol type="string" name="wifi_available_title_connected" />
<java-symbol type="string" name="wifi_available_title_failed_to_connect" />
@@ -3847,4 +3846,6 @@
<java-symbol type="string" name="config_factoryResetPackage" />
<java-symbol type="array" name="config_highRefreshRateBlacklist" />
+ <java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
+
</resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 3d9a1d9..7c6271c 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -19,6 +19,7 @@
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,6 +44,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import org.mockito.verification.VerificationWithTimeout;
import java.util.Arrays;
import java.util.HashSet;
@@ -56,6 +58,8 @@
public class StartProgramListUpdatesFanoutTest {
private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout";
+ private static final VerificationWithTimeout CB_TIMEOUT = timeout(100);
+
// Mocks
@Mock IBroadcastRadio mBroadcastRadioMock;
@Mock ITunerSession mHalTunerSessionMock;
@@ -200,10 +204,10 @@
// Adding mDabEnsembleInfo should not update any client.
updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null);
- verify(mAidlTunerCallbackMocks[0], times(1)).onProgramListUpdated(any());
- verify(mAidlTunerCallbackMocks[1], times(2)).onProgramListUpdated(any());
- verify(mAidlTunerCallbackMocks[2], times(1)).onProgramListUpdated(any());
- verify(mAidlTunerCallbackMocks[3], times(2)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(2)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(1)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any());
}
@Test
@@ -240,7 +244,7 @@
// Update the HAL with mModifiedAmFmInfo, and verify only the remaining client is updated.
updateHalProgramInfo(true, Arrays.asList(mModifiedAmFmInfo), null);
- verify(mAidlTunerCallbackMocks[0], times(1)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any());
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true,
Arrays.asList(mModifiedAmFmInfo), null);
@@ -313,6 +317,6 @@
}
ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet,
removedSet);
- verify(clientMock).onProgramListUpdated(expectedChunk);
+ verify(clientMock, CB_TIMEOUT).onProgramListUpdated(expectedChunk);
}
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index bdbf368..97b7ae9 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -544,7 +544,6 @@
Settings.Global.WIFI_ON,
Settings.Global.WIFI_P2P_DEVICE_NAME,
Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
- Settings.Global.WIFI_REENABLE_DELAY_MS,
Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
Settings.Global.WIFI_SAVED_STATE,
Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
@@ -721,7 +720,6 @@
Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
- Settings.Secure.FACE_UNLOCK_EDUCATION_INFO_DISPLAYED,
Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED);
@Test
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java
similarity index 85%
rename from core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
rename to core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java
index eea8c83..5f042d3 100644
--- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
+++ b/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.provider;
+package android.provider.settings.validators;
import static com.google.common.truth.Truth.assertThat;
@@ -23,7 +23,7 @@
import static org.junit.Assert.fail;
import android.platform.test.annotations.Presubmit;
-import android.provider.SettingsValidators.Validator;
+import android.provider.Settings;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -138,7 +138,7 @@
@Test
public void testDiscreteValueValidator() {
String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
- Validator v = new SettingsValidators.DiscreteValueValidator(beerTypes);
+ Validator v = new DiscreteValueValidator(beerTypes);
assertTrue(v.validate("Ale"));
assertTrue(v.validate("American IPA"));
assertTrue(v.validate("Stout"));
@@ -148,14 +148,14 @@
@Test
public void testDiscreteValueValidator_onNullValue_returnsFalse() {
String[] discreteTypes = new String[]{"Type1", "Type2"};
- Validator v = new SettingsValidators.DiscreteValueValidator(discreteTypes);
+ Validator v = new DiscreteValueValidator(discreteTypes);
assertFalse(v.validate(null));
}
@Test
public void testInclusiveIntegerRangeValidator() {
- Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+ Validator v = new InclusiveIntegerRangeValidator(0, 5);
assertTrue(v.validate("0"));
assertTrue(v.validate("2"));
assertTrue(v.validate("5"));
@@ -165,14 +165,14 @@
@Test
public void testInclusiveIntegerRangeValidator_onNullValue_returnsFalse() {
- Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+ Validator v = new InclusiveIntegerRangeValidator(0, 5);
assertFalse(v.validate(null));
}
@Test
public void testInclusiveFloatRangeValidator() {
- Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f);
+ Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
assertTrue(v.validate("0.0"));
assertTrue(v.validate("2.0"));
assertTrue(v.validate("5.0"));
@@ -182,14 +182,14 @@
@Test
public void testInclusiveFloatRangeValidator_onNullValue_returnsFalse() {
- Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f);
+ Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
assertFalse(v.validate(null));
}
@Test
public void testComponentNameListValidator() {
- Validator v = new SettingsValidators.ComponentNameListValidator(",");
+ Validator v = new ComponentNameListValidator(",");
assertTrue(v.validate("com.android.localtransport/.LocalTransport,"
+ "com.google.android.gms/.backup.migrate.service.D2dTransport"));
assertFalse(v.validate("com.google.5android,android"));
@@ -197,21 +197,21 @@
@Test
public void testComponentNameListValidator_onNullValue_returnsFalse() {
- Validator v = new SettingsValidators.ComponentNameListValidator(",");
+ Validator v = new ComponentNameListValidator(",");
assertFalse(v.validate(null));
}
@Test
public void testPackageNameListValidator() {
- Validator v = new SettingsValidators.PackageNameListValidator(",");
+ Validator v = new PackageNameListValidator(",");
assertTrue(v.validate("com.android.localtransport.LocalTransport,com.google.android.gms"));
assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
}
@Test
public void testPackageNameListValidator_onNullValue_returnsFalse() {
- Validator v = new SettingsValidators.PackageNameListValidator(",");
+ Validator v = new PackageNameListValidator(",");
assertFalse(v.validate(null));
}
@@ -256,51 +256,41 @@
@Test
public void testTTSListValidator_withValidInput_returnsTrue() {
- Validator v = new SettingsValidators.TTSListValidator();
-
- assertTrue(v.validate("com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES"));
+ assertTrue(
+ SettingsValidators.TTS_LIST_VALIDATOR.validate(
+ "com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES"));
}
@Test
public void testTTSListValidator_withInvalidInput_returnsFalse() {
- Validator v = new SettingsValidators.TTSListValidator();
-
- assertFalse(v.validate("com.foo.ttsengine:eng-USA,INVALID"));
+ assertFalse(
+ SettingsValidators.TTS_LIST_VALIDATOR.validate(
+ "com.foo.ttsengine:eng-USA,INVALID"));
}
@Test
public void testTTSListValidator_withEmptyInput_returnsFalse() {
- Validator v = new SettingsValidators.TTSListValidator();
-
- assertFalse(v.validate(""));
+ assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(""));
}
@Test
public void testTTSListValidator_withNullInput_returnsFalse() {
- Validator v = new SettingsValidators.TTSListValidator();
-
- assertFalse(v.validate(null));
+ assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(null));
}
@Test
public void testTileListValidator_withValidInput_returnsTrue() {
- Validator v = new SettingsValidators.TileListValidator();
-
- assertTrue(v.validate("1,2,3,4"));
+ assertTrue(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,2,3,4"));
}
@Test
public void testTileListValidator_withMissingValue_returnsFalse() {
- Validator v = new SettingsValidators.TileListValidator();
-
- assertFalse(v.validate("1,,3"));
+ assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,,3"));
}
@Test
public void testTileListValidator_withNullInput_returnsFalse() {
- Validator v = new SettingsValidators.TileListValidator();
-
- assertFalse(v.validate(null));
+ assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate(null));
}
@Test
diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java
new file mode 100644
index 0000000..f1bebfb
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ArraySetTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import androidx.test.filters.LargeTest;
+
+import junit.framework.TestCase;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.ConcurrentModificationException;
+
+/**
+ * Unit tests for ArraySet that don't belong in CTS.
+ */
+@LargeTest
+public class ArraySetTest extends TestCase {
+ private static final String TAG = "ArraySetTest";
+ ArraySet<String> mSet = new ArraySet<>();
+
+ @After
+ public void tearDown() {
+ mSet = null;
+ }
+
+ /**
+ * Attempt to generate a ConcurrentModificationException in ArraySet.
+ * <p>
+ * ArraySet is explicitly documented to be non-thread-safe, yet it's easy to accidentally screw
+ * this up; ArraySet should (in the spirit of the core Java collection types) make an effort to
+ * catch this and throw ConcurrentModificationException instead of crashing somewhere in its
+ * internals.
+ */
+ @Test
+ public void testConcurrentModificationException() throws Exception {
+ final int testDurMs = 10_000;
+ System.out.println("Starting ArraySet concurrency test");
+ new Thread(() -> {
+ int i = 0;
+ while (mSet != null) {
+ try {
+ mSet.add(String.format("key %d", i++));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e);
+ fail("Concurrent modification uncaught, causing indexing failure: " + e);
+ } catch (ClassCastException e) {
+ Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e);
+ fail("Concurrent modification uncaught, causing cache corruption: " + e);
+ } catch (ConcurrentModificationException e) {
+ System.out.println("[successfully caught CME at put #" + i
+ + " size=" + (mSet == null ? "??" : String.valueOf(mSet.size())) + "]");
+ if (i % 200 == 0) {
+ System.out.print(".");
+ }
+ }
+ }
+ }).start();
+ for (int i = 0; i < (testDurMs / 100); i++) {
+ try {
+ if (mSet.size() % 4 == 0) {
+ mSet.clear();
+ }
+ System.out.print("X");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Log.e(TAG, "concurrent modification uncaught, causing indexing failure", e);
+ fail("Concurrent modification uncaught, causing indexing failure: " + e);
+ } catch (ClassCastException e) {
+ Log.e(TAG, "concurrent modification uncaught, causing cache corruption", e);
+ fail("Concurrent modification uncaught, causing cache corruption: " + e);
+ } catch (ConcurrentModificationException e) {
+ System.out.println(
+ "[successfully caught CME at clear #" + i + " size=" + mSet.size() + "]");
+ }
+ }
+ }
+
+ /**
+ * Check to make sure the same operations behave as expected in a single thread.
+ */
+ @Test
+ public void testNonConcurrentAccesses() throws Exception {
+ for (int i = 0; i < 100000; i++) {
+ try {
+ mSet.add(String.format("key %d", i++));
+ if (i % 200 == 0) {
+ System.out.print(".");
+ }
+ if (i % 500 == 0) {
+ mSet.clear();
+ System.out.print("X");
+ }
+ } catch (ConcurrentModificationException e) {
+ Log.e(TAG, "concurrent modification caught on single thread", e);
+ fail();
+ }
+ }
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4187c80..89523d6 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -322,6 +322,8 @@
<permission name="android.permission.SET_WALLPAPER" />
<permission name="android.permission.SET_WALLPAPER_COMPONENT" />
<permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
+ <!-- Permission required to test system only camera devices. -->
+ <permission name="android.permission.SYSTEM_CAMERA" />
<!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
<permission name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"/>
</privapp-permissions>
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 9a27f28..b93759f 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -21,6 +21,7 @@
#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
#include <android/hardware_buffer.h>
+#include <android/native_window.h>
#endif
#include <algorithm>
@@ -30,11 +31,11 @@
namespace uirenderer {
#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
-SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc,
- sk_sp<SkColorSpace> colorSpace) {
+static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t format,
+ sk_sp<SkColorSpace> colorSpace) {
SkColorType colorType = kUnknown_SkColorType;
SkAlphaType alphaType = kOpaque_SkAlphaType;
- switch (bufferDesc.format) {
+ switch (format) {
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
colorType = kN32_SkColorType;
alphaType = kPremul_SkAlphaType;
@@ -56,10 +57,20 @@
alphaType = kPremul_SkAlphaType;
break;
default:
- ALOGV("Unsupported format: %d, return unknown by default", bufferDesc.format);
+ ALOGV("Unsupported format: %d, return unknown by default", format);
break;
}
- return SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType, alphaType, colorSpace);
+ return SkImageInfo::Make(width, height, colorType, alphaType, colorSpace);
+}
+
+SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer,
+ sk_sp<SkColorSpace> colorSpace) {
+ return createImageInfo(buffer.width, buffer.height, buffer.format, colorSpace);
+}
+
+SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc,
+ sk_sp<SkColorSpace> colorSpace) {
+ return createImageInfo(bufferDesc.width, bufferDesc.height, bufferDesc.format, colorSpace);
}
#endif
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 7c2378a..07b5ec8 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -25,6 +25,7 @@
#include <SkColorSpace.h>
#include <SkImageInfo.h>
+struct ANativeWindow_Buffer;
struct AHardwareBuffer_Desc;
namespace android {
@@ -91,8 +92,13 @@
return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f);
}
+#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
+ANDROID_API SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer,
+ sk_sp<SkColorSpace> colorSpace);
+
SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc,
sk_sp<SkColorSpace> colorSpace);
+#endif
android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType);
ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 89d3cc4..16f2917 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -20,6 +20,7 @@
],
shared_libs: [
+ "libbinder",
"libcutils",
"liblog",
"libutils",
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index c1868d3..fd386e9 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -245,7 +245,8 @@
if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
|| (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
| DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
- | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) {
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID
+ | DIRTY_ICON_STYLE))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -274,6 +275,21 @@
update.state.transformationMatrix.dtdy);
}
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible
+ || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
+ Parcel p;
+ p.writeInt32(update.state.icon.style);
+ p.writeFloat(update.state.icon.hotSpotX);
+ p.writeFloat(update.state.icon.hotSpotY);
+
+ // Pass cursor metadata in the sprite surface so that when Android is running as a
+ // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
+ // update mouse cursor in the host OS.
+ t.setMetadata(
+ update.state.surfaceControl, METADATA_MOUSE_CURSOR, p);
+ }
+
int32_t surfaceLayer = mOverlayLayer + update.state.layer;
if (wantSurfaceVisibleAndDrawn
&& (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
@@ -397,9 +413,14 @@
} else {
dirty = DIRTY_BITMAP;
}
+
+ if (mLocked.state.icon.style != icon.style) {
+ mLocked.state.icon.style = icon.style;
+ dirty |= DIRTY_ICON_STYLE;
+ }
} else if (mLocked.state.icon.isValid()) {
mLocked.state.icon.bitmap.reset();
- dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE;
} else {
return; // setting to invalid icon and already invalid so nothing to do
}
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 5b216f5..79a904f 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -55,11 +55,12 @@
* Icon that a sprite displays, including its hotspot.
*/
struct SpriteIcon {
- inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
- inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
- bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+ inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) { }
+ inline SpriteIcon(const SkBitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
SkBitmap bitmap;
+ int32_t style;
float hotSpotX;
float hotSpotY;
@@ -69,11 +70,12 @@
bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(),
0, 0);
}
- return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+ return SpriteIcon(bitmapCopy, style, hotSpotX, hotSpotY);
}
inline void reset() {
bitmap.reset();
+ style = 0;
hotSpotX = 0;
hotSpotY = 0;
}
@@ -149,15 +151,15 @@
SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
/* Creates a new sprite, initially invisible. */
- sp<Sprite> createSprite();
+ virtual sp<Sprite> createSprite();
/* Opens or closes a transaction to perform a batch of sprite updates as part of
* a single operation such as setPosition and setAlpha. It is not necessary to
* open a transaction when updating a single property.
* Calls to openTransaction() nest and must be matched by an equal number
* of calls to closeTransaction(). */
- void openTransaction();
- void closeTransaction();
+ virtual void openTransaction();
+ virtual void closeTransaction();
private:
enum {
@@ -174,6 +176,7 @@
DIRTY_VISIBILITY = 1 << 5,
DIRTY_HOTSPOT = 1 << 6,
DIRTY_DISPLAY_ID = 1 << 7,
+ DIRTY_ICON_STYLE = 1 << 8,
};
/* Describes the state of a sprite.
diff --git a/libs/input/TEST_MAPPING b/libs/input/TEST_MAPPING
new file mode 100644
index 0000000..fe74c62
--- /dev/null
+++ b/libs/input/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libinputservice_test"
+ }
+ ]
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
new file mode 100644
index 0000000..e83b2a7
--- /dev/null
+++ b/libs/input/tests/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2019 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.
+
+cc_test {
+ name: "libinputservice_test",
+ srcs: [
+ "PointerController_test.cpp",
+ ],
+ shared_libs: [
+ "libinputservice",
+ "libgui",
+ "libhwui",
+ "libutils",
+ ],
+ static_libs: [
+ "libgmock",
+ "libgtest",
+ ],
+ header_libs: [
+ "libbase_headers",
+ "libinputflinger_headers",
+ ],
+ include_dirs: [
+ "frameworks/base/libs",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
new file mode 100644
index 0000000..92efb4e
--- /dev/null
+++ b/libs/input/tests/PointerController_test.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mocks/MockSprite.h"
+#include "mocks/MockSpriteController.h"
+
+#include <input/PointerController.h>
+#include <input/SpriteController.h>
+
+#include <atomic>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <thread>
+
+namespace android {
+
+enum TestCursorType {
+ CURSOR_TYPE_DEFAULT = 0,
+ CURSOR_TYPE_HOVER,
+ CURSOR_TYPE_TOUCH,
+ CURSOR_TYPE_ANCHOR,
+ CURSOR_TYPE_ADDITIONAL_1,
+ CURSOR_TYPE_ADDITIONAL_2,
+ CURSOR_TYPE_CUSTOM = -1,
+};
+
+using ::testing::AllOf;
+using ::testing::Field;
+using ::testing::NiceMock;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::Test;
+
+std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) {
+ return std::make_pair(type * 10, type * 10 + 5);
+}
+
+class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
+public:
+ virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
+ virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
+ virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
+ std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
+ virtual int32_t getDefaultPointerIconId() override;
+ virtual int32_t getCustomPointerIconId() override;
+
+private:
+ void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
+};
+
+void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
+ loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
+}
+
+void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
+ int32_t) {
+ loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
+ loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
+ loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
+}
+
+void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
+ std::map<int32_t, SpriteIcon>* outResources,
+ std::map<int32_t, PointerAnimation>* outAnimationResources,
+ int32_t) {
+ SpriteIcon icon;
+ PointerAnimation anim;
+
+ for (int32_t cursorType : {CURSOR_TYPE_ADDITIONAL_1, CURSOR_TYPE_ADDITIONAL_2}) {
+ loadPointerIconForType(&icon, cursorType);
+ anim.animationFrames.push_back(icon);
+ anim.durationPerFrame = 10;
+ (*outResources)[cursorType] = icon;
+ (*outAnimationResources)[cursorType] = anim;
+ }
+}
+
+int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
+ return CURSOR_TYPE_DEFAULT;
+}
+
+int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() {
+ return CURSOR_TYPE_CUSTOM;
+}
+
+void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) {
+ icon->style = type;
+ std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type);
+ icon->hotSpotX = hotSpot.first;
+ icon->hotSpotY = hotSpot.second;
+}
+
+class PointerControllerTest : public Test {
+protected:
+ PointerControllerTest();
+ ~PointerControllerTest();
+
+ sp<MockSprite> mPointerSprite;
+ sp<MockPointerControllerPolicyInterface> mPolicy;
+ sp<MockSpriteController> mSpriteController;
+ sp<PointerController> mPointerController;
+
+private:
+ void loopThread();
+
+ std::atomic<bool> mRunning = true;
+ class MyLooper : public Looper {
+ public:
+ MyLooper() : Looper(false) {}
+ ~MyLooper() = default;
+ };
+ sp<MyLooper> mLooper;
+ std::thread mThread;
+};
+
+PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
+ mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
+
+ mSpriteController = new NiceMock<MockSpriteController>(mLooper);
+ mPolicy = new MockPointerControllerPolicyInterface();
+
+ EXPECT_CALL(*mSpriteController, createSprite())
+ .WillOnce(Return(mPointerSprite));
+
+ mPointerController = new PointerController(mPolicy, mLooper, mSpriteController);
+
+ DisplayViewport viewport;
+ viewport.displayId = ADISPLAY_ID_DEFAULT;
+ viewport.logicalRight = 1600;
+ viewport.logicalBottom = 1200;
+ viewport.physicalRight = 800;
+ viewport.physicalBottom = 600;
+ viewport.deviceWidth = 400;
+ viewport.deviceHeight = 300;
+ mPointerController->setDisplayViewport(viewport);
+}
+
+PointerControllerTest::~PointerControllerTest() {
+ mRunning.store(false, std::memory_order_relaxed);
+ mThread.join();
+}
+
+void PointerControllerTest::loopThread() {
+ Looper::setForThread(mLooper);
+
+ while (mRunning.load(std::memory_order_relaxed)) {
+ mLooper->pollOnce(100);
+ }
+}
+
+TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
+ mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
+
+ std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT);
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite, setIcon(
+ AllOf(
+ Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->reloadPointerResources();
+}
+
+TEST_F(PointerControllerTest, updatePointerIcon) {
+ mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
+
+ int32_t type = CURSOR_TYPE_ADDITIONAL_1;
+ std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite, setIcon(
+ AllOf(
+ Field(&SpriteIcon::style, type),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->updatePointerIcon(type);
+}
+
+TEST_F(PointerControllerTest, setCustomPointerIcon) {
+ mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE);
+
+ int32_t style = CURSOR_TYPE_CUSTOM;
+ float hotSpotX = 15;
+ float hotSpotY = 20;
+
+ SpriteIcon icon;
+ icon.style = style;
+ icon.hotSpotX = hotSpotX;
+ icon.hotSpotY = hotSpotY;
+
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite, setIcon(
+ AllOf(
+ Field(&SpriteIcon::style, style),
+ Field(&SpriteIcon::hotSpotX, hotSpotX),
+ Field(&SpriteIcon::hotSpotY, hotSpotY))));
+ mPointerController->setCustomPointerIcon(icon);
+}
+
+} // namespace android
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
new file mode 100644
index 0000000..013b79c
--- /dev/null
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef _MOCK_SPRITE_H
+#define _MOCK_SPRITE_H
+
+#include <input/SpriteController.h>
+
+#include <gmock/gmock.h>
+
+namespace android {
+
+class MockSprite : public Sprite {
+public:
+ virtual ~MockSprite() = default;
+
+ MOCK_METHOD(void, setIcon, (const SpriteIcon& icon), (override));
+ MOCK_METHOD(void, setVisible, (bool), (override));
+ MOCK_METHOD(void, setPosition, (float, float), (override));
+ MOCK_METHOD(void, setLayer, (int32_t), (override));
+ MOCK_METHOD(void, setAlpha, (float), (override));
+ MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
+ MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+};
+
+} // namespace android
+
+#endif // _MOCK_SPRITE_H
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
new file mode 100644
index 0000000..a034f66
--- /dev/null
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef _MOCK_SPRITE_CONTROLLER_H
+#define _MOCK_SPRITE_CONTROLLER_H
+
+#include "MockSprite.h"
+
+#include <input/SpriteController.h>
+
+namespace android {
+
+class MockSpriteController : public SpriteController {
+
+public:
+ MockSpriteController(sp<Looper> looper) : SpriteController(looper, 0) {}
+ ~MockSpriteController() {}
+
+ MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
+ MOCK_METHOD(void, openTransaction, (), (override));
+ MOCK_METHOD(void, closeTransaction, (), (override));
+};
+
+} // namespace android
+
+#endif // _MOCK_SPRITE_CONTROLLER_H
diff --git a/media/Android.bp b/media/Android.bp
index 29064ad..ef32239 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -67,6 +67,7 @@
"apex/java/android/media/Session2Link.java",
"apex/java/android/media/Session2Token.java",
],
+ path: "apex/java",
}
filegroup {
@@ -79,6 +80,7 @@
"apex/java/android/media/IMediaSession2.aidl",
"apex/java/android/media/IMediaSession2Service.aidl",
],
+ path: "apex/java",
}
filegroup {
@@ -98,6 +100,7 @@
"apex/java/android/media/BufferingParams.java",
"apex/java/android/media/ProxyDataSourceCallback.java",
],
+ path: "apex/java",
}
metalava_updatable_media_args = " --error UnhiddenSystemApi " +
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 148ffaf..55583d5 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -37,6 +37,7 @@
import libcore.io.Streams;
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -1866,14 +1867,17 @@
FileInputStream in = null;
FileOutputStream out = null;
+ File originalFile = null;
+ if (mFilename != null) {
+ originalFile = new File(mFilename);
+ }
File tempFile = null;
try {
// Move the original file to temporary file.
if (mFilename != null) {
tempFile = new File(mFilename + ".tmp");
- File originalFile = new File(mFilename);
if (!originalFile.renameTo(tempFile)) {
- throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
+ throw new IOException("Couldn't rename to " + tempFile.getAbsolutePath());
}
} else if (mSeekableFileDescriptor != null) {
tempFile = File.createTempFile("temp", "jpg");
@@ -1882,8 +1886,8 @@
out = new FileOutputStream(tempFile);
Streams.copy(in, out);
}
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
+ } catch (Exception e) {
+ throw new IOException("Failed to copy original file to temp file", e);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
@@ -1900,9 +1904,18 @@
Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
out = new FileOutputStream(mSeekableFileDescriptor);
}
- saveJpegAttributes(in, out);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
+ try (BufferedInputStream bufferedIn = new BufferedInputStream(in);
+ BufferedOutputStream bufferedOut = new BufferedOutputStream(out)) {
+ saveJpegAttributes(bufferedIn, bufferedOut);
+ }
+ } catch (Exception e) {
+ if (mFilename != null) {
+ if (!tempFile.renameTo(originalFile)) {
+ throw new IOException("Couldn't restore original file: "
+ + originalFile.getAbsolutePath());
+ }
+ }
+ throw new IOException("Failed to save new file", e);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 471fa2c..7eec8d9 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -16,17 +16,20 @@
package android.media;
+import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
+import android.content.ContentProviderClient;
import android.content.Context;
-import android.content.Intent;
import android.content.ServiceConnection;
-import android.media.IMediaScannerListener;
-import android.media.IMediaScannerService;
import android.net.Uri;
+import android.os.Build;
import android.os.IBinder;
-import android.os.RemoteException;
+import android.provider.MediaStore;
import android.util.Log;
+import com.android.internal.os.BackgroundThread;
+
+import java.io.File;
/**
* MediaScannerConnection provides a way for applications to pass a
@@ -38,20 +41,24 @@
* to the client of the MediaScannerConnection class.
*/
public class MediaScannerConnection implements ServiceConnection {
-
private static final String TAG = "MediaScannerConnection";
- private Context mContext;
- private MediaScannerConnectionClient mClient;
- private IMediaScannerService mService;
- private boolean mConnected; // true if connect() has been called since last disconnect()
+ private final Context mContext;
+ private final MediaScannerConnectionClient mClient;
+ private ContentProviderClient mProvider;
+
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ private IMediaScannerService mService;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ private boolean mConnected;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() {
+ @Override
public void scanCompleted(String path, Uri uri) {
- MediaScannerConnectionClient client = mClient;
- if (client != null) {
- client.onScanCompleted(path, uri);
- }
}
};
@@ -81,15 +88,6 @@
* MediaScanner service has been established.
*/
public void onMediaScannerConnected();
-
- /**
- * Called to notify the client when the media scanner has finished
- * scanning a file.
- * @param path the path to the file that has been scanned.
- * @param uri the Uri for the file if the scanning operation succeeded
- * and the file was added to the media database, or null if scanning failed.
- */
- public void onScanCompleted(String path, Uri uri);
}
/**
@@ -111,13 +109,12 @@
*/
public void connect() {
synchronized (this) {
- if (!mConnected) {
- Intent intent = new Intent(IMediaScannerService.class.getName());
- intent.setComponent(
- new ComponentName("com.android.providers.media",
- "com.android.providers.media.MediaScannerService"));
- mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
- mConnected = true;
+ if (mProvider == null) {
+ mProvider = mContext.getContentResolver()
+ .acquireContentProviderClient(MediaStore.AUTHORITY);
+ if (mClient != null) {
+ mClient.onMediaScannerConnected();
+ }
}
}
}
@@ -127,22 +124,9 @@
*/
public void disconnect() {
synchronized (this) {
- if (mConnected) {
- if (false) {
- Log.v(TAG, "Disconnecting from Media Scanner");
- }
- try {
- mContext.unbindService(this);
- if (mClient instanceof ClientProxy) {
- mClient = null;
- }
- mService = null;
- } catch (IllegalArgumentException ex) {
- if (false) {
- Log.v(TAG, "disconnect failed: " + ex);
- }
- }
- mConnected = false;
+ if (mProvider != null) {
+ mProvider.close();
+ mProvider = null;
}
}
}
@@ -152,7 +136,7 @@
* @return true if we are connected, false otherwise
*/
public synchronized boolean isConnected() {
- return (mService != null && mConnected);
+ return (mProvider != null);
}
/**
@@ -166,55 +150,15 @@
*/
public void scanFile(String path, String mimeType) {
synchronized (this) {
- if (mService == null || !mConnected) {
+ if (mProvider == null) {
throw new IllegalStateException("not connected to MediaScannerService");
}
- try {
- if (false) {
- Log.v(TAG, "Scanning file " + path);
+ BackgroundThread.getExecutor().execute(() -> {
+ final Uri uri = scanFileQuietly(mProvider, new File(path));
+ if (mClient != null) {
+ mClient.onScanCompleted(path, uri);
}
- mService.requestScanFile(path, mimeType, mListener);
- } catch (RemoteException e) {
- if (false) {
- Log.d(TAG, "Failed to scan file " + path);
- }
- }
- }
- }
-
- static class ClientProxy implements MediaScannerConnectionClient {
- final String[] mPaths;
- final String[] mMimeTypes;
- final OnScanCompletedListener mClient;
- MediaScannerConnection mConnection;
- int mNextPath;
-
- ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
- mPaths = paths;
- mMimeTypes = mimeTypes;
- mClient = client;
- }
-
- public void onMediaScannerConnected() {
- scanNextPath();
- }
-
- public void onScanCompleted(String path, Uri uri) {
- if (mClient != null) {
- mClient.onScanCompleted(path, uri);
- }
- scanNextPath();
- }
-
- void scanNextPath() {
- if (mNextPath >= mPaths.length) {
- mConnection.disconnect();
- mConnection = null;
- return;
- }
- String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null;
- mConnection.scanFile(mPaths[mNextPath], mimeType);
- mNextPath++;
+ });
}
}
@@ -237,36 +181,76 @@
*/
public static void scanFile(Context context, String[] paths, String[] mimeTypes,
OnScanCompletedListener callback) {
- ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
- MediaScannerConnection connection = new MediaScannerConnection(context, client);
- client.mConnection = connection;
- connection.connect();
- }
-
- /**
- * Part of the ServiceConnection interface. Do not call.
- */
- public void onServiceConnected(ComponentName className, IBinder service) {
- if (false) {
- Log.v(TAG, "Connected to Media Scanner");
- }
- synchronized (this) {
- mService = IMediaScannerService.Stub.asInterface(service);
- if (mService != null && mClient != null) {
- mClient.onMediaScannerConnected();
+ BackgroundThread.getExecutor().execute(() -> {
+ try (ContentProviderClient client = context.getContentResolver()
+ .acquireContentProviderClient(MediaStore.AUTHORITY)) {
+ for (String path : paths) {
+ final Uri uri = scanFileQuietly(client, new File(path));
+ if (callback != null) {
+ callback.onScanCompleted(path, uri);
+ }
+ }
}
+ });
+ }
+
+ private static Uri scanFileQuietly(ContentProviderClient client, File file) {
+ Uri uri = null;
+ try {
+ uri = MediaStore.scanFile(client, file);
+ Log.d(TAG, "Scanned " + file + " to " + uri);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to scan " + file + ": " + e);
+ }
+ return uri;
+ }
+
+ @Deprecated
+ static class ClientProxy implements MediaScannerConnectionClient {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ final String[] mPaths;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ final String[] mMimeTypes;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ final OnScanCompletedListener mClient;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ MediaScannerConnection mConnection;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ int mNextPath;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
+ mPaths = paths;
+ mMimeTypes = mimeTypes;
+ mClient = client;
+ }
+
+ @Override
+ public void onMediaScannerConnected() {
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+ void scanNextPath() {
}
}
/**
* Part of the ServiceConnection interface. Do not call.
*/
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // No longer needed
+ }
+
+ /**
+ * Part of the ServiceConnection interface. Do not call.
+ */
+ @Override
public void onServiceDisconnected(ComponentName className) {
- if (false) {
- Log.v(TAG, "Disconnected from Media Scanner");
- }
- synchronized (this) {
- mService = null;
- }
+ // No longer needed
}
}
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index be0d966..792a2ba 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -272,15 +272,6 @@
@UnsupportedAppUsage
public Metadata() { }
- // Have to declare protected for finalize() since it is protected
- // in the base class Object.
- @Override
- protected void finalize() throws Throwable {
- if (mParcel != null) {
- mParcel.recycle();
- }
- }
-
/**
* Go over all the records, collecting metadata keys and records'
* type field offset in the Parcel. These are stored in
@@ -427,10 +418,6 @@
parcel.setDataPosition(pin);
return false;
}
-
- if (mParcel != null) {
- mParcel.recycle();
- }
mParcel = parcel;
return true;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 97fbea6..7ea83f5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -32,7 +32,10 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
import android.util.Log;
+import android.view.Display;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -87,8 +90,7 @@
/**
* A status bar (and navigation bar) tailored for the automotive use case.
*/
-public class CarStatusBar extends StatusBar implements
- CarBatteryController.BatteryViewHandler {
+public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
private static final String TAG = "CarStatusBar";
// used to calculate how fast to open or close the window
private static final float DEFAULT_FLING_VELOCITY = 0;
@@ -169,6 +171,9 @@
private boolean mIsSwipingVerticallyToClose;
// Whether heads-up notifications should be shown when shade is open.
private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
+ // If the nav bar should be hidden when the soft keyboard is visible.
+ private boolean mHideNavBarForKeyboard;
+ private boolean mBottomNavBarVisible;
private final CarPowerStateListener mCarPowerStateListener =
(int state) -> {
@@ -190,6 +195,17 @@
// builds the nav bar
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
+
+ // Keyboard related setup, before nav bars are created.
+ mHideNavBarForKeyboard = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
+ mBottomNavBarVisible = false;
+
+ // Need to initialize screen lifecycle before calling super.start - before switcher is
+ // created.
+ mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
+ mScreenLifecycle.addObserver(mScreenObserver);
+
super.start();
mTaskStackListener = new TaskStackListenerImpl();
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
@@ -236,9 +252,6 @@
mPowerManagerHelper.connectToCarService();
mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
-
- mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
- mScreenLifecycle.addObserver(mScreenObserver);
}
/**
@@ -720,6 +733,13 @@
buildNavBarContent();
attachNavBarWindows();
+ // Try setting up the initial state of the nav bar if applicable.
+ if (result != null) {
+ setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken,
+ result.mImeWindowVis, result.mImeBackDisposition,
+ result.mShowImeSwitcher);
+ }
+
// There has been a car customized nav bar on the default display, so just create nav bars
// on external displays.
mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result);
@@ -758,22 +778,33 @@
}
- private void attachNavBarWindows() {
-
- if (mShowBottom) {
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("CarNavigationBar");
- lp.windowAnimations = 0;
- mWindowManager.addView(mNavigationBarWindow, lp);
+ /**
+ * We register for soft keyboard visibility events such that we can hide the navigation bar
+ * giving more screen space to the IME. Note: this is optional and controlled by
+ * {@code com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard}.
+ */
+ @Override
+ public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ boolean showImeSwitcher) {
+ if (!mHideNavBarForKeyboard) {
+ return;
}
+ if (mContext.getDisplay().getDisplayId() != displayId) {
+ return;
+ }
+
+ boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
+ if (!isKeyboardVisible) {
+ attachBottomNavBarWindow();
+ } else {
+ detachBottomNavBarWindow();
+ }
+ }
+
+ private void attachNavBarWindows() {
+ attachBottomNavBarWindow();
+
if (mShowLeft) {
int width = mContext.getResources().getDimensionPixelSize(
R.dimen.car_left_navigation_bar_width);
@@ -808,7 +839,49 @@
rightlp.gravity = Gravity.RIGHT;
mWindowManager.addView(mRightNavigationBarWindow, rightlp);
}
+ }
+ /**
+ * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of
+ * attaching the bottom nav bar.
+ */
+ protected void attachBottomNavBarWindow() {
+ if (!mShowBottom) {
+ return;
+ }
+
+ if (mBottomNavBarVisible) {
+ return;
+ }
+ mBottomNavBarVisible = true;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("CarNavigationBar");
+ lp.windowAnimations = 0;
+ mWindowManager.addView(mNavigationBarWindow, lp);
+ }
+
+ /**
+ * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of
+ * detaching the bottom nav bar.
+ */
+ protected void detachBottomNavBarWindow() {
+ if (!mShowBottom) {
+ return;
+ }
+
+ if (!mBottomNavBarVisible) {
+ return;
+ }
+ mBottomNavBarVisible = false;
+ mWindowManager.removeView(mNavigationBarWindow);
}
private void buildBottomBar(int layout) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 077f7ec..cf286bd 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -20,6 +20,8 @@
import android.gsi.GsiProgress;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
import android.os.image.DynamicSystemManager;
import android.util.Log;
import android.webkit.URLUtil;
@@ -28,11 +30,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
-import java.util.Arrays;
import java.util.Locale;
import java.util.zip.GZIPInputStream;
-
class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
private static final String TAG = "InstallationAsyncTask";
@@ -125,28 +125,26 @@
Thread.sleep(10);
}
-
if (mInstallationSession == null) {
- throw new IOException("Failed to start installation with requested size: "
- + (mSystemSize + mUserdataSize));
+ throw new IOException(
+ "Failed to start installation with requested size: "
+ + (mSystemSize + mUserdataSize));
}
installedSize = mUserdataSize;
+ MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE);
byte[] bytes = new byte[READ_BUFFER_SIZE];
-
+ mInstallationSession.setAshmem(
+ new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE);
int numBytesRead;
-
Log.d(TAG, "Start installation loop");
while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
+ memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
if (isCancelled()) {
break;
}
-
- byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE
- ? bytes : Arrays.copyOf(bytes, numBytesRead);
-
- if (!mInstallationSession.write(writeBuffer)) {
+ if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
throw new IOException("Failed write() to DynamicSystem");
}
@@ -157,7 +155,6 @@
reportedInstalledSize = installedSize;
}
}
-
return null;
} catch (Exception e) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 2286f4c..36e945f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,7 +34,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.SettingsValidators.Validator;
+import android.provider.settings.validators.Validator;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.BackupUtils;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a3b0e6b..00b2563 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1538,9 +1538,6 @@
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
GlobalSettingsProto.Wifi.NETWORKS_AVAILABLE_NOTIFICATION_ON);
dumpSetting(s, p,
- Settings.Global.WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- GlobalSettingsProto.Wifi.CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON);
- dumpSetting(s, p,
Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
GlobalSettingsProto.Wifi.NETWORKS_AVAILABLE_REPEAT_DELAY);
dumpSetting(s, p,
@@ -1607,9 +1604,6 @@
Settings.Global.WIFI_P2P_DEVICE_NAME,
GlobalSettingsProto.Wifi.P2P_DEVICE_NAME);
dumpSetting(s, p,
- Settings.Global.WIFI_REENABLE_DELAY_MS,
- GlobalSettingsProto.Wifi.REENABLE_DELAY_MS);
- dumpSetting(s, p,
Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
GlobalSettingsProto.Wifi.EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7016d30..e492e28 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -70,7 +70,7 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
-import android.provider.SettingsValidators;
+import android.provider.settings.validators.Validator;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -314,11 +314,6 @@
public boolean onCreate() {
Settings.setInSystemServer();
- // fail to boot if there're any backed up settings that don't have a non-null validator
- ensureAllBackedUpSystemSettingsHaveValidators();
- ensureAllBackedUpGlobalSettingsHaveValidators();
- ensureAllBackedUpSecureSettingsHaveValidators();
-
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -338,57 +333,6 @@
return true;
}
- private void ensureAllBackedUpSystemSettingsHaveValidators() {
- String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
- Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
-
- failToBootIfOffendersPresent(offenders, "Settings.System");
- }
-
- private void ensureAllBackedUpGlobalSettingsHaveValidators() {
- String offenders = getOffenders(concat(Settings.Global.SETTINGS_TO_BACKUP,
- Settings.Global.LEGACY_RESTORE_SETTINGS), Settings.Global.VALIDATORS);
-
- failToBootIfOffendersPresent(offenders, "Settings.Global");
- }
-
- private void ensureAllBackedUpSecureSettingsHaveValidators() {
- String offenders = getOffenders(concat(Settings.Secure.SETTINGS_TO_BACKUP,
- Settings.Secure.LEGACY_RESTORE_SETTINGS), Settings.Secure.VALIDATORS);
-
- failToBootIfOffendersPresent(offenders, "Settings.Secure");
- }
-
- private void failToBootIfOffendersPresent(String offenders, String settingsType) {
- if (offenders.length() > 0) {
- throw new RuntimeException("All " + settingsType + " settings that are backed up"
- + " have to have a non-null validator, but those don't: " + offenders);
- }
- }
-
- private String getOffenders(String[] settingsToBackup, Map<String,
- SettingsValidators.Validator> validators) {
- StringBuilder offenders = new StringBuilder();
- for (String setting : settingsToBackup) {
- if (validators.get(setting) == null) {
- offenders.append(setting).append(" ");
- }
- }
- return offenders.toString();
- }
-
- private final String[] concat(String[] first, String[] second) {
- if (second == null || second.length == 0) {
- return first;
- }
- final int firstLen = first.length;
- final int secondLen = second.length;
- String[] both = new String[firstLen + secondLen];
- System.arraycopy(first, 0, both, 0, firstLen);
- System.arraycopy(second, 0, both, firstLen, secondLen);
- return both;
- }
-
@Override
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
@@ -1773,7 +1717,7 @@
}
private void validateSystemSettingValue(String name, String value) {
- SettingsValidators.Validator validator = Settings.System.VALIDATORS.get(name);
+ Validator validator = Settings.System.VALIDATORS.get(name);
if (validator != null && !validator.validate(value)) {
throw new IllegalArgumentException("Invalid value: " + value
+ " for setting: " + name);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bb9a5e4..b2ff4b3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -169,6 +169,9 @@
<uses-permission android:name="android.permission.MANAGE_SENSORS" />
<uses-permission android:name="android.permission.MANAGE_AUDIO_POLICY" />
<uses-permission android:name="android.permission.MANAGE_CAMERA" />
+ <!-- Permissions needed to test system only camera devices -->
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
<!-- Permission needed to enable/disable Bluetooth/Wifi -->
<uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" />
<uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" />
@@ -235,6 +238,7 @@
<activity
android:name=".BugreportWarningActivity"
+ android:theme="@android:style/Theme.DeviceDefault.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:exported="false" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 58e1d47..8c0108d 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -352,10 +352,10 @@
private final BugreportInfo mInfo;
- BugreportCallbackImpl(String name) {
+ BugreportCallbackImpl(String name, @Nullable String title, @Nullable String description) {
// pid not used in this workflow, so setting default = 0
mInfo = new BugreportInfo(mContext, 0 /* pid */, name,
- 100 /* max progress*/);
+ 100 /* max progress*/, title, description);
}
@Override
@@ -578,6 +578,8 @@
}
int bugreportType = intent.getIntExtra(EXTRA_BUGREPORT_TYPE,
BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+ String shareTitle = intent.getStringExtra(EXTRA_TITLE);
+ String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
ParcelFileDescriptor screenshotFd = createReadWriteFile(BUGREPORT_DIR,
bugreportName + ".png");
@@ -595,7 +597,8 @@
+ " bugreport file fd: " + bugreportFd
+ " screenshot file fd: " + screenshotFd);
- BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(bugreportName);
+ BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(bugreportName,
+ shareTitle, shareDescription);
try {
mBugreportManager.startBugreport(bugreportFd, screenshotFd,
new BugreportParams(bugreportType), executor, bugreportCallback);
@@ -982,7 +985,10 @@
}
screenshotFile = null;
}
- onBugreportFinished(id, bugreportFile, screenshotFile, info.title, info.description, max);
+ // TODO: Since we are passing id to the function, it should be able to find the info linked
+ // to the id and therefore use the value of shareTitle and shareDescription.
+ onBugreportFinished(id, bugreportFile, screenshotFile, info.shareTitle,
+ info.shareDescription, max);
}
@@ -1844,6 +1850,14 @@
String title;
/**
+ * One-line summary of the bug; when set, will be used as the subject of the
+ * {@link Intent#ACTION_SEND_MULTIPLE} intent. This is the predefined title which is
+ * set initially when the request to take a bugreport is made. This overrides any changes
+ * in the title that the user makes after the bugreport starts.
+ */
+ String shareTitle;
+
+ /**
* User-provided, detailed description of the bugreport; when set, will be added to the body
* of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
*/
@@ -1906,7 +1920,9 @@
int screenshotCounter;
/**
- * Descriptive text that will be shown to the user in the notification message.
+ * Descriptive text that will be shown to the user in the notification message. This is the
+ * predefined description which is set initially when the request to take a bugreport is
+ * made.
*/
String shareDescription;
@@ -1914,18 +1930,21 @@
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
*/
BugreportInfo(Context context, int id, int pid, String name, int max) {
- this(context, pid, name, max);
+ this(context, pid, name, max, null, null);
this.id = id;
}
/**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, int pid, String name, int max) {
+ BugreportInfo(Context context, int pid, String name, int max, @Nullable String shareTitle,
+ @Nullable String shareDescription) {
this.context = context;
this.pid = pid;
this.name = name;
this.max = this.realMax = max;
+ this.shareTitle = shareTitle == null ? "" : shareTitle;
+ this.shareDescription = shareDescription == null ? "" : shareDescription;
}
/**
@@ -2019,6 +2038,7 @@
.append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
.append(" addedDetailsToZip: ").append(addedDetailsToZip)
.append("\n\tshareDescription: ").append(shareDescription)
+ .append("\n\tshareTitle: ").append(shareTitle)
.toString();
}
@@ -2046,6 +2066,7 @@
finished = in.readInt() == 1;
screenshotCounter = in.readInt();
shareDescription = in.readString();
+ shareTitle = in.readString();
}
@Override
@@ -2071,6 +2092,7 @@
dest.writeInt(finished ? 1 : 0);
dest.writeInt(screenshotCounter);
dest.writeString(shareDescription);
+ dest.writeString(shareTitle);
}
@Override
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
new file mode 100644
index 0000000..3be7ca9
--- /dev/null
+++ b/packages/SoundPicker/Android.bp
@@ -0,0 +1,18 @@
+android_app {
+ name: "SoundPicker",
+ manifest: "AndroidManifest.xml",
+
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ platform_apis: true,
+ certificate: "media",
+ privileged: true,
+}
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
new file mode 100644
index 0000000..9d08182
--- /dev/null
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.soundpicker"
+ android:sharedUserId="android.media">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <application
+ android:allowBackup="false"
+ android:supportsRtl="true">
+ <receiver android:name="RingtoneReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+ </intent-filter>
+ </receiver>
+
+ <service android:name="RingtoneOverlayService" />
+
+ <activity android:name="RingtonePickerActivity"
+ android:theme="@style/PickerDialogTheme"
+ android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RINGTONE_PICKER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SoundPicker/res/drawable/ic_add.xml b/packages/SoundPicker/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/drawable/ic_add_padded.xml b/packages/SoundPicker/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_add"
+ android:insetTop="4dp"
+ android:insetRight="4dp"
+ android:insetBottom="4dp"
+ android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..6f91d77
--- /dev/null
+++ b/packages/SoundPicker/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<!--
+ Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+ Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@null"
+ android:textColor="?android:attr/colorAccent"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:drawableStart="@drawable/ic_add_padded"
+ android:drawablePadding="8dp"
+ android:ellipsize="marquee"
+ android:visibility="gone" />
diff --git a/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..0e11621
--- /dev/null
+++ b/packages/SoundPicker/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<com.android.providers.media.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ >
+
+ <CheckedTextView
+ android:id="@+id/checked_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+ android:drawablePadding="8dp"
+ android:ellipsize="marquee"
+ android:layout_toLeftOf="@+id/work_icon"
+ android:maxLines="3" />
+
+ <ImageView
+ android:id="@id/work_icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="20dp" />
+</com.android.providers.media.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..14421c9
--- /dev/null
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground">
+
+<ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="24dp"
+ android:layout_marginLeft="24dp"
+ android:src="@drawable/ic_add" />
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/add_new_sound_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:text="@null"
+ android:textColor="?android:attr/colorAccent"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:maxLines="3"
+ android:gravity="center_vertical"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:drawablePadding="20dp"
+ android:ellipsize="marquee" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..e7d37ea
--- /dev/null
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.providers.media.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ >
+
+ <CheckedTextView
+ android:id="@+id/checked_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="20dp"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+ android:drawablePadding="20dp"
+ android:ellipsize="marquee"
+ android:layout_toLeftOf="@+id/work_icon"
+ android:maxLines="3" />
+
+ <ImageView
+ android:id="@id/work_icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="20dp" />
+</com.android.providers.media.CheckedListItem>
diff --git a/packages/SoundPicker/res/raw/default_alarm_alert.ogg b/packages/SoundPicker/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker/res/raw/default_notification_sound.ogg b/packages/SoundPicker/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker/res/raw/default_ringtone.ogg b/packages/SoundPicker/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker/res/values-watch/config.xml b/packages/SoundPicker/res/values-watch/config.xml
new file mode 100644
index 0000000..0bc24fa
--- /dev/null
+++ b/packages/SoundPicker/res/values-watch/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. Do not translate.
+
+ NOTE: The naming convention is "config_camelCaseValue". -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+ ringtone will be automatically selected when the picker is closed. -->
+ <bool name="config_showOkCancelButtons">false</bool>
+</resources>
diff --git a/packages/SoundPicker/res/values/config.xml b/packages/SoundPicker/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. Do not translate.
+
+ NOTE: The naming convention is "config_camelCaseValue". -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+ ringtone will be automatically selected when the picker is closed. -->
+ <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
new file mode 100644
index 0000000..56ed5fd
--- /dev/null
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Choice in the ringtone picker. If chosen, the default ringtone will be used. -->
+ <string name="ringtone_default">Default ringtone</string>
+
+ <!-- Choice in the notification sound picker. If chosen, the default notification sound will be
+ used. -->
+ <string name="notification_sound_default">Default notification sound</string>
+
+ <!-- Choice in the alarm sound picker. If chosen, the default alarm sound will be used. -->
+ <string name="alarm_sound_default">Default alarm sound</string>
+
+ <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+ <string name="add_ringtone_text">Add ringtone</string>
+ <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+ <string name="add_alarm_text">Add alarm</string>
+ <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+ <string name="add_notification_text">Add notification</string>
+ <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+ <string name="delete_ringtone_text">Delete</string>
+ <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+ <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+ <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+ <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+</resources>
diff --git a/packages/SoundPicker/res/values/styles.xml b/packages/SoundPicker/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+ </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..2d37b4c
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+ private static final String TAG = "RingtoneOverlayService";
+ private static final boolean DEBUG = false;
+
+ @Override
+ public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+ AsyncTask.execute(() -> {
+ updateRingtones();
+ stopSelf();
+ });
+
+ // Try again later if we are killed before we finish.
+ return Service.START_REDELIVER_INTENT;
+ }
+
+ @Override
+ public IBinder onBind(@Nullable final Intent intent) {
+ return null;
+ }
+
+ private void updateRingtones() {
+ copyResourceAndSetAsSound(R.raw.default_ringtone,
+ System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+ copyResourceAndSetAsSound(R.raw.default_notification_sound,
+ System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+ copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+ System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+ }
+
+ /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+ * file URI as the default for a sound. */
+ private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+ @NonNull final String subPath) {
+ final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+ if (!destDir.exists() && !destDir.mkdirs()) {
+ Log.e(TAG, "can't create " + destDir.getAbsolutePath());
+ return;
+ }
+
+ final File dest = new File(destDir, "default_" + name + ".ogg");
+ try (
+ InputStream is = getResources().openRawResource(id);
+ FileOutputStream os = new FileOutputStream(dest);
+ ) {
+ if (is.available() > 0) {
+ FileUtils.copy(is, os);
+ final Uri uri = scanFile(dest);
+ if (uri != null) {
+ set(name, uri);
+ }
+ } else {
+ // TODO Shall we remove any former copied resource in this case and unset
+ // the defaults if we use this event a second time to clear the data?
+ if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+ }
+ }
+
+ private Uri scanFile(@NonNull final File file) {
+ return MediaStore.scanFile(this, file);
+ }
+
+ private void set(@NonNull final String name, @NonNull final Uri uri) {
+ final Uri settingUri = System.getUriFor(name);
+ RingtoneManager.setActualDefaultRingtoneUri(this,
+ RingtoneManager.getDefaultType(settingUri), uri);
+ System.putInt(getContentResolver(), name + "_set", 1);
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..4ba5146
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+public final class RingtonePickerActivity extends AlertActivity implements
+ AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+ AlertController.AlertParams.OnPrepareListViewListener {
+
+ private static final int POS_UNKNOWN = -1;
+
+ private static final String TAG = "RingtonePickerActivity";
+
+ private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+ private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+ private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+ private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+ private static final int ADD_FILE_REQUEST_CODE = 300;
+
+ private RingtoneManager mRingtoneManager;
+ private int mType;
+
+ private Cursor mCursor;
+ private Handler mHandler;
+ private BadgedRingtoneAdapter mAdapter;
+
+ /** The position in the list of the 'Silent' item. */
+ private int mSilentPos = POS_UNKNOWN;
+
+ /** The position in the list of the 'Default' item. */
+ private int mDefaultRingtonePos = POS_UNKNOWN;
+
+ /** The position in the list of the ringtone to sample. */
+ private int mSampleRingtonePos = POS_UNKNOWN;
+
+ /** Whether this list has the 'Silent' item. */
+ private boolean mHasSilentItem;
+
+ /** The Uri to place a checkmark next to. */
+ private Uri mExistingUri;
+
+ /** The number of static items in the list. */
+ private int mStaticItemCount;
+
+ /** Whether this list has the 'Default' item. */
+ private boolean mHasDefaultItem;
+
+ /** The Uri to play when the 'Default' item is clicked. */
+ private Uri mUriForDefaultItem;
+
+ /** Id of the user to which the ringtone picker should list the ringtones */
+ private int mPickerUserId;
+
+ /** Context of the user specified by mPickerUserId */
+ private Context mTargetContext;
+
+ /**
+ * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+ * will stop the previous ringtone. However, the RingtoneManager doesn't
+ * manage the default ringtone for us, so we should stop this one manually.
+ */
+ private Ringtone mDefaultRingtone;
+
+ /**
+ * The ringtone that's currently playing, unless the currently playing one is the default
+ * ringtone.
+ */
+ private Ringtone mCurrentRingtone;
+
+ /**
+ * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+ */
+ private long mCheckedItemId = -1;
+
+ private int mAttributesFlags;
+
+ private boolean mShowOkCancelButtons;
+
+ /**
+ * Keep the currently playing ringtone around when changing orientation, so that it
+ * can be stopped later, after the activity is recreated.
+ */
+ private static Ringtone sPlayingRingtone;
+
+ private DialogInterface.OnClickListener mRingtoneClickListener =
+ new DialogInterface.OnClickListener() {
+
+ /*
+ * On item clicked
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == mCursor.getCount() + mStaticItemCount) {
+ // The "Add new ringtone" item was clicked. Start a file picker intent to select
+ // only audio files (MIME type "audio/*")
+ final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+ chooseFile.setType("audio/*");
+ chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+ new String[] { "audio/*", "application/ogg" });
+ startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+ return;
+ }
+
+ // Save the position of most recently clicked item
+ setCheckedItem(which);
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+
+ // Play clip
+ playRingtone(which, 0);
+ }
+
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mHandler = new Handler();
+
+ Intent intent = getIntent();
+ mPickerUserId = UserHandle.myUserId();
+ mTargetContext = this;
+
+ // Get the types of ringtones to show
+ mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+ initRingtoneManager();
+
+ /*
+ * Get whether to show the 'Default' item, and the URI to play when the
+ * default is clicked
+ */
+ mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+ mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+ if (mUriForDefaultItem == null) {
+ if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+ } else if (mType == RingtoneManager.TYPE_ALARM) {
+ mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+ mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+ } else {
+ // or leave it null for silence.
+ mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+ }
+ }
+
+ // Get whether to show the 'Silent' item
+ mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+ // AudioAttributes flags
+ mAttributesFlags |= intent.getIntExtra(
+ RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+ 0 /*defaultValue == no flags*/);
+
+ mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+ // The volume keys will control the stream that we are choosing a ringtone for
+ setVolumeControlStream(mRingtoneManager.inferStreamType());
+
+ // Get the URI whose list item should have a checkmark
+ mExistingUri = intent
+ .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+ // Create the list of ringtones and hold on to it so we can update later.
+ mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+ /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+ if (savedInstanceState != null) {
+ setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
+ }
+
+ final AlertController.AlertParams p = mAlertParams;
+ p.mAdapter = mAdapter;
+ p.mOnClickListener = mRingtoneClickListener;
+ p.mLabelColumn = COLUMN_LABEL;
+ p.mIsSingleChoice = true;
+ p.mOnItemSelectedListener = this;
+ if (mShowOkCancelButtons) {
+ p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+ p.mPositiveButtonListener = this;
+ }
+ p.mOnPrepareListViewListener = this;
+
+ p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+ if (p.mTitle == null) {
+ if (mType == RingtoneManager.TYPE_ALARM) {
+ p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+ } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ p.mTitle =
+ getString(com.android.internal.R.string.ringtone_picker_title_notification);
+ } else {
+ p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+ }
+ }
+
+ setupAlert();
+ }
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+ // Add the custom ringtone in a separate thread
+ final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+ @Override
+ protected Uri doInBackground(Uri... params) {
+ try {
+ return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+ } catch (IOException | IllegalArgumentException e) {
+ Log.e(TAG, "Unable to add new ringtone", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Uri ringtoneUri) {
+ if (ringtoneUri != null) {
+ requeryForAdapter();
+ } else {
+ // Ringtone was not added, display error Toast
+ Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ };
+ installTask.execute(data.getData());
+ }
+ }
+
+ // Disabled because context menus aren't Material Design :(
+ /*
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+ Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+ if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+ // It's a custom ringtone so we display the context menu
+ menu.setHeaderTitle(ringtone.getTitle(this));
+ menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Menu.FIRST: {
+ int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+ Uri deletedRingtoneUri = getRingtone(
+ getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+ if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+ requeryForAdapter();
+ } else {
+ Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+ .show();
+ }
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+ */
+
+ @Override
+ public void onDestroy() {
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ super.onDestroy();
+ }
+
+ public void onPrepareListView(ListView listView) {
+ // Reset the static item count, as this method can be called multiple times
+ mStaticItemCount = 0;
+
+ if (mHasDefaultItem) {
+ mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+ if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+ setCheckedItem(mDefaultRingtonePos);
+ }
+ }
+
+ if (mHasSilentItem) {
+ mSilentPos = addSilentItem(listView);
+
+ // The 'Silent' item should use a null Uri
+ if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+ setCheckedItem(mSilentPos);
+ }
+ }
+
+ if (getCheckedItem() == POS_UNKNOWN) {
+ setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+ }
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+ // If external storage is available, add a button to install sounds from storage.
+ if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ addNewSoundItem(listView);
+ }
+
+ // Enable context menu in ringtone items
+ registerForContextMenu(listView);
+ }
+
+ /**
+ * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+ * selected item position to match the new position of the chosen sound.
+ *
+ * This should only need to happen after adding or removing a ringtone.
+ */
+ private void requeryForAdapter() {
+ // Refresh and set a new cursor, closing the old one.
+ initRingtoneManager();
+ mAdapter.changeCursor(mCursor);
+
+ // Update checked item location.
+ int checkedPosition = POS_UNKNOWN;
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ if (mAdapter.getItemId(i) == mCheckedItemId) {
+ checkedPosition = getListPosition(i);
+ break;
+ }
+ }
+ if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+ checkedPosition = mSilentPos;
+ }
+ setCheckedItem(checkedPosition);
+ setupAlert();
+ }
+
+ /**
+ * Adds a static item to the top of the list. A static item is one that is not from the
+ * RingtoneManager.
+ *
+ * @param listView The ListView to add to.
+ * @param textResId The resource ID of the text for the item.
+ * @return The position of the inserted item.
+ */
+ private int addStaticItem(ListView listView, int textResId) {
+ TextView textView = (TextView) getLayoutInflater().inflate(
+ com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+ textView.setText(textResId);
+ listView.addHeaderView(textView);
+ mStaticItemCount++;
+ return listView.getHeaderViewsCount() - 1;
+ }
+
+ private int addDefaultRingtoneItem(ListView listView) {
+ if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ return addStaticItem(listView, R.string.notification_sound_default);
+ } else if (mType == RingtoneManager.TYPE_ALARM) {
+ return addStaticItem(listView, R.string.alarm_sound_default);
+ }
+
+ return addStaticItem(listView, R.string.ringtone_default);
+ }
+
+ private int addSilentItem(ListView listView) {
+ return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+ }
+
+ private void addNewSoundItem(ListView listView) {
+ View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+ false /* attachToRoot */);
+ TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+ if (mType == RingtoneManager.TYPE_ALARM) {
+ text.setText(R.string.add_alarm_text);
+ } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ text.setText(R.string.add_notification_text);
+ } else {
+ text.setText(R.string.add_ringtone_text);
+ }
+ listView.addFooterView(view);
+ }
+
+ private void initRingtoneManager() {
+ // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+ // causes unexpected behavior.
+ mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+ if (mType != -1) {
+ mRingtoneManager.setType(mType);
+ }
+ mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+ }
+
+ private Ringtone getRingtone(int ringtoneManagerPosition) {
+ if (ringtoneManagerPosition < 0) {
+ return null;
+ }
+ return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+ }
+
+ private int getCheckedItem() {
+ return mAlertParams.mCheckedItem;
+ }
+
+ private void setCheckedItem(int pos) {
+ mAlertParams.mCheckedItem = pos;
+ mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+ }
+
+ /*
+ * On click of Ok/Cancel buttons
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+ // Stop playing the previous ringtone
+ mRingtoneManager.stopPreviousRingtone();
+
+ if (positiveResult) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+
+ /*
+ * On item selected via keys
+ */
+ public void onItemSelected(AdapterView parent, View view, int position, long id) {
+ // footer view
+ if (position >= mCursor.getCount() + mStaticItemCount) {
+ return;
+ }
+
+ playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ }
+
+ private void playRingtone(int position, int delayMs) {
+ mHandler.removeCallbacks(this);
+ mSampleRingtonePos = position;
+ mHandler.postDelayed(this, delayMs);
+ }
+
+ public void run() {
+ stopAnyPlayingRingtone();
+ if (mSampleRingtonePos == mSilentPos) {
+ return;
+ }
+
+ Ringtone ringtone;
+ if (mSampleRingtonePos == mDefaultRingtonePos) {
+ if (mDefaultRingtone == null) {
+ mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+ }
+ /*
+ * Stream type of mDefaultRingtone is not set explicitly here.
+ * It should be set in accordance with mRingtoneManager of this Activity.
+ */
+ if (mDefaultRingtone != null) {
+ mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+ }
+ ringtone = mDefaultRingtone;
+ mCurrentRingtone = null;
+ } else {
+ ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+ mCurrentRingtone = ringtone;
+ }
+
+ if (ringtone != null) {
+ if (mAttributesFlags != 0) {
+ ringtone.setAudioAttributes(
+ new AudioAttributes.Builder(ringtone.getAudioAttributes())
+ .setFlags(mAttributesFlags)
+ .build());
+ }
+ ringtone.play();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (!isChangingConfigurations()) {
+ stopAnyPlayingRingtone();
+ } else {
+ saveAnyPlayingRingtone();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (!isChangingConfigurations()) {
+ stopAnyPlayingRingtone();
+ }
+ }
+
+ private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+ setResult(RESULT_OK,
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+ }
+
+ private Uri getCurrentlySelectedRingtoneUri() {
+ if (getCheckedItem() == mDefaultRingtonePos) {
+ // Use the default Uri that they originally gave us.
+ return mUriForDefaultItem;
+ } else if (getCheckedItem() == mSilentPos) {
+ // Use a null Uri for the 'Silent' item.
+ return null;
+ } else {
+ return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+ }
+ }
+
+ private void saveAnyPlayingRingtone() {
+ if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+ sPlayingRingtone = mDefaultRingtone;
+ } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+ sPlayingRingtone = mCurrentRingtone;
+ }
+ }
+
+ private void stopAnyPlayingRingtone() {
+ if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+ sPlayingRingtone.stop();
+ }
+ sPlayingRingtone = null;
+
+ if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+ mDefaultRingtone.stop();
+ }
+
+ if (mRingtoneManager != null) {
+ mRingtoneManager.stopPreviousRingtone();
+ }
+ }
+
+ private int getRingtoneManagerPosition(int listPos) {
+ return listPos - mStaticItemCount;
+ }
+
+ private int getListPosition(int ringtoneManagerPos) {
+
+ // If the manager position is -1 (for not found), return that
+ if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+ return ringtoneManagerPos + mStaticItemCount;
+ }
+
+ private static class LocalizedCursor extends CursorWrapper {
+
+ final int mTitleIndex;
+ final Resources mResources;
+ String mNamePrefix;
+ final Pattern mSanitizePattern;
+
+ LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+ super(cursor);
+ mTitleIndex = mCursor.getColumnIndex(columnLabel);
+ mResources = resources;
+ mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+ if (mTitleIndex == -1) {
+ Log.e(TAG, "No index for column " + columnLabel);
+ mNamePrefix = null;
+ } else {
+ try {
+ // Build the prefix for the name of the resource to look up
+ // format is: "ResourcePackageName::ResourceTypeName/"
+ // (the type name is expected to be "string" but let's not hardcode it).
+ // Here we use an existing resource "notification_sound_default" which is
+ // always expected to be found.
+ mNamePrefix = String.format("%s:%s/%s",
+ mResources.getResourcePackageName(R.string.notification_sound_default),
+ mResources.getResourceTypeName(R.string.notification_sound_default),
+ SOUND_NAME_RES_PREFIX);
+ } catch (NotFoundException e) {
+ mNamePrefix = null;
+ }
+ }
+ }
+
+ /**
+ * Process resource name to generate a valid resource name.
+ * @param input
+ * @return a non-null String
+ */
+ private String sanitize(String input) {
+ if (input == null) {
+ return "";
+ }
+ return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ final String defaultName = mCursor.getString(columnIndex);
+ if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+ return defaultName;
+ }
+ TypedValue value = new TypedValue();
+ try {
+ // the name currently in the database is used to derive a name to match
+ // against resource names in this package
+ mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+ } catch (NotFoundException e) {
+ // no localized string, use the default string
+ return defaultName;
+ }
+ if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+ Log.d(TAG, String.format("Replacing name %s with %s",
+ defaultName, value.string.toString()));
+ return value.string.toString();
+ } else {
+ Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+ return defaultName;
+ }
+ }
+ }
+
+ private class BadgedRingtoneAdapter extends CursorAdapter {
+ private final boolean mIsManagedProfile;
+
+ public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+ super(context, cursor);
+ mIsManagedProfile = isManagedProfile;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (position < 0) {
+ return position;
+ }
+ return super.getItemId(position);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ // Set text as the title of the ringtone
+ ((TextView) view.findViewById(R.id.checked_text_view))
+ .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+ boolean isWorkRingtone = false;
+ if (mIsManagedProfile) {
+ /*
+ * Display the work icon if the ringtone belongs to a work profile. We can tell that
+ * a ringtone belongs to a work profile if the picker user is a managed profile, the
+ * ringtone Uri is in external storage, and either the uri has no user id or has the
+ * id of the picker user
+ */
+ Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+ int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+ Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+ if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+ .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+ isWorkRingtone = true;
+ }
+ }
+
+ ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+ if(isWorkRingtone) {
+ workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+ UserHandle.of(mPickerUserId), -1 /* density */));
+ workIcon.setVisibility(View.VISIBLE);
+ } else {
+ workIcon.setVisibility(View.GONE);
+ }
+ }
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java
new file mode 100644
index 0000000..9e1ba3a
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneReceiver.java
@@ -0,0 +1,37 @@
+/* //device/content/providers/media/src/com/android/providers/media/MediaScannerReceiver.java
+**
+** Copyright 2007, 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.soundpicker;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RingtoneReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+ initResourceRingtones(context);
+ }
+ }
+
+ private void initResourceRingtones(Context context) {
+ context.startService(
+ new Intent(context, RingtoneOverlayService.class));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 10d132a..45126f3 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -19,16 +19,12 @@
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
-
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
-import android.os.SystemProperties;
-import android.telephony.CarrierConfigManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -37,19 +33,17 @@
import android.text.TextUtils;
import android.util.Log;
-import androidx.annotation.VisibleForTesting;
-
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
import com.android.settingslib.WirelessUtils;
import com.android.systemui.Dependency;
import com.android.systemui.keyguard.WakefulnessLifecycle;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import androidx.annotation.VisibleForTesting;
+
/**
* Controller that generates text including the carrier names and/or the status of all the SIM
* interfaces in the device. Through a callback, the updates can be retrieved either as a list or
@@ -72,8 +66,6 @@
private Context mContext;
private CharSequence mSeparator;
private WakefulnessLifecycle mWakefulnessLifecycle;
- @VisibleForTesting
- protected boolean mDisplayOpportunisticSubscriptionCarrierText;
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
new WakefulnessLifecycle.Observer() {
@Override
@@ -174,9 +166,6 @@
mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
Context.TELEPHONY_SERVICE)).getPhoneCount();
mSimErrorState = new boolean[mSimSlotsNumber];
- updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
- TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
- false));
}
/**
@@ -253,63 +242,8 @@
}
}
- /**
- * @param subscriptions
- */
- private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
- if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
- SubscriptionInfo info1 = subscriptions.get(0);
- SubscriptionInfo info2 = subscriptions.get(1);
- if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
- // If both subscriptions are primary, show both.
- if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
-
- // If carrier required, always show signal bar of primary subscription.
- // Otherwise, show whichever subscription is currently active for Internet.
- boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
- .getBoolean(CarrierConfigManager
- .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
- if (alwaysShowPrimary) {
- subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
- } else {
- subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
- ? info2 : info1);
- }
-
- }
- }
- }
-
- /**
- * updates if opportunistic sub carrier text should be displayed or not
- *
- */
- @VisibleForTesting
- public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
- mDisplayOpportunisticSubscriptionCarrierText = isEnable;
- }
-
protected List<SubscriptionInfo> getSubscriptionInfo() {
- List<SubscriptionInfo> subs;
- if (mDisplayOpportunisticSubscriptionCarrierText) {
- SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
- .getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE));
- subs = subscriptionManager.getActiveSubscriptionInfoList(false);
- if (subs == null) {
- subs = new ArrayList<>();
- } else {
- filterMobileSubscriptionInSameGroup(subs);
- }
- } else {
- subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
- if (subs == null) {
- subs = new ArrayList<>();
- } else {
- filterMobileSubscriptionInSameGroup(subs);
- }
- }
- return subs;
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
}
protected void updateCarrierText() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index de20972..5d6cc83 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -30,6 +30,7 @@
import static android.os.BatteryManager.EXTRA_STATUS;
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -77,6 +78,7 @@
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -261,6 +263,7 @@
private boolean mLogoutEnabled;
// If the user long pressed the lock icon, disabling face auth for the current session.
private boolean mLockIconPressed;
+ private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
/**
* Short delay before restarting biometric authentication after a successful try
@@ -406,9 +409,11 @@
}
};
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @VisibleForTesting
+ public PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveMobileDataSubscription = subId;
mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
}
};
@@ -511,7 +516,9 @@
}
}
- /** @return List of SubscriptionInfo records, maybe empty but never null */
+ /**
+ * @return List of SubscriptionInfo records, maybe empty but never null.
+ */
public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) {
List<SubscriptionInfo> sil = mSubscriptionInfo;
if (sil == null || forceReload) {
@@ -523,7 +530,42 @@
} else {
mSubscriptionInfo = sil;
}
- return mSubscriptionInfo;
+ return new ArrayList<>(mSubscriptionInfo);
+ }
+
+ /**
+ * This method returns filtered list of SubscriptionInfo from {@link #getSubscriptionInfo}.
+ * above. Maybe empty but never null.
+ *
+ * In DSDS mode if both subscriptions are grouped and one is opportunistic, we filter out one
+ * of them based on carrier config. e.g. In this case we should only show one carrier name
+ * on the status bar and quick settings.
+ */
+ public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
+ List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
+ if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+ SubscriptionInfo info1 = subscriptions.get(0);
+ SubscriptionInfo info2 = subscriptions.get(1);
+ if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
+ // If both subscriptions are primary, show both.
+ if (!info1.isOpportunistic() && !info2.isOpportunistic()) return subscriptions;
+
+ // If carrier required, always show signal bar of primary subscription.
+ // Otherwise, show whichever subscription is currently active for Internet.
+ boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
+ .getBoolean(CarrierConfigManager
+ .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
+ if (alwaysShowPrimary) {
+ subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
+ } else {
+ subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
+ ? info2 : info1);
+ }
+
+ }
+ }
+
+ return subscriptions;
}
@Override
@@ -1446,6 +1488,7 @@
protected void handleStartedGoingToSleep(int arg1) {
checkIsHandlerThread();
+ mLockIconPressed = false;
clearBiometricRecognized();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1481,7 +1524,6 @@
private void handleScreenTurnedOff() {
checkIsHandlerThread();
- mLockIconPressed = false;
mHardwareFingerprintUnavailableRetryCount = 0;
mHardwareFaceUnavailableRetryCount = 0;
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2700,6 +2742,7 @@
pw.println(" " + mSubscriptionInfo.get(i));
}
}
+ pw.println(" Current active data subId=" + mActiveMobileDataSubscription);
pw.println(" Service states:");
for (int subId : mServiceStates.keySet()) {
pw.println(" " + subId + "=" + mServiceStates.get(subId));
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 7e3b423..d5c928b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -140,11 +140,12 @@
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry,
KeyguardBouncer.BouncerExpansionCallback expansionCallback,
- FalsingManager falsingManager) {
+ FalsingManager falsingManager, KeyguardBypassController bypassController) {
return new KeyguardBouncer(context, callback, lockPatternUtils, container,
dismissCallbackRegistry, falsingManager,
expansionCallback, UnlockMethodCache.getInstance(context),
- KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper()));
+ KeyguardUpdateMonitor.getInstance(context), bypassController,
+ new Handler(Looper.getMainLooper()));
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
new file mode 100644
index 0000000..d4baefd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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.systemui.biometrics;
+
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import com.android.systemui.biometrics.ui.BiometricDialogView;
+
+/**
+ * Interface for the biometric dialog UI.
+ */
+public interface BiometricDialog {
+
+ // TODO: Clean up save/restore state
+ String[] KEYS_TO_BACKUP = {
+ BiometricPrompt.KEY_TITLE,
+ BiometricPrompt.KEY_USE_DEFAULT_TITLE,
+ BiometricPrompt.KEY_SUBTITLE,
+ BiometricPrompt.KEY_DESCRIPTION,
+ BiometricPrompt.KEY_POSITIVE_TEXT,
+ BiometricPrompt.KEY_NEGATIVE_TEXT,
+ BiometricPrompt.KEY_REQUIRE_CONFIRMATION,
+ BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL,
+ BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL,
+
+ BiometricDialogView.KEY_TRY_AGAIN_VISIBILITY,
+ BiometricDialogView.KEY_CONFIRM_VISIBILITY,
+ BiometricDialogView.KEY_CONFIRM_ENABLED,
+ BiometricDialogView.KEY_STATE,
+ BiometricDialogView.KEY_ERROR_TEXT_VISIBILITY,
+ BiometricDialogView.KEY_ERROR_TEXT_STRING,
+ BiometricDialogView.KEY_ERROR_TEXT_IS_TEMPORARY,
+ BiometricDialogView.KEY_ERROR_TEXT_COLOR,
+ };
+
+ /**
+ * Show the dialog.
+ * @param wm
+ * @param skipIntroAnimation
+ */
+ void show(WindowManager wm, boolean skipIntroAnimation);
+
+ /**
+ * Dismiss the dialog without sending a callback.
+ */
+ void dismissWithoutCallback(boolean animate);
+
+ /**
+ * Dismiss the dialog. Animate away.
+ */
+ void dismissFromSystemServer();
+
+ /**
+ * Biometric authenticated. May be pending user confirmation, or completed.
+ */
+ void onAuthenticationSucceeded();
+
+ /**
+ * Authentication failed (reject, timeout). Dialog stays showing.
+ * @param failureReason
+ */
+ void onAuthenticationFailed(String failureReason);
+
+ /**
+ * Authentication rejected, or help message received.
+ * @param help
+ */
+ void onHelp(String help);
+
+ /**
+ * Authentication failed. Dialog going away.
+ * @param error
+ */
+ void onError(String error);
+
+ /**
+ * Save the current state.
+ * @param outState
+ */
+ void onSaveState(Bundle outState);
+
+ /**
+ * Restore a previous state.
+ * @param savedState
+ */
+ void restoreState(Bundle savedState);
+
+ /**
+ * Get the client's package name
+ */
+ String getOpPackageName();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index e66a8fa..a8e5722 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -16,137 +16,156 @@
package com.android.systemui.biometrics;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
-import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.biometrics.ui.BiometricDialogView;
import com.android.systemui.statusbar.CommandQueue;
+import java.util.List;
+
/**
- * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
- * BiometricDialogView).
+ * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
+ * appropriate biometric UI (e.g. BiometricDialogView).
*/
-public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks {
+public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
+ DialogViewCallback {
private static final String TAG = "BiometricDialogImpl";
private static final boolean DEBUG = true;
- private static final int MSG_SHOW_DIALOG = 1;
- private static final int MSG_BIOMETRIC_AUTHENTICATED = 2;
- private static final int MSG_BIOMETRIC_HELP = 3;
- private static final int MSG_BIOMETRIC_ERROR = 4;
- private static final int MSG_HIDE_DIALOG = 5;
- private static final int MSG_BUTTON_NEGATIVE = 6;
- private static final int MSG_USER_CANCELED = 7;
- private static final int MSG_BUTTON_POSITIVE = 8;
- private static final int MSG_TRY_AGAIN_PRESSED = 9;
+ private final Injector mInjector;
+ // TODO: These should just be saved from onSaveState
private SomeArgs mCurrentDialogArgs;
- private BiometricDialogView mCurrentDialog;
+ @VisibleForTesting
+ BiometricDialog mCurrentDialog;
+
+ private Handler mHandler = new Handler(Looper.getMainLooper());
private WindowManager mWindowManager;
- private IBiometricServiceReceiverInternal mReceiver;
- private boolean mDialogShowing;
- private Callback mCallback = new Callback();
- private WakefulnessLifecycle mWakefulnessLifecycle;
+ @VisibleForTesting
+ IActivityTaskManager mActivityTaskManager;
+ @VisibleForTesting
+ BiometricTaskStackListener mTaskStackListener;
+ @VisibleForTesting
+ IBiometricServiceReceiverInternal mReceiver;
- private Handler mHandler = new Handler(Looper.getMainLooper()) {
+ public class BiometricTaskStackListener extends TaskStackListener {
@Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_SHOW_DIALOG:
- handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
- null /* savedState */);
- break;
- case MSG_BIOMETRIC_AUTHENTICATED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleBiometricAuthenticated((boolean) args.arg1 /* authenticated */,
- (String) args.arg2 /* failureReason */);
- args.recycle();
- break;
- }
- case MSG_BIOMETRIC_HELP: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleBiometricHelp((String) args.arg1 /* message */);
- args.recycle();
- break;
- }
- case MSG_BIOMETRIC_ERROR:
- handleBiometricError((String) msg.obj);
- break;
- case MSG_HIDE_DIALOG:
- handleHideDialog((Boolean) msg.obj);
- break;
- case MSG_BUTTON_NEGATIVE:
- handleButtonNegative();
- break;
- case MSG_USER_CANCELED:
- handleUserCanceled();
- break;
- case MSG_BUTTON_POSITIVE:
- handleButtonPositive();
- break;
- case MSG_TRY_AGAIN_PRESSED:
- handleTryAgainPressed();
- break;
- default:
- Log.w(TAG, "Unknown message: " + msg.what);
- break;
- }
- }
- };
-
- private class Callback implements DialogViewCallback {
- @Override
- public void onUserCanceled() {
- mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
- }
-
- @Override
- public void onErrorShown() {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_DIALOG,
- false /* userCanceled */), BiometricPrompt.HIDE_DIALOG_DELAY);
- }
-
- @Override
- public void onNegativePressed() {
- mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
- }
-
- @Override
- public void onPositivePressed() {
- mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
- }
-
- @Override
- public void onTryAgainPressed() {
- mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
+ public void onTaskStackChanged() {
+ mHandler.post(mTaskStackChangedRunnable);
}
}
- final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedGoingToSleep() {
- if (mDialogShowing) {
- if (DEBUG) Log.d(TAG, "User canceled due to screen off");
- mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
+ private final Runnable mTaskStackChangedRunnable = () -> {
+ if (mCurrentDialog != null) {
+ try {
+ final String clientPackage = mCurrentDialog.getOpPackageName();
+ Log.w(TAG, "Task stack changed, current client: " + clientPackage);
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(clientPackage)) {
+ Log.w(TAG, "Evicting client due to: " + topPackage);
+ mCurrentDialog.dismissWithoutCallback(true /* animate */);
+ mCurrentDialog = null;
+ mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ mReceiver = null;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception", e);
}
}
};
@Override
+ public void onTryAgainPressed() {
+ try {
+ mReceiver.onTryAgainPressed();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when handling try again", e);
+ }
+ }
+
+ @Override
+ public void onDismissed(@DismissedReason int reason) {
+ switch (reason) {
+ case DialogViewCallback.DISMISSED_USER_CANCELED:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ break;
+
+ case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ break;
+
+ case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
+ break;
+
+ case DialogViewCallback.DISMISSED_AUTHENTICATED:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
+ break;
+
+ case DialogViewCallback.DISMISSED_ERROR:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
+ break;
+
+ case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
+ sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+ break;
+
+ default:
+ Log.e(TAG, "Unhandled reason: " + reason);
+ break;
+ }
+ }
+
+ private void sendResultAndCleanUp(@DismissedReason int reason) {
+ if (mReceiver == null) {
+ Log.e(TAG, "Receiver is null");
+ return;
+ }
+ try {
+ mReceiver.onDialogDismissed(reason);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception", e);
+ }
+ onDialogDismissed(reason);
+ }
+
+ public static class Injector {
+ IActivityTaskManager getActivityTaskManager() {
+ return ActivityTaskManager.getService();
+ }
+ }
+
+ public BiometricDialogImpl() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ BiometricDialogImpl(Injector injector) {
+ mInjector = injector;
+ }
+
+ @Override
public void start() {
final PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
@@ -154,30 +173,38 @@
|| pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
getComponent(CommandQueue.class).addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
- mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ mActivityTaskManager = mInjector.getActivityTaskManager();
+
+ try {
+ mTaskStackListener = new BiometricTaskStackListener();
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register task stack listener", e);
+ }
}
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId) {
+ int type, boolean requireConfirmation, int userId, String opPackageName) {
if (DEBUG) {
Log.d(TAG, "showBiometricDialog, type: " + type
+ ", requireConfirmation: " + requireConfirmation);
}
- // Remove these messages as they are part of the previous client
- mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
- mHandler.removeMessages(MSG_BIOMETRIC_HELP);
- mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
- mHandler.removeMessages(MSG_HIDE_DIALOG);
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
args.arg2 = receiver;
args.argi1 = type;
args.arg3 = requireConfirmation;
args.argi2 = userId;
- mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
+ args.arg4 = opPackageName;
+
+ boolean skipAnimation = false;
+ if (mCurrentDialog != null) {
+ Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
+ skipAnimation = true;
+ }
+ showDialog(args, skipAnimation, null /* savedState */);
}
@Override
@@ -185,185 +212,111 @@
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
+ " reason: " + failureReason);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = authenticated;
- args.arg2 = failureReason;
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
- }
-
- @Override
- public void onBiometricHelp(String message) {
- if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = message;
- mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
- }
-
- @Override
- public void onBiometricError(String error) {
- if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
- mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget();
- }
-
- @Override
- public void hideBiometricDialog() {
- if (DEBUG) Log.d(TAG, "hideBiometricDialog");
- mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
- }
-
- private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
- mCurrentDialogArgs = args;
- final int type = args.argi1;
-
- // Create a new dialog but do not replace the current one yet.
- BiometricDialogView newDialog;
- if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
- newDialog = new FingerprintDialogView(mContext, mCallback);
- } else if (type == BiometricAuthenticator.TYPE_FACE) {
- newDialog = new FaceDialogView(mContext, mCallback);
- } else {
- Log.e(TAG, "Unsupported type: " + type);
- return;
- }
-
- if (DEBUG) Log.d(TAG, "handleShowDialog, "
- + " savedState: " + savedState
- + " mCurrentDialog: " + mCurrentDialog
- + " newDialog: " + newDialog
- + " type: " + type);
-
- if (savedState != null) {
- // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
- // even though it may be removed / re-created again
- newDialog.restoreState(savedState);
- } else if (mCurrentDialog != null && mDialogShowing) {
- // If somehow we're asked to show a dialog, the old one doesn't need to be animated
- // away. This can happen if the app cancels and re-starts auth during configuration
- // change. This is ugly because we also have to do things on onConfigurationChanged
- // here.
- mCurrentDialog.forceRemove();
- }
-
- mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
- newDialog.setBundle((Bundle) args.arg1);
- newDialog.setRequireConfirmation((boolean) args.arg3);
- newDialog.setUserId(args.argi2);
- newDialog.setSkipIntro(skipAnimation);
- mCurrentDialog = newDialog;
- mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
- mDialogShowing = true;
- }
-
- private void handleBiometricAuthenticated(boolean authenticated, String failureReason) {
- if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
-
if (authenticated) {
- mCurrentDialog.announceForAccessibility(
- mContext.getResources()
- .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
- if (mCurrentDialog.requiresConfirmation()) {
- mCurrentDialog.updateState(BiometricDialogView.STATE_PENDING_CONFIRMATION);
- } else {
- mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
- mHandler.postDelayed(() -> {
- handleHideDialog(false /* userCanceled */);
- }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
- }
+ mCurrentDialog.onAuthenticationSucceeded();
} else {
mCurrentDialog.onAuthenticationFailed(failureReason);
}
}
- private void handleBiometricHelp(String message) {
- if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
- mCurrentDialog.onHelpReceived(message);
+ @Override
+ public void onBiometricHelp(String message) {
+ if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
+
+ mCurrentDialog.onHelp(message);
}
- private void handleBiometricError(String error) {
- if (DEBUG) Log.d(TAG, "handleBiometricError: " + error);
- if (!mDialogShowing) {
- if (DEBUG) Log.d(TAG, "Dialog already dismissed");
- return;
- }
- mCurrentDialog.onErrorReceived(error);
+ @Override
+ public void onBiometricError(String error) {
+ if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
+ mCurrentDialog.onError(error);
}
- private void handleHideDialog(boolean userCanceled) {
- if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
- if (!mDialogShowing) {
- // This can happen if there's a race and we get called from both
- // onAuthenticated and onError, etc.
- Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
+ @Override
+ public void hideBiometricDialog() {
+ if (DEBUG) Log.d(TAG, "hideBiometricDialog");
+
+ mCurrentDialog.dismissFromSystemServer();
+ }
+
+ private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
+ mCurrentDialogArgs = args;
+ final int type = args.argi1;
+ final Bundle biometricPromptBundle = (Bundle) args.arg1;
+ final boolean requireConfirmation = (boolean) args.arg3;
+ final int userId = args.argi2;
+ final String opPackageName = (String) args.arg4;
+
+ // Create a new dialog but do not replace the current one yet.
+ final BiometricDialog newDialog = buildDialog(
+ biometricPromptBundle,
+ requireConfirmation,
+ userId,
+ type,
+ opPackageName);
+
+ if (newDialog == null) {
+ Log.e(TAG, "Unsupported type: " + type);
return;
}
- if (userCanceled) {
- try {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException when hiding dialog", e);
- }
+
+ if (DEBUG) {
+ Log.d(TAG, "showDialog, "
+ + " savedState: " + savedState
+ + " mCurrentDialog: " + mCurrentDialog
+ + " newDialog: " + newDialog
+ + " type: " + type);
+ }
+
+ if (savedState != null) {
+ // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
+ // even though it may be removed / re-created again
+ newDialog.restoreState(savedState);
+ } else if (mCurrentDialog != null) {
+ // If somehow we're asked to show a dialog, the old one doesn't need to be animated
+ // away. This can happen if the app cancels and re-starts auth during configuration
+ // change. This is ugly because we also have to do things on onConfigurationChanged
+ // here.
+ mCurrentDialog.dismissWithoutCallback(false /* animate */);
+ }
+
+ mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
+ mCurrentDialog = newDialog;
+ mCurrentDialog.show(mWindowManager, skipAnimation);
+ }
+
+ private void onDialogDismissed(@DismissedReason int reason) {
+ if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
+ if (mCurrentDialog == null) {
+ Log.w(TAG, "Dialog already dismissed");
}
mReceiver = null;
- mDialogShowing = false;
- mCurrentDialog.startDismiss();
- }
-
- private void handleButtonNegative() {
- if (mReceiver == null) {
- Log.e(TAG, "Receiver is null");
- return;
- }
- try {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception when handling negative button", e);
- }
- handleHideDialog(false /* userCanceled */);
- }
-
- private void handleButtonPositive() {
- if (mReceiver == null) {
- Log.e(TAG, "Receiver is null");
- return;
- }
- try {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception when handling positive button", e);
- }
- handleHideDialog(false /* userCanceled */);
- }
-
- private void handleUserCanceled() {
- handleHideDialog(true /* userCanceled */);
- }
-
- private void handleTryAgainPressed() {
- try {
- mReceiver.onTryAgainPressed();
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException when handling try again", e);
- }
+ mCurrentDialog = null;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- final boolean wasShowing = mDialogShowing;
// Save the state of the current dialog (buttons showing, etc)
- final Bundle savedState = new Bundle();
if (mCurrentDialog != null) {
+ final Bundle savedState = new Bundle();
mCurrentDialog.onSaveState(savedState);
- }
+ mCurrentDialog.dismissWithoutCallback(false /* animate */);
+ mCurrentDialog = null;
- if (mDialogShowing) {
- mCurrentDialog.forceRemove();
- mDialogShowing = false;
+ showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
}
+ }
- if (wasShowing) {
- handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
- }
+ protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
+ boolean requireConfirmation, int userId, int type, String opPackageName) {
+ return new BiometricDialogView.Builder(mContext)
+ .setCallback(this)
+ .setBiometricPromptBundle(biometricPromptBundle)
+ .setRequireConfirmation(requireConfirmation)
+ .setUserId(userId)
+ .setOpPackageName(opPackageName)
+ .build(type);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
index 24fd22e..b65d1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
@@ -16,36 +16,38 @@
package com.android.systemui.biometrics;
+import android.annotation.IntDef;
+
/**
* Callback interface for dialog views. These should be implemented by the controller (e.g.
* FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
*/
public interface DialogViewCallback {
- /**
- * Invoked when the user cancels authentication by tapping outside the prompt, etc. The dialog
- * should be dismissed.
- */
- void onUserCanceled();
+
+ int DISMISSED_USER_CANCELED = 1;
+ int DISMISSED_BUTTON_NEGATIVE = 2;
+ int DISMISSED_BUTTON_POSITIVE = 3;
+
+ int DISMISSED_AUTHENTICATED = 4;
+ int DISMISSED_ERROR = 5;
+ int DISMISSED_BY_SYSTEM_SERVER = 6;
+
+ @IntDef({DISMISSED_USER_CANCELED,
+ DISMISSED_BUTTON_NEGATIVE,
+ DISMISSED_BUTTON_POSITIVE,
+ DISMISSED_AUTHENTICATED,
+ DISMISSED_ERROR,
+ DISMISSED_BY_SYSTEM_SERVER})
+ @interface DismissedReason {}
/**
- * Invoked when an error is shown. The dialog should be dismissed after a set amount of time.
+ * Invoked when the dialog is dismissed
+ * @param reason
*/
- void onErrorShown();
+ void onDismissed(@DismissedReason int reason);
/**
- * Invoked when the negative button is pressed. The client should be notified and the dialog
- * should be dismissed.
- */
- void onNegativePressed();
-
- /**
- * Invoked when the positive button is pressed. The client should be notified and the dialog
- * should be dismissed.
- */
- void onPositivePressed();
-
- /**
- * Invoked when the "try again" button is pressed.
+ * Invoked when the "try again" button is clicked
*/
void onTryAgainPressed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
rename to packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
index ce67577..2b4dde5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.biometrics;
+package com.android.systemui.biometrics.ui;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
@@ -23,6 +23,7 @@
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Binder;
import android.os.Bundle;
@@ -46,25 +47,30 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.biometrics.BiometricDialog;
+import com.android.systemui.biometrics.DialogViewCallback;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.leak.RotationUtils;
/**
* Abstract base class. Shows a dialog for BiometricPrompt.
*/
-public abstract class BiometricDialogView extends LinearLayout {
+public abstract class BiometricDialogView extends LinearLayout implements BiometricDialog {
private static final String TAG = "BiometricDialogView";
- private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
- private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
- private static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled";
- private static final String KEY_STATE = "key_state";
- private static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility";
- private static final String KEY_ERROR_TEXT_STRING = "key_error_text_string";
- private static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary";
- private static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color";
+ public static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
+ public static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
+ public static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled";
+ public static final String KEY_STATE = "key_state";
+ public static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility";
+ public static final String KEY_ERROR_TEXT_STRING = "key_error_text_string";
+ public static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary";
+ public static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color";
private static final int ANIMATION_DURATION_SHOW = 250; // ms
private static final int ANIMATION_DURATION_AWAY = 350; // ms
@@ -77,6 +83,8 @@
protected static final int STATE_PENDING_CONFIRMATION = 3;
protected static final int STATE_AUTHENTICATED = 4;
+ @VisibleForTesting
+ final WakefulnessLifecycle mWakefulnessLifecycle;
private final AccessibilityManager mAccessibilityManager;
private final IBinder mWindowToken = new Binder();
private final Interpolator mLinearOutSlowIn;
@@ -90,22 +98,30 @@
protected final ViewGroup mLayout;
protected final LinearLayout mDialog;
- protected final TextView mTitleText;
- protected final TextView mSubtitleText;
- protected final TextView mDescriptionText;
- protected final ImageView mBiometricIcon;
- protected final TextView mErrorText;
- protected final Button mPositiveButton;
- protected final Button mNegativeButton;
- protected final Button mTryAgainButton;
+ @VisibleForTesting
+ final TextView mTitleText;
+ @VisibleForTesting
+ final TextView mSubtitleText;
+ @VisibleForTesting
+ final TextView mDescriptionText;
+ @VisibleForTesting
+ final ImageView mBiometricIcon;
+ @VisibleForTesting
+ final TextView mErrorText;
+ @VisibleForTesting
+ final Button mPositiveButton;
+ @VisibleForTesting
+ final Button mNegativeButton;
+ @VisibleForTesting
+ final Button mTryAgainButton;
protected final int mTextColor;
private Bundle mBundle;
private Bundle mRestoredState;
+ private String mOpPackageName;
private int mState = STATE_IDLE;
- private boolean mAnimatingAway;
private boolean mWasForceRemoved;
private boolean mSkipIntro;
protected boolean mRequireConfirmation;
@@ -141,6 +157,15 @@
}
};
+ @VisibleForTesting
+ final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onStartedGoingToSleep() {
+ animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
+ }
+ };
+
protected Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -155,8 +180,80 @@
}
};
- public BiometricDialogView(Context context, DialogViewCallback callback) {
+ /**
+ * Builds the dialog with specified parameters.
+ */
+ public static class Builder {
+ public static final int TYPE_FINGERPRINT = BiometricAuthenticator.TYPE_FINGERPRINT;
+ public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE;
+
+ private Context mContext;
+ private DialogViewCallback mCallback;
+ private Bundle mBundle;
+ private boolean mRequireConfirmation;
+ private int mUserId;
+ private String mOpPackageName;
+
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ public Builder setCallback(DialogViewCallback callback) {
+ mCallback = callback;
+ return this;
+ }
+
+ public Builder setBiometricPromptBundle(Bundle bundle) {
+ mBundle = bundle;
+ return this;
+ }
+
+ public Builder setRequireConfirmation(boolean requireConfirmation) {
+ mRequireConfirmation = requireConfirmation;
+ return this;
+ }
+
+ public Builder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ public Builder setOpPackageName(String opPackageName) {
+ mOpPackageName = opPackageName;
+ return this;
+ }
+
+ public BiometricDialogView build(int type) {
+ return build(type, new Injector());
+ }
+
+ public BiometricDialogView build(int type, Injector injector) {
+ BiometricDialogView dialog;
+ if (type == TYPE_FINGERPRINT) {
+ dialog = new FingerprintDialogView(mContext, mCallback, injector);
+ } else if (type == TYPE_FACE) {
+ dialog = new FaceDialogView(mContext, mCallback, injector);
+ } else {
+ return null;
+ }
+ dialog.setBundle(mBundle);
+ dialog.setRequireConfirmation(mRequireConfirmation);
+ dialog.setUserId(mUserId);
+ dialog.setOpPackageName(mOpPackageName);
+ return dialog;
+ }
+ }
+
+ public static class Injector {
+ public WakefulnessLifecycle getWakefulnessLifecycle() {
+ return Dependency.get(WakefulnessLifecycle.class);
+ }
+ }
+
+ protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) {
super(context);
+ mWakefulnessLifecycle = injector.getWakefulnessLifecycle();
+
mCallback = callback;
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
@@ -178,19 +275,13 @@
addView(mLayout);
mLayout.setOnKeyListener(new View.OnKeyListener() {
- boolean downPressed = false;
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode != KeyEvent.KEYCODE_BACK) {
return false;
}
- if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) {
- downPressed = true;
- } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
- downPressed = false;
- } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) {
- downPressed = false;
- mCallback.onUserCanceled();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
}
return true;
}
@@ -219,16 +310,16 @@
mNegativeButton.setOnClickListener((View v) -> {
if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) {
- mCallback.onUserCanceled();
+ animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
} else {
- mCallback.onNegativePressed();
+ animateAway(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
}
});
mPositiveButton.setOnClickListener((View v) -> {
updateState(STATE_AUTHENTICATED);
mHandler.postDelayed(() -> {
- mCallback.onPositivePressed();
+ animateAway(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
}, getDelayAfterAuthenticatedDurationMs());
});
@@ -248,21 +339,12 @@
mLayout.requestFocus();
}
- public void onSaveState(Bundle bundle) {
- bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
- bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
- bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled());
- bundle.putInt(KEY_STATE, mState);
- bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility());
- bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText());
- bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE));
- bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor());
- }
-
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -278,6 +360,7 @@
}
mNegativeButton.setVisibility(View.VISIBLE);
+ mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) {
mDialog.getLayoutParams().width = (int) mDialogWidth;
@@ -285,7 +368,6 @@
if (mRestoredState == null) {
updateState(STATE_AUTHENTICATING);
- mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
final int hint = getHintStringResourceId();
if (hint != 0) {
mErrorText.setText(hint);
@@ -346,34 +428,49 @@
mSkipIntro = false;
}
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ }
+
private void setDismissesDialog(View v) {
v.setClickable(true);
v.setOnClickListener(v1 -> {
if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
- mCallback.onUserCanceled();
+ animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
}
});
}
- public void startDismiss() {
+ private void animateAway(@DialogViewCallback.DismissedReason int reason) {
+ animateAway(true /* sendReason */, reason);
+ }
+
+ /**
+ * Animate the dialog away
+ * @param reason one of the {@link DialogViewCallback} codes
+ */
+ private void animateAway(boolean sendReason, @DialogViewCallback.DismissedReason int reason) {
if (!mCompletedAnimatingIn) {
Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
mPendingDismissDialog = true;
return;
}
- mAnimatingAway = true;
-
// This is where final cleanup should occur.
final Runnable endActionRunnable = new Runnable() {
@Override
public void run() {
mWindowManager.removeView(BiometricDialogView.this);
- mAnimatingAway = false;
// Set the icons / text back to normal state
handleResetMessage();
showTryAgainButton(false /* show */);
updateState(STATE_IDLE);
+ if (sendReason) {
+ mCallback.onDismissed(reason);
+ }
}
};
@@ -398,47 +495,30 @@
}
/**
- * Force remove the window, cancelling any animation that's happening. This should only be
- * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method
- * will cause the dialog to show without an animation the next time it's attached.
- */
- public void forceRemove() {
- mLayout.animate().cancel();
- mDialog.animate().cancel();
- mWindowManager.removeView(BiometricDialogView.this);
- mAnimatingAway = false;
- mWasForceRemoved = true;
- }
-
- /**
* Skip the intro animation
*/
- public void setSkipIntro(boolean skip) {
+ private void setSkipIntro(boolean skip) {
mSkipIntro = skip;
}
- public boolean isAnimatingAway() {
- return mAnimatingAway;
- }
-
- public void setBundle(Bundle bundle) {
+ private void setBundle(Bundle bundle) {
mBundle = bundle;
}
- public void setRequireConfirmation(boolean requireConfirmation) {
+ private void setRequireConfirmation(boolean requireConfirmation) {
mRequireConfirmation = requireConfirmation;
}
- public boolean requiresConfirmation() {
+ protected boolean requiresConfirmation() {
return mRequireConfirmation;
}
- public void setUserId(int userId) {
+ private void setUserId(int userId) {
mUserId = userId;
}
- public ViewGroup getLayout() {
- return mLayout;
+ private void setOpPackageName(String opPackageName) {
+ mOpPackageName = opPackageName;
}
// Shows an error/help message
@@ -452,17 +532,63 @@
BiometricPrompt.HIDE_DIALOG_DELAY);
}
+ @Override
+ public void show(WindowManager wm, boolean skipIntroAnimation) {
+ setSkipIntro(skipIntroAnimation);
+ wm.addView(this, getLayoutParams(mWindowToken));
+ }
+
+ /**
+ * Force remove the window, cancelling any animation that's happening. This should only be
+ * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method
+ * will cause the dialog to show without an animation the next time it's attached.
+ */
+ @Override
+ public void dismissWithoutCallback(boolean animate) {
+ if (animate) {
+ animateAway(false /* sendReason */, 0 /* reason */);
+ } else {
+ mLayout.animate().cancel();
+ mDialog.animate().cancel();
+ mWindowManager.removeView(BiometricDialogView.this);
+ mWasForceRemoved = true;
+ }
+ }
+
+ @Override
+ public void dismissFromSystemServer() {
+ animateAway(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded() {
+ announceForAccessibility(getResources().getText(getAuthenticatedAccessibilityResourceId()));
+
+ if (requiresConfirmation()) {
+ updateState(STATE_PENDING_CONFIRMATION);
+ } else {
+ mHandler.postDelayed(() -> {
+ animateAway(DialogViewCallback.DISMISSED_AUTHENTICATED);
+ }, getDelayAfterAuthenticatedDurationMs());
+
+ updateState(STATE_AUTHENTICATED);
+ }
+ }
+
+
+ @Override
+ public void onAuthenticationFailed(String message) {
+ updateState(STATE_ERROR);
+ showTemporaryMessage(message);
+ }
+
/**
* Transient help message (acquire) is received, dialog stays showing. Sensor stays in
* "authenticating" state.
* @param message
*/
- public void onHelpReceived(String message) {
- updateState(STATE_ERROR);
- showTemporaryMessage(message);
- }
-
- public void onAuthenticationFailed(String message) {
+ @Override
+ public void onHelp(String message) {
updateState(STATE_ERROR);
showTemporaryMessage(message);
}
@@ -471,14 +597,61 @@
* Hard error is received, dialog will be dismissed soon.
* @param error
*/
- public void onErrorReceived(String error) {
+ @Override
+ public void onError(String error) {
updateState(STATE_ERROR);
showTemporaryMessage(error);
showTryAgainButton(false /* show */);
- mCallback.onErrorShown(); // TODO: Split between fp and face
+
+ mHandler.postDelayed(() -> {
+ animateAway(DialogViewCallback.DISMISSED_ERROR);
+ }, BiometricPrompt.HIDE_DIALOG_DELAY);
}
- public void updateState(int newState) {
+
+ @Override
+ public void onSaveState(Bundle bundle) {
+ bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
+ bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
+ bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled());
+ bundle.putInt(KEY_STATE, mState);
+ bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility());
+ bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText());
+ bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE));
+ bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor());
+ }
+
+ @Override
+ public void restoreState(Bundle bundle) {
+ mRestoredState = bundle;
+ final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY);
+ mTryAgainButton.setVisibility(tryAgainVisibility);
+ final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY);
+ mPositiveButton.setVisibility(confirmVisibility);
+ final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED);
+ mPositiveButton.setEnabled(confirmEnabled);
+ mState = bundle.getInt(KEY_STATE);
+ mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
+ mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
+ final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY);
+ mErrorText.setVisibility(errorTextVisibility);
+ if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE
+ || confirmVisibility == View.INVISIBLE) {
+ announceAccessibilityEvent();
+ }
+ mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
+ if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
+ BiometricPrompt.HIDE_DIALOG_DELAY);
+ }
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return mOpPackageName;
+ }
+
+ protected void updateState(int newState) {
if (newState == STATE_PENDING_CONFIRMATION) {
mHandler.removeMessages(MSG_RESET_MESSAGE);
mErrorText.setTextColor(mTextColor);
@@ -505,48 +678,24 @@
mState = newState;
}
- public void showTryAgainButton(boolean show) {
+ protected void showTryAgainButton(boolean show) {
}
- public void onDialogAnimatedIn() {
+ protected void onDialogAnimatedIn() {
mCompletedAnimatingIn = true;
if (mPendingDismissDialog) {
Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
- startDismiss();
+ animateAway(false /* sendReason */, 0);
mPendingDismissDialog = false;
}
}
- public void restoreState(Bundle bundle) {
- mRestoredState = bundle;
- final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY);
- mTryAgainButton.setVisibility(tryAgainVisibility);
- final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY);
- mPositiveButton.setVisibility(confirmVisibility);
- final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED);
- mPositiveButton.setEnabled(confirmEnabled);
- mState = bundle.getInt(KEY_STATE);
- mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
- mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
- final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY);
- mErrorText.setVisibility(errorTextVisibility);
- if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE
- || confirmVisibility == View.INVISIBLE) {
- announceAccessibilityEvent();
- }
- mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
- if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
- BiometricPrompt.HIDE_DIALOG_DELAY);
- }
- }
-
- protected int getState() {
- return mState;
- }
-
- public WindowManager.LayoutParams getLayoutParams() {
+ /**
+ * @param windowToken token for the window
+ * @return
+ */
+ public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -555,7 +704,7 @@
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("BiometricDialogView");
- lp.token = mWindowToken;
+ lp.token = windowToken;
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
rename to packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
index ae6cb5c..bd87148 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.biometrics;
+package com.android.systemui.biometrics.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -33,7 +33,9 @@
import android.view.View;
import android.view.ViewOutlineProvider;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.biometrics.DialogViewCallback;
/**
* This class loads the view for the system-provided dialog. The view consists of:
@@ -52,7 +54,8 @@
private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
private static final int SIZE_UNKNOWN = 0;
- private static final int SIZE_SMALL = 1;
+ @VisibleForTesting
+ static final int SIZE_SMALL = 1;
private static final int SIZE_GROWING = 2;
private static final int SIZE_BIG = 3;
@@ -152,13 +155,13 @@
announceAccessibilityEvent();
};
- public FaceDialogView(Context context,
- DialogViewCallback callback) {
- super(context, callback);
+ protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) {
+ super(context, callback, injector);
mIconController = new IconController();
}
- private void updateSize(int newSize) {
+ @VisibleForTesting
+ void updateSize(int newSize) {
final float padding = dpToPixels(IMPLICIT_Y_PADDING);
final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
@@ -339,8 +342,8 @@
}
@Override
- public void onErrorReceived(String error) {
- super.onErrorReceived(error);
+ public void onError(String error) {
+ super.onError(error);
// All error messages will cause the dialog to go from small -> big. Error messages
// are messages such as lockout, auth failed, etc.
if (mSize == SIZE_SMALL) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
rename to packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
index 183933e..e597080 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.biometrics;
+package com.android.systemui.biometrics.ui;
import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -22,6 +22,7 @@
import android.util.Log;
import com.android.systemui.R;
+import com.android.systemui.biometrics.DialogViewCallback;
/**
* This class loads the view for the system-provided dialog. The view consists of:
@@ -32,9 +33,9 @@
private static final String TAG = "FingerprintDialogView";
- public FingerprintDialogView(Context context,
- DialogViewCallback callback) {
- super(context, callback);
+ protected FingerprintDialogView(Context context, DialogViewCallback callback,
+ Injector injector) {
+ super(context, callback, injector);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index d7411260..aac721e 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -146,7 +146,13 @@
* @return true if EglSurface is ready.
*/
public boolean createEglSurface(SurfaceHolder surfaceHolder) {
- mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
+ if (hasEglDisplay()) {
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
+ } else {
+ Log.w(TAG, "mEglDisplay is null");
+ return false;
+ }
+
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
@@ -186,7 +192,13 @@
public boolean createEglContext() {
int[] attrib_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_NONE};
- mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
+ if (hasEglDisplay()) {
+ mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
+ } else {
+ Log.w(TAG, "mEglDisplay is null");
+ return false;
+ }
+
if (mEglContext == EGL_NO_CONTEXT) {
Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
@@ -213,6 +225,14 @@
}
/**
+ * Check if we have EglDisplay.
+ * @return true if EglDisplay is ready.
+ */
+ public boolean hasEglDisplay() {
+ return mEglDisplay != null;
+ }
+
+ /**
* Swap buffer to display.
* @return true if swap successfully.
*/
@@ -235,7 +255,9 @@
if (hasEglContext()) {
destroyEglContext();
}
- eglTerminate(mEglDisplay);
+ if (hasEglDisplay()) {
+ eglTerminate(mEglDisplay);
+ }
mEglReady = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index fa0fe13..134d4b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -271,7 +271,7 @@
default void onRotationProposal(int rotation, boolean isValid) { }
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId) { }
+ int type, boolean requireConfirmation, int userId, String opPackageName) { }
default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
@@ -741,7 +741,7 @@
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId) {
+ int type, boolean requireConfirmation, int userId, String opPackageName) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
@@ -749,6 +749,7 @@
args.argi1 = type;
args.arg3 = requireConfirmation;
args.argi2 = userId;
+ args.arg4 = opPackageName;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
}
@@ -1036,7 +1037,8 @@
(IBiometricServiceReceiverInternal) someArgs.arg2,
someArgs.argi1 /* type */,
(boolean) someArgs.arg3 /* requireConfirmation */,
- someArgs.argi2 /* userId */);
+ someArgs.argi2 /* userId */,
+ (String) someArgs.arg4 /* opPackageName */);
}
someArgs.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index d1b3c3c..2a5ccdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -134,7 +134,7 @@
private void updateText() {
CharSequence displayText = null;
- List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
final int N = subs.size();
for (int i = 0; i < N; i++) {
int subId = subs.get(i).getSubscriptionId();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index d9328fa..e4bd4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -107,7 +107,7 @@
*/
public boolean isInLockedDownShade() {
if (!mStatusBarKeyguardViewManager.isShowing()
- || !mStatusBarKeyguardViewManager.isSecure()) {
+ || !mUnlockMethodCache.isMethodSecure()) {
return false;
}
int state = mStateController.getState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index c4d346c..dc9b373 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -77,6 +77,7 @@
}
};
private final Runnable mRemoveViewRunnable = this::removeView;
+ private final KeyguardBypassController mKeyguardBypassController;
protected KeyguardHostView mKeyguardView;
private final Runnable mResetRunnable = ()-> {
if (mKeyguardView != null) {
@@ -97,7 +98,8 @@
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache,
- KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardBypassController keyguardBypassController, Handler handler) {
mContext = context;
mCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -109,6 +111,7 @@
mHandler = handler;
mUnlockMethodCache = unlockMethodCache;
mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mKeyguardBypassController = keyguardBypassController;
}
public void show(boolean resetSecuritySelection) {
@@ -171,7 +174,8 @@
// Split up the work over multiple frames.
DejankUtils.removeCallbacks(mResetRunnable);
if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer()
- && !mKeyguardUpdateMonitor.userNeedsStrongAuth()) {
+ && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+ && !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
DejankUtils.postAfterTraversal(mShowRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3508c90..ccb85fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -221,7 +221,7 @@
mBiometricUnlockController = biometricUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
- mExpansionCallback, falsingManager);
+ mExpansionCallback, falsingManager, bypassController);
mNotificationPanelView = notificationPanelView;
notificationPanelView.addExpansionListener(this);
mBypassController = bypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 0d6178b..f2c0434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -97,7 +97,7 @@
boolean allSimsMissing = true;
CharSequence displayText = null;
- List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
final int N = subs.size();
for (int i = 0; i < N; i++) {
int subId = subs.get(i).getSubscriptionId();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index e75365e..7acf4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -537,8 +537,14 @@
return mConfig.nr5GIconMap.get(Config.NR_CONNECTED);
}
} else if (nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED) {
- if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED)) {
- return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED);
+ if (mCurrentState.activityDormant) {
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED_RRC_IDLE)) {
+ return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED_RRC_IDLE);
+ }
+ } else {
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED_RRC_CON)) {
+ return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED_RRC_CON);
+ }
}
} else if (nrState == NetworkRegistrationInfo.NR_STATE_RESTRICTED) {
if (mConfig.nr5GIconMap.containsKey(Config.NR_RESTRICTED)) {
@@ -559,6 +565,8 @@
|| activity == TelephonyManager.DATA_ACTIVITY_IN;
mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
|| activity == TelephonyManager.DATA_ACTIVITY_OUT;
+ mCurrentState.activityDormant = activity == TelephonyManager.DATA_ACTIVITY_DORMANT;
+
notifyListenersIfNecessary();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 04f96a4..292571e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -1112,8 +1112,9 @@
static class Config {
static final int NR_CONNECTED_MMWAVE = 1;
static final int NR_CONNECTED = 2;
- static final int NR_NOT_RESTRICTED = 3;
- static final int NR_RESTRICTED = 4;
+ static final int NR_NOT_RESTRICTED_RRC_IDLE = 3;
+ static final int NR_NOT_RESTRICTED_RRC_CON = 4;
+ static final int NR_RESTRICTED = 5;
Map<Integer, MobileIconGroup> nr5GIconMap = new HashMap<>();
@@ -1133,10 +1134,11 @@
*/
private static final Map<String, Integer> NR_STATUS_STRING_TO_INDEX;
static {
- NR_STATUS_STRING_TO_INDEX = new HashMap<>(4);
+ NR_STATUS_STRING_TO_INDEX = new HashMap<>(5);
NR_STATUS_STRING_TO_INDEX.put("connected_mmwave", NR_CONNECTED_MMWAVE);
NR_STATUS_STRING_TO_INDEX.put("connected", NR_CONNECTED);
- NR_STATUS_STRING_TO_INDEX.put("not_restricted", NR_NOT_RESTRICTED);
+ NR_STATUS_STRING_TO_INDEX.put("not_restricted_rrc_idle", NR_NOT_RESTRICTED_RRC_IDLE);
+ NR_STATUS_STRING_TO_INDEX.put("not_restricted_rrc_con", NR_NOT_RESTRICTED_RRC_CON);
NR_STATUS_STRING_TO_INDEX.put("restricted", NR_RESTRICTED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 9ec30d4..abe3f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -258,6 +258,7 @@
boolean enabled;
boolean activityIn;
boolean activityOut;
+ public boolean activityDormant;
int level;
IconGroup iconGroup;
int inetCondition;
@@ -274,6 +275,7 @@
inetCondition = state.inetCondition;
activityIn = state.activityIn;
activityOut = state.activityOut;
+ activityDormant = state.activityDormant;
rssi = state.rssi;
time = state.time;
}
@@ -297,6 +299,7 @@
.append("iconGroup=").append(iconGroup).append(',')
.append("activityIn=").append(activityIn).append(',')
.append("activityOut=").append(activityOut).append(',')
+ .append("activityDormant=").append(activityDormant).append(',')
.append("rssi=").append(rssi).append(',')
.append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time));
}
@@ -314,6 +317,7 @@
&& other.iconGroup == iconGroup
&& other.activityIn == activityIn
&& other.activityOut == activityOut
+ && other.activityDormant == activityDormant
&& other.rssi == rssi;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 0044ca7..1ae1b97 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -70,15 +70,10 @@
private static final CharSequence AIRPLANE_MODE_TEXT = "Airplane mode";
private static final String TEST_CARRIER = "TEST_CARRIER";
private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
- private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
private static final int TEST_CARRIER_ID = 1;
private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
- DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
- TEST_CARRIER_ID, 0);
- private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(0, "", 0,
- TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
- DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
+ DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
TEST_CARRIER_ID, 0);
private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
@@ -125,7 +120,6 @@
mKeyguardUpdateMonitor);
// This should not start listening on any of the real dependencies
mCarrierTextController.setListening(mCarrierTextCallback);
- mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(false);
}
@Test
@@ -134,7 +128,7 @@
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -154,7 +148,7 @@
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(IccCardConstants.State.READY);
when(mKeyguardUpdateMonitor.getSimState(1)).thenReturn(
IccCardConstants.State.CARD_IO_ERROR);
@@ -178,7 +172,7 @@
@Test
public void testWrongSlots() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
new ArrayList<>());
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
IccCardConstants.State.CARD_IO_ERROR);
@@ -192,7 +186,7 @@
@Test
public void testMoreSlotsThanSubs() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
new ArrayList<>());
// STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
@@ -242,7 +236,7 @@
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -266,7 +260,7 @@
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION_ROAMING);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -287,7 +281,7 @@
@Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
new ArrayList<>());
ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -311,7 +305,7 @@
list.add(TEST_SUBSCRIPTION);
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -336,7 +330,7 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt()))
.thenReturn(IccCardConstants.State.READY)
.thenReturn(IccCardConstants.State.NOT_READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -361,7 +355,7 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt()))
.thenReturn(IccCardConstants.State.NOT_READY)
.thenReturn(IccCardConstants.State.READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
@@ -388,7 +382,7 @@
.thenReturn(IccCardConstants.State.READY)
.thenReturn(IccCardConstants.State.NOT_READY)
.thenReturn(IccCardConstants.State.READY);
- when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -403,30 +397,6 @@
captor.getValue().carrierText);
}
- @Test
- public void testCarrierText_GroupedSubWithOpportunisticCarrierText() {
- reset(mCarrierTextCallback);
- List<SubscriptionInfo> list = new ArrayList<>();
- list.add(TEST_SUBSCRIPTION);
- list.add(TEST_SUBSCRIPTION_2);
- when(mKeyguardUpdateMonitor.getSimState(anyInt()))
- .thenReturn(IccCardConstants.State.READY);
-
- mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(true);
- when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
-
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
- ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
-
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
- verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
- assertEquals(TEST_CARRIER_2, captor.getValue().carrierText);
- }
-
public static class TestCarrierTextController extends CarrierTextController {
private KeyguardUpdateMonitor mKUM;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index db6177a..a3cb6c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,15 +16,18 @@
package com.android.keyguard;
+import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -42,6 +45,7 @@
import android.os.Bundle;
import android.os.UserManager;
import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -62,6 +66,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@@ -73,7 +79,18 @@
// new tests.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardUpdateMonitorTest extends SysuiTestCase {
-
+ private static final String TEST_CARRIER = "TEST_CARRIER";
+ private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
+ private static final int TEST_CARRIER_ID = 1;
+ private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
+ private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
+ TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
+ TEST_CARRIER_ID, 0);
+ private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
+ TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
+ TEST_CARRIER_ID, 0);
@Mock
private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@Mock
@@ -92,6 +109,8 @@
private DevicePolicyManager mDevicePolicyManager;
@Mock
private KeyguardBypassController mKeyguardBypassController;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
private TestableLooper mTestableLooper;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -119,6 +138,7 @@
context.addMockSystemService(FaceManager.class, mFaceManager);
context.addMockSystemService(UserManager.class, mUserManager);
context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+ context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
mTestableLooper = TestableLooper.get(this);
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
@@ -441,6 +461,22 @@
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
+ @Test
+ public void testGetSubscriptionInfo_whenInGroupedSubWithOpportunistic() {
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION_2);
+ when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+ mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+ TEST_SUBSCRIPTION_2.getSubscriptionId());
+ mTestableLooper.processAllMessages();
+
+ List<SubscriptionInfo> listToVerify = mKeyguardUpdateMonitor
+ .getFilteredSubscriptionInfo(false);
+ assertThat(listToVerify.size()).isEqualTo(1);
+ assertThat(listToVerify.get(0)).isEqualTo(TEST_SUBSCRIPTION_2);
+ }
+
private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
new file mode 100644
index 0000000..8f2f8b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 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.systemui.biometrics;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.IActivityTaskManager;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class BiometricDialogImplTest extends SysuiTestCase {
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private IBiometricServiceReceiverInternal mReceiver;
+ @Mock
+ private BiometricDialog mDialog1;
+ @Mock
+ private BiometricDialog mDialog2;
+
+ private TestableBiometricDialogImpl mBiometricDialogImpl;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ TestableContext context = spy(mContext);
+
+ mContext.putComponent(StatusBar.class, mock(StatusBar.class));
+ mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+
+ when(context.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+
+ when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
+ when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
+
+ mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector());
+ mBiometricDialogImpl.mContext = context;
+ mBiometricDialogImpl.mComponents = mContext.getComponents();
+
+ mBiometricDialogImpl.start();
+ }
+
+ // Callback tests
+
+ @Test
+ public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_USER_CANCELED);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ }
+
+ @Test
+ public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ }
+
+ @Test
+ public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
+ }
+
+ @Test
+ public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_AUTHENTICATED);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
+ }
+
+ @Test
+ public void testSendsReasonError_whenDismissedByError() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_ERROR);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
+ }
+
+ @Test
+ public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
+ verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+ }
+
+ // Statusbar tests
+
+ @Test
+ public void testShowInvoked_whenSystemRequested()
+ throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+ }
+
+ @Test
+ public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */);
+ verify(mDialog1).onAuthenticationSucceeded();
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ final String failureReason = "failure reason";
+ mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason);
+
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mDialog1).onAuthenticationFailed(captor.capture());
+
+ assertEquals(captor.getValue(), failureReason);
+ }
+
+ @Test
+ public void testOnHelpInvoked_whenSystemRequested() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ final String helpMessage = "help";
+ mBiometricDialogImpl.onBiometricHelp(helpMessage);
+
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mDialog1).onHelp(captor.capture());
+
+ assertEquals(captor.getValue(), helpMessage);
+ }
+
+ @Test
+ public void testOnErrorInvoked_whenSystemRequested() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ final String errMessage = "error message";
+ mBiometricDialogImpl.onBiometricError(errMessage);
+
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mDialog1).onError(captor.capture());
+
+ assertEquals(captor.getValue(), errMessage);
+ }
+
+ @Test
+ public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.hideBiometricDialog();
+ verify(mDialog1).dismissFromSystemServer();
+ }
+
+ @Test
+ public void testClientNotified_whenDismissedBySystemServer() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ mBiometricDialogImpl.hideBiometricDialog();
+ verify(mDialog1).dismissFromSystemServer();
+
+ assertNotNull(mBiometricDialogImpl.mCurrentDialog);
+ assertNotNull(mBiometricDialogImpl.mReceiver);
+ }
+
+ // Corner case tests
+
+ @Test
+ public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+
+ showDialog(BiometricPrompt.TYPE_FACE);
+
+ // First dialog should be dismissed without animation
+ verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
+
+ // Second dialog should be shown without animation
+ verify(mDialog2).show(any(), eq(true)) /* skipIntro */;
+ }
+
+ @Test
+ public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+
+ mBiometricDialogImpl.onConfigurationChanged(new Configuration());
+
+ ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mDialog1).onSaveState(captor.capture());
+
+ // Old dialog doesn't animate
+ verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
+
+ // Saved state is restored into new dialog
+ ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
+ verify(mDialog2).restoreState(captor2.capture());
+
+ // Dialog for new configuration skips intro
+ verify(mDialog2).show(any(), eq(true) /* skipIntro */);
+
+ // TODO: This should check all values we want to save/restore
+ assertEquals(captor.getValue(), captor2.getValue());
+ }
+
+ @Test
+ public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+
+ List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ taskInfo.topActivity = mock(ComponentName.class);
+ when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
+ tasks.add(taskInfo);
+ when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+
+ mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged();
+ waitForIdleSync();
+
+ assertNull(mBiometricDialogImpl.mCurrentDialog);
+ assertNull(mBiometricDialogImpl.mReceiver);
+ verify(mDialog1).dismissWithoutCallback(true /* animate */);
+ verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
+ }
+
+ // Helpers
+
+ private void showDialog(int type) {
+ mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(),
+ mReceiver /* receiver */,
+ type,
+ true /* requireConfirmation */,
+ 0 /* userId */,
+ "testPackage");
+ }
+
+ private Bundle createTestDialogBundle() {
+ Bundle bundle = new Bundle();
+
+ bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
+ bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, "Subtitle");
+ bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, "Description");
+ bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative Button");
+
+ // RequireConfirmation is a hint to BiometricService. This can be forced to be required
+ // by user settings, and should be tested in BiometricService.
+ bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
+
+ return bundle;
+ }
+
+ private final class TestableBiometricDialogImpl extends BiometricDialogImpl {
+ private int mBuildCount = 0;
+
+ public TestableBiometricDialogImpl(Injector injector) {
+ super(injector);
+ }
+
+ @Override
+ protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
+ boolean requireConfirmation, int userId, int type, String opPackageName) {
+ BiometricDialog dialog;
+ if (mBuildCount == 0) {
+ dialog = mDialog1;
+ } else if (mBuildCount == 1) {
+ dialog = mDialog2;
+ } else {
+ dialog = null;
+ }
+ mBuildCount++;
+ return dialog;
+ }
+ }
+
+ private final class MockInjector extends BiometricDialogImpl.Injector {
+ @Override
+ IActivityTaskManager getActivityTaskManager() {
+ return mock(IActivityTaskManager.class);
+ }
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
new file mode 100644
index 0000000..bbdd837
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 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.systemui.biometrics.ui;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.spy;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.DialogViewCallback;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class BiometricDialogViewTest extends SysuiTestCase {
+
+ FaceDialogView mFaceDialogView;
+
+ private static final String TITLE = "Title";
+ private static final String SUBTITLE = "Subtitle";
+ private static final String DESCRIPTION = "Description";
+ private static final String NEGATIVE_BUTTON = "Negative Button";
+
+ private static final String TEST_HELP = "Help";
+
+ TestableContext mTestableContext;
+ @Mock
+ private DialogViewCallback mCallback;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private DevicePolicyManager mDpm;
+
+ private static class Injector extends BiometricDialogView.Injector {
+ @Override
+ public WakefulnessLifecycle getWakefulnessLifecycle() {
+ final WakefulnessLifecycle lifecycle = new WakefulnessLifecycle();
+ lifecycle.dispatchFinishedWakingUp();
+ return lifecycle;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTestableContext = spy(mContext);
+ mTestableContext.addMockSystemService(UserManager.class, mUserManager);
+ mTestableContext.addMockSystemService(DevicePolicyManager.class, mDpm);
+ }
+
+ @Test
+ public void testContentStates_confirmationRequired_authenticated() {
+ mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
+ true /* requireConfirmation */);
+ mFaceDialogView.onAttachedToWindow();
+
+ // When starting authentication
+ assertEquals(View.VISIBLE, mFaceDialogView.mTitleText.getVisibility());
+ assertEquals(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
+ assertEquals(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
+ assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
+ assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
+ assertEquals(View.VISIBLE, mFaceDialogView.mNegativeButton.getVisibility());
+ assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
+
+ // Contents are as expected
+ assertTrue(TITLE.contentEquals(mFaceDialogView.mTitleText.getText()));
+ assertTrue(SUBTITLE.contentEquals(mFaceDialogView.mSubtitleText.getText()));
+ assertTrue(DESCRIPTION.contentEquals(mFaceDialogView.mDescriptionText.getText()));
+ assertTrue(mFaceDialogView.mPositiveButton.getText().toString()
+ .contentEquals(mContext.getString(R.string.biometric_dialog_confirm)));
+ assertTrue(NEGATIVE_BUTTON.contentEquals(mFaceDialogView.mNegativeButton.getText()));
+ assertTrue(mFaceDialogView.mTryAgainButton.getText().toString()
+ .contentEquals(mContext.getString(R.string.biometric_dialog_try_again)));
+
+ // When help message is received
+ mFaceDialogView.onHelp(TEST_HELP);
+ assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
+ assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
+
+ // When authenticated, confirm button comes out
+ mFaceDialogView.onAuthenticationSucceeded();
+ assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
+ assertEquals(true, mFaceDialogView.mPositiveButton.isEnabled());
+ }
+
+ @Test
+ public void testContentStates_confirmationNotRequired_authenticated() {
+ mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
+ false /* requireConfirmation */);
+ mFaceDialogView.onAttachedToWindow();
+ mFaceDialogView.updateSize(FaceDialogView.SIZE_SMALL);
+
+ assertEquals(View.INVISIBLE, mFaceDialogView.mTitleText.getVisibility());
+ assertNotSame(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
+ assertNotSame(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
+ assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
+ assertEquals(View.GONE, mFaceDialogView.mPositiveButton.getVisibility());
+ assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
+ assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
+ }
+
+ @Test
+ public void testContentStates_confirmationNotRequired_help() {
+ mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
+ false /* requireConfirmation */);
+ mFaceDialogView.onAttachedToWindow();
+
+ mFaceDialogView.onHelp(TEST_HELP);
+ assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
+ assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
+ }
+
+ @Test
+ public void testBack_sendsUserCanceled() {
+ // TODO: Need robolectric framework to wait for handler to complete
+ }
+
+ @Test
+ public void testScreenOff_sendsUserCanceled() {
+ // TODO: Need robolectric framework to wait for handler to complete
+ }
+
+ @Test
+ public void testRestoreState_contentStatesCorrect() {
+ mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
+ false /* requireConfirmation */);
+ mFaceDialogView.onAttachedToWindow();
+ mFaceDialogView.onAuthenticationFailed(TEST_HELP);
+
+ final Bundle bundle = new Bundle();
+ mFaceDialogView.onSaveState(bundle);
+
+ mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
+ false /* requireConfirmation */);
+ mFaceDialogView.restoreState(bundle);
+ mFaceDialogView.onAttachedToWindow();
+
+ assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility());
+ }
+
+ private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback,
+ boolean requireConfirmation) {
+ return (FaceDialogView) new BiometricDialogView.Builder(context)
+ .setCallback(callback)
+ .setBiometricPromptBundle(createTestDialogBundle())
+ .setRequireConfirmation(requireConfirmation)
+ .setUserId(0)
+ .setOpPackageName("test_package")
+ .build(BiometricDialogView.Builder.TYPE_FACE, new Injector());
+ }
+
+ private Bundle createTestDialogBundle() {
+ Bundle bundle = new Bundle();
+
+ bundle.putCharSequence(BiometricPrompt.KEY_TITLE, TITLE);
+ bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, SUBTITLE);
+ bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, DESCRIPTION);
+ bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, NEGATIVE_BUTTON);
+
+ // RequireConfirmation is a hint to BiometricService. This can be forced to be required
+ // by user settings, and should be tested in BiometricService.
+ bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
+
+ return bundle;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
deleted file mode 100644
index 7ea6493..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.classifier;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertThat;
-
-import android.os.Handler;
-import android.provider.DeviceConfig;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
-import com.android.systemui.shared.plugins.PluginManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class FalsingManagerProxyTest extends SysuiTestCase {
- @Mock
- PluginManager mPluginManager;
- private boolean mDefaultConfigValue;
- private Handler mHandler;
- private TestableLooper mTestableLooper;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
- mHandler = new Handler(mTestableLooper.getLooper());
- mDefaultConfigValue = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, false);
- // In case it runs on a device where it's been set to true, set it to false by hand.
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
- }
-
- @After
- public void tearDown() {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, mDefaultConfigValue ? "true" : "false", false);
- }
-
- @Test
- public void test_brightLineFalsingManagerDisabled() {
- FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
-
- assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
- }
-
- @Test
- public void test_brightLineFalsingManagerEnabled() {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
- FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
-
- assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
- }
-
- @Test
- public void test_brightLineFalsingManagerToggled() {
- FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
- assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
- mTestableLooper.processAllMessages();
- proxy.setupFalsingManager(getContext());
- assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
-
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
- mTestableLooper.processAllMessages();
- proxy.setupFalsingManager(getContext());
- assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
new file mode 100644
index 0000000..b4a60d6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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.systemui.glwallpaper;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.RETURNS_DEFAULTS;
+import static org.mockito.Mockito.mock;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceHolder;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class EglHelperTest extends SysuiTestCase {
+
+ @Mock
+ private EglHelper mEglHelper;
+ @Mock
+ private SurfaceHolder mSurfaceHolder;
+
+ @Before
+ public void setUp() throws Exception {
+ mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS);
+ mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS);
+ }
+
+ @Test
+ public void testInit_finish() {
+ mEglHelper.init(mSurfaceHolder);
+ mEglHelper.finish();
+ }
+
+ @Test
+ public void testFinish_shouldNotCrash() {
+ assertFalse(mEglHelper.hasEglDisplay());
+ assertFalse(mEglHelper.hasEglSurface());
+ assertFalse(mEglHelper.hasEglContext());
+
+ mEglHelper.finish();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 1e1f2156..b252a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -365,4 +365,45 @@
waitForIdleSync();
verify(mCallbacks).onRecentsAnimationStateChanged(eq(true));
}
+
+ @Test
+ public void testShowBiometricDialog() {
+ Bundle bundle = new Bundle();
+ String packageName = "test";
+ mCommandQueue.showBiometricDialog(bundle, null /* receiver */, 1, true, 3, packageName);
+ waitForIdleSync();
+ verify(mCallbacks).showBiometricDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3),
+ eq(packageName));
+ }
+
+ @Test
+ public void testOnBiometricAuthenticated() {
+ String failureReason = "test_failure_reason";
+ mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason);
+ waitForIdleSync();
+ verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason));
+ }
+
+ @Test
+ public void testOnBiometricHelp() {
+ String helpMessage = "test_help_message";
+ mCommandQueue.onBiometricHelp(helpMessage);
+ waitForIdleSync();
+ verify(mCallbacks).onBiometricHelp(eq(helpMessage));
+ }
+
+ @Test
+ public void testOnBiometricError() {
+ String errorMessage = "test_error_message";
+ mCommandQueue.onBiometricError(errorMessage);
+ waitForIdleSync();
+ verify(mCallbacks).onBiometricError(eq(errorMessage));
+ }
+
+ @Test
+ public void testHideBiometricDialog() {
+ mCommandQueue.hideBiometricDialog();
+ waitForIdleSync();
+ verify(mCallbacks).hideBiometricDialog();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 907e695..cd60e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
@@ -84,6 +85,8 @@
@Mock
private UnlockMethodCache mUnlockMethodCache;
@Mock
+ private KeyguardBypassController mKeyguardBypassController;
+ @Mock
private Handler mHandler;
private KeyguardBouncer mBouncer;
@@ -98,7 +101,8 @@
when(mKeyguardHostView.getHeight()).thenReturn(500);
mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager,
- mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor, mHandler) {
+ mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor,
+ mKeyguardBypassController, mHandler) {
@Override
protected void inflateView() {
super.inflateView();
@@ -391,6 +395,15 @@
}
@Test
+ public void testShow_delaysIfFaceAuthIsRunning_unlessBypass() {
+ when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+ mBouncer.show(true /* reset */);
+
+ verify(mHandler, never()).postDelayed(any(), anyLong());
+ }
+
+ @Test
public void testRegisterUpdateMonitorCallback() {
verify(mKeyguardUpdateMonitor).registerCallback(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 9ae9ceb..e691b7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -228,6 +228,18 @@
NetworkControllerImpl.Config.add5GIconMapping("connected:5g", mConfig);
}
+ public void setupNr5GIconConfigurationForNotRestrictedRrcCon() {
+ NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig);
+ NetworkControllerImpl.Config.add5GIconMapping("connected:5g_plus", mConfig);
+ NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_con:5g", mConfig);
+ }
+
+ public void setupNr5GIconConfigurationForNotRestrictedRrcIdle() {
+ NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig);
+ NetworkControllerImpl.Config.add5GIconMapping("connected:5g_plus", mConfig);
+ NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_idle:5g", mConfig);
+ }
+
public void setConnectivityViaBroadcast(
int networkType, boolean validated, boolean isConnected) {
setConnectivityCommon(networkType, validated, isConnected);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 5128675..f0394da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -175,6 +175,35 @@
}
@Test
+ public void testNr5GIcon_NrNotRestrictedRrcCon_show5GIcon() {
+ setupNr5GIconConfigurationForNotRestrictedRrcCon();
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ updateDataActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(ss).getNrState();
+ mPhoneStateListener.onServiceStateChanged(ss);
+
+ verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, TelephonyIcons.ICON_5G,
+ true, DEFAULT_QS_SIGNAL_STRENGTH, TelephonyIcons.ICON_5G, true, true);
+ }
+
+ @Test
+ public void testNr5GIcon_NrNotRestrictedRrcIdle_show5GIcon() {
+ setupNr5GIconConfigurationForNotRestrictedRrcIdle();
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ updateDataActivity(TelephonyManager.DATA_ACTIVITY_DORMANT);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(ss).getNrState();
+ mPhoneStateListener.onServiceStateChanged(ss);
+
+ verifyDataIndicators(TelephonyIcons.ICON_5G);
+ }
+
+ @Test
public void testNr5GIcon_NrConnectedWithoutMMWave_show5GIcon() {
setupDefaultNr5GIconConfiguration();
setupDefaultSignal();
diff --git a/services/Android.bp b/services/Android.bp
index 54157d0..27f8d36 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -43,6 +43,10 @@
"android.hidl.manager-V1.0-java",
],
+ plugins: [
+ "compat-changeid-annotation-processor",
+ ],
+
// Uncomment to enable output of certain warnings (deprecated, unchecked)
//javacflags: ["-Xlint"],
@@ -56,3 +60,9 @@
defaults: ["libservices.core-libs"],
whole_static_libs: ["libservices.core"],
}
+
+platform_compat_config {
+ name: "services-platform-compat-config",
+ prefix: "services",
+ src: ":services",
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 1356157..5e9c08b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -348,13 +348,13 @@
}
}
- protected abstract boolean isCalledForCurrentUserLocked();
+ protected abstract boolean hasRightsToCurrentUserLocked();
@Override
public List<AccessibilityWindowInfo> getWindows() {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -387,7 +387,7 @@
public AccessibilityWindowInfo getWindow(int windowId) {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -420,7 +420,7 @@
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -481,7 +481,7 @@
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -542,7 +542,7 @@
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -602,7 +602,7 @@
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
@@ -663,7 +663,7 @@
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -728,7 +728,7 @@
throws RemoteException {
final int resolvedWindowId;
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -748,7 +748,7 @@
@Override
public boolean performGlobalAction(int action) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
}
@@ -771,7 +771,7 @@
@Override
public float getMagnificationScale(int displayId) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return 1.0f;
}
}
@@ -787,7 +787,7 @@
public Region getMagnificationRegion(int displayId) {
synchronized (mLock) {
final Region region = Region.obtain();
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return region;
}
MagnificationController magnificationController =
@@ -810,7 +810,7 @@
@Override
public float getMagnificationCenterX(int displayId) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return 0.0f;
}
MagnificationController magnificationController =
@@ -832,7 +832,7 @@
@Override
public float getMagnificationCenterY(int displayId) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return 0.0f;
}
MagnificationController magnificationController =
@@ -864,7 +864,7 @@
@Override
public boolean resetMagnification(int displayId, boolean animate) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
if (!mSecurityPolicy.canControlMagnification(this)) {
@@ -886,7 +886,7 @@
public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
float centerY, boolean animate) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
if (!mSecurityPolicy.canControlMagnification(this)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 315d6fa..b88b24e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -468,7 +468,7 @@
}
}
- private boolean hasPermission(String permission) {
+ boolean hasPermission(String permission) {
return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 02f7821..d7f61e5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
@@ -27,6 +28,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -211,19 +213,31 @@
}
@Override
- protected boolean isCalledForCurrentUserLocked() {
+ protected boolean hasRightsToCurrentUserLocked() {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
- return resolvedUserId == mSystemSupport.getCurrentUserIdLocked();
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.ROOT_UID
+ || callingUid == Process.SYSTEM_UID
+ || callingUid == Process.SHELL_UID) {
+ return true;
+ }
+ if (mSecurityPolicy.resolveProfileParentLocked(UserHandle.getUserId(callingUid))
+ == mSystemSupport.getCurrentUserIdLocked()) {
+ return true;
+ }
+ if (mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ || mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
+ return true;
+ }
+ return false;
}
@Override
public boolean setSoftKeyboardShowMode(int showMode) {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
final UserState userState = mUserStateWeakReference.get();
@@ -241,7 +255,7 @@
@Override
public boolean isAccessibilityButtonAvailable() {
synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
+ if (!hasRightsToCurrentUserLocked()) {
return false;
}
UserState userState = mUserStateWeakReference.get();
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 2698b72..79d975d 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -66,6 +66,7 @@
mUiAutomationServiceOwner.unlinkToDeath(this, 0);
mUiAutomationServiceOwner = null;
destroyUiAutomationService();
+ Slog.v(LOG_TAG, "UiAutomation service owner died");
}
};
@@ -263,7 +264,7 @@
}
@Override
- protected boolean isCalledForCurrentUserLocked() {
+ protected boolean hasRightsToCurrentUserLocked() {
// Allow UiAutomation to work for any user
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index a64f4e4..6b7c3e6 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -807,6 +807,7 @@
packageName, versionCode, userId);
final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled);
mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName);
+ injectDisableAppInfo(options, userId, packageName);
return options;
}
@@ -820,6 +821,19 @@
}
return false;
}
+
+ private void injectDisableAppInfo(@NonNull AutofillOptions options, int userId,
+ String packageName) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ options.appDisabledExpiration = service.getAppDisabledExpirationLocked(
+ packageName);
+ options.disabledActivities = service.getAppDisabledActivitiesLocked(
+ packageName);
+ }
+ }
+ }
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 1e1e07d..d7ed2e9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -980,12 +980,12 @@
for (int i = 0; i < size; i++) {
final String packageName = mDisabledApps.keyAt(i);
final long expiration = mDisabledApps.valueAt(i);
- builder.append(prefix).append(prefix)
- .append(i).append(". ").append(packageName).append(": ");
- TimeUtils.formatDuration((expiration - now), builder);
- builder.append('\n');
- }
- pw.println(builder);
+ builder.append(prefix).append(prefix)
+ .append(i).append(". ").append(packageName).append(": ");
+ TimeUtils.formatDuration((expiration - now), builder);
+ builder.append('\n');
+ }
+ pw.println(builder);
}
pw.print(prefix); pw.print("Disabled activities: ");
@@ -1000,12 +1000,12 @@
for (int i = 0; i < size; i++) {
final ComponentName component = mDisabledActivities.keyAt(i);
final long expiration = mDisabledActivities.valueAt(i);
- builder.append(prefix).append(prefix)
- .append(i).append(". ").append(component).append(": ");
- TimeUtils.formatDuration((expiration - now), builder);
- builder.append('\n');
- }
- pw.println(builder);
+ builder.append(prefix).append(prefix)
+ .append(i).append(". ").append(component).append(": ");
+ TimeUtils.formatDuration((expiration - now), builder);
+ builder.append('\n');
+ }
+ pw.println(builder);
}
final int size = mSessions.size();
@@ -1418,6 +1418,36 @@
}
}
+ // Called by AutofillManagerService
+ long getAppDisabledExpirationLocked(@NonNull String packageName) {
+ if (mDisabledApps == null) {
+ return 0;
+ }
+ final Long expiration = mDisabledApps.get(packageName);
+ return expiration != null ? expiration : 0;
+ }
+
+ // Called by AutofillManagerService
+ @Nullable
+ ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) {
+ if (mDisabledActivities != null) {
+ final int size = mDisabledActivities.size();
+ ArrayMap<String, Long> disabledList = null;
+ for (int i = 0; i < size; i++) {
+ final ComponentName component = mDisabledActivities.keyAt(i);
+ if (packageName.equals(component.getPackageName())) {
+ if (disabledList == null) {
+ disabledList = new ArrayMap<>();
+ }
+ final long expiration = mDisabledActivities.valueAt(i);
+ disabledList.put(component.flattenToShortString(), expiration);
+ }
+ }
+ return disabledList;
+ }
+ return null;
+ }
+
/**
* Checks if autofill is disabled by service to the given activity.
*/
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4579a3d..48f16ac 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -772,13 +772,19 @@
final long disableDuration = response.getDisableDuration();
if (disableDuration > 0) {
final int flags = response.getFlags();
- if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
+ final boolean disableActivityOnly =
+ (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0;
+ notifyDisableAutofillToClient(disableDuration,
+ disableActivityOnly ? mComponentName : null);
+
+ if (disableActivityOnly) {
mService.disableAutofillForActivity(mComponentName, disableDuration,
id, mCompatMode);
} else {
mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
id, mCompatMode);
}
+
// Although "standard" autofill is disabled, it might still trigger augmented autofill
if (triggerAugmentedAutofillLocked() != null) {
mForAugmentedAutofillOnly = true;
@@ -2567,6 +2573,17 @@
}
}
+ private void notifyDisableAutofillToClient(long disableDuration, ComponentName componentName) {
+ synchronized (mLock) {
+ if (mCurrentViewId == null) return;
+ try {
+ mClient.notifyDisableAutofill(disableDuration, componentName);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying client disable autofill: id=" + mCurrentViewId, e);
+ }
+ }
+ }
+
@GuardedBy("mLock")
private void updateTrackedIdsLocked() {
// Only track the views of the last response as only those are reported back to the
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 23afce6..222a6f2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,56 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import static java.util.Collections.emptySet;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.backup.BackupManager;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
+import com.android.server.backup.utils.RandomAccessFileUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Set;
/**
* Definition of the system service that performs backup/restore operations.
@@ -31,53 +75,1502 @@
* <p>This class is responsible for handling user-aware operations and acts as a delegator, routing
* incoming calls to the appropriate per-user {@link UserBackupManagerService} to handle the
* corresponding backup/restore operation.
+ *
+ * <p>It also determines whether the backup service is available. It can be disabled in the
+ * following two ways:
+ *
+ * <ul>
+ * <li>Temporary - call {@link #setBackupServiceActive(int, boolean)}, or
+ * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
+ * </ul>
+ *
+ * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through
+ * privileged callers (currently {@link DevicePolicyManager}). If called on {@link
+ * UserHandle#USER_SYSTEM}, backup is disabled for all users.
*/
-public class BackupManagerService {
+public class BackupManagerService extends IBackupManager.Stub {
public static final String TAG = "BackupManagerService";
public static final boolean DEBUG = true;
public static final boolean MORE_DEBUG = false;
public static final boolean DEBUG_SCHEDULING = true;
- private final Context mContext;
- private final Trampoline mTrampoline;
- private final SparseArray<UserBackupManagerService> mServiceUsers;
+ @VisibleForTesting
+ static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:";
- /** Instantiate a new instance of {@link BackupManagerService}. */
- public BackupManagerService(
- Context context,
- Trampoline trampoline,
- SparseArray<UserBackupManagerService> userServices) {
- mContext = checkNotNull(context);
- mTrampoline = checkNotNull(trampoline);
- // TODO(b/135661048): Remove
- mServiceUsers = userServices;
+ /**
+ * Name of file that disables the backup service. If this file exists, then backup is disabled
+ * for all users.
+ */
+ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
+
+ /**
+ * Name of file for non-system users that enables the backup service for the user. Backup is
+ * disabled by default in non-system users.
+ */
+ private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
+
+ /**
+ * Name of file for non-system users that remembers whether backup was explicitly activated or
+ * deactivated with a call to setBackupServiceActive.
+ */
+ private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated";
+
+ // Product-level suppression of backup/restore.
+ private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
+
+ private static final String BACKUP_THREAD = "backup";
+
+ static BackupManagerService sInstance;
+
+ static BackupManagerService getInstance() {
+ return checkNotNull(sInstance);
+ }
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+
+ private final boolean mGlobalDisable;
+ // Lock to write backup suppress files.
+ // TODD(b/121198006): remove this object and synchronized all methods on "this".
+ private final Object mStateLock = new Object();
+
+ private final Handler mHandler;
+ private final Set<ComponentName> mTransportWhitelist;
+
+ /** Keeps track of all unlocked users registered with this service. Indexed by user id. */
+ private final SparseArray<UserBackupManagerService> mUserServices;
+
+ private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId > 0) { // for only non system users
+ mHandler.post(() -> onRemovedNonSystemUser(userId));
+ }
+ }
+ }
+ };
+
+ public BackupManagerService(Context context) {
+ this(context, new SparseArray<>());
+ }
+
+ @VisibleForTesting
+ BackupManagerService(Context context, SparseArray<UserBackupManagerService> userServices) {
+ mContext = context;
+ mGlobalDisable = isBackupDisabled();
+ HandlerThread handlerThread =
+ new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ mUserManager = UserManager.get(context);
+ mUserServices = userServices;
+ Set<ComponentName> transportWhitelist =
+ SystemConfig.getInstance().getBackupTransportWhitelist();
+ mTransportWhitelist = (transportWhitelist == null) ? emptySet() : transportWhitelist;
+ mContext.registerReceiver(
+ mUserRemovedReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+ }
+
+ // TODO: Remove this when we implement DI by injecting in the construtor.
+ @VisibleForTesting
+ Handler getBackupHandler() {
+ return mHandler;
+ }
+
+ protected boolean isBackupDisabled() {
+ return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
+ }
+
+ protected int binderGetCallingUserId() {
+ return Binder.getCallingUserHandle().getIdentifier();
+ }
+
+ protected int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ /** Stored in the system user's directory. */
+ protected File getSuppressFileForSystemUser() {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
+ BACKUP_SUPPRESS_FILENAME);
+ }
+
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getRememberActivatedFileForNonSystemUser(int userId) {
+ return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId);
+ }
+
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId);
+ }
+
+ /**
+ * Remove backup state for non system {@code userId} when the user is removed from the device.
+ * For non system users, backup state is stored in both the user's own dir and the system dir.
+ * When the user is removed, the user's own dir gets removed by the OS. This method ensures that
+ * the part of the user backup state which is in the system dir also gets removed.
+ */
+ private void onRemovedNonSystemUser(int userId) {
+ Slog.i(TAG, "Removing state for non system user " + userId);
+ File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId);
+ if (!FileUtils.deleteContentsAndDir(dir)) {
+ Slog.w(TAG, "Failed to delete state dir for removed user: " + userId);
+ }
+ }
+
+ // TODO (b/124359804) move to util method in FileUtils
+ private void createFile(File file) throws IOException {
+ if (file.exists()) {
+ return;
+ }
+
+ file.getParentFile().mkdirs();
+ if (!file.createNewFile()) {
+ Slog.w(TAG, "Failed to create file " + file.getPath());
+ }
+ }
+
+ // TODO (b/124359804) move to util method in FileUtils
+ private void deleteFile(File file) {
+ if (!file.exists()) {
+ return;
+ }
+
+ if (!file.delete()) {
+ Slog.w(TAG, "Failed to delete file " + file.getPath());
+ }
+ }
+
+ /**
+ * Deactivates the backup service for user {@code userId}. If this is the system user, it
+ * creates a suppress file which disables backup for all users. If this is a non-system user, it
+ * only deactivates backup for that user by deleting its activate file.
+ */
+ @GuardedBy("mStateLock")
+ private void deactivateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ createFile(getSuppressFileForSystemUser());
+ } else {
+ deleteFile(getActivatedFileForNonSystemUser(userId));
+ }
+ }
+
+ /**
+ * Enables the backup service for user {@code userId}. If this is the system user, it deletes
+ * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
+ * deleting the suppress file does not automatically enable backup for non-system users, they
+ * need their own activate file in order to participate in the service.
+ */
+ @GuardedBy("mStateLock")
+ private void activateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ deleteFile(getSuppressFileForSystemUser());
+ } else {
+ createFile(getActivatedFileForNonSystemUser(userId));
+ }
+ }
+
+ // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
+ // it's used in multiple places where I/O waits would cause system lock-ups.
+ private boolean isUserReadyForBackup(int userId) {
+ return mUserServices.get(UserHandle.USER_SYSTEM) != null
+ && mUserServices.get(userId) != null;
+ }
+
+ /**
+ * Backup is activated for the system user if the suppress file does not exist. Backup is
+ * activated for non-system users if the suppress file does not exist AND the user's activated
+ * file exists.
+ */
+ private boolean isBackupActivatedForUser(int userId) {
+ if (getSuppressFileForSystemUser().exists()) {
+ return false;
+ }
+
+ return userId == UserHandle.USER_SYSTEM
+ || getActivatedFileForNonSystemUser(userId).exists();
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ protected void postToHandler(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+
+ /**
+ * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
+ * Starts the backup service for this user if backup is active for this user. Offloads work onto
+ * the handler thread {@link #mHandlerThread} to keep unlock time low since backup is not
+ * essential for device functioning.
+ */
+ void onUnlockUser(int userId) {
+ postToHandler(() -> startServiceForUser(userId));
+ }
+
+ /**
+ * Starts the backup service for user {@code userId} by creating a new instance of {@link
+ * UserBackupManagerService} and registering it with this service.
+ */
+ @VisibleForTesting
+ void startServiceForUser(int userId) {
+ // We know that the user is unlocked here because it is called from setBackupServiceActive
+ // and unlockUser which have these guarantees. So we can check if the file exists.
+ if (mGlobalDisable) {
+ Slog.i(TAG, "Backup service not supported");
+ return;
+ }
+ if (!isBackupActivatedForUser(userId)) {
+ Slog.i(TAG, "Backup not activated for user " + userId);
+ return;
+ }
+ if (mUserServices.get(userId) != null) {
+ Slog.i(TAG, "userId " + userId + " already started, so not starting again");
+ return;
+ }
+ Slog.i(TAG, "Starting service for user: " + userId);
+ UserBackupManagerService userBackupManagerService =
+ UserBackupManagerService.createAndInitializeService(
+ userId, mContext, this, mTransportWhitelist);
+ startServiceForUser(userId, userBackupManagerService);
+ }
+
+ /**
+ * Starts the backup service for user {@code userId} by registering its instance of {@link
+ * UserBackupManagerService} with this service and setting enabled state.
+ */
+ void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) {
+ mUserServices.put(userId, userBackupManagerService);
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+ userBackupManagerService.initializeBackupEnableState();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Stops the backup service for user {@code userId} when the user is stopped. */
+ @VisibleForTesting
+ protected void stopServiceForUser(int userId) {
+ UserBackupManagerService userBackupManagerService = mUserServices.removeReturnOld(userId);
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.tearDownService();
+
+ KeyValueBackupJob.cancel(userId, mContext);
+ FullBackupJob.cancel(userId, mContext);
+ }
+ }
+
+ /**
+ * Returns a list of users currently unlocked that have a {@link UserBackupManagerService}
+ * registered.
+ *
+ * Warning: Do NOT modify returned object as it's used inside.
+ *
+ * TODO: Return a copy or only expose read-only information through other means.
+ */
+ @VisibleForTesting
+ SparseArray<UserBackupManagerService> getUserServices() {
+ return mUserServices;
+ }
+
+ /**
+ * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped.
+ * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
+ */
+ void onStopUser(int userId) {
+ postToHandler(
+ () -> {
+ if (!mGlobalDisable) {
+ Slog.i(TAG, "Stopping service for user: " + userId);
+ stopServiceForUser(userId);
+ }
+ });
+ }
+
+ /** Returns {@link UserBackupManagerService} for user {@code userId}. */
+ @Nullable
+ public UserBackupManagerService getUserService(int userId) {
+ return mUserServices.get(userId);
+ }
+
+ /**
+ * The system user and managed profiles can only be acted on by callers in the system or root
+ * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
+ * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
+ */
+ private void enforcePermissionsOnUser(int userId) throws SecurityException {
+ boolean isRestrictedUser =
+ userId == UserHandle.USER_SYSTEM
+ || getUserManager().getUserInfo(userId).isManagedProfile();
+
+ if (isRestrictedUser) {
+ int caller = binderGetCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
+ throw new SecurityException("No permission to configure backup activity");
+ }
+ } else {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BACKUP, "No permission to configure backup activity");
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to configure backup activity");
+ }
+ }
+
+ /**
+ * Only privileged callers should be changing the backup state. Deactivating backup in the
+ * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
+ * is unlocked at this point yet, so handle both cases.
+ */
+ public void setBackupServiceActive(int userId, boolean makeActive) {
+ enforcePermissionsOnUser(userId);
+
+ // In Q, backup is OFF by default for non-system users. In the future, we will change that
+ // to ON unless backup was explicitly deactivated with a (permissioned) call to
+ // setBackupServiceActive.
+ // Therefore, remember this for use in the future. Basically the default in the future will
+ // be: rememberFile.exists() ? rememberFile.value() : ON
+ // Note that this has to be done right after the permission checks and before any other
+ // action since we need to remember that a permissioned call was made irrespective of
+ // whether the call changes the state or not.
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ File rememberFile = getRememberActivatedFileForNonSystemUser(userId);
+ createFile(rememberFile);
+ RandomAccessFileUtils.writeBoolean(rememberFile, makeActive);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity", e);
+ }
+ }
+
+ if (mGlobalDisable) {
+ Slog.i(TAG, "Backup service not supported");
+ return;
+ }
+
+ synchronized (mStateLock) {
+ Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
+ if (makeActive) {
+ try {
+ activateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity");
+ }
+
+ // If the user is unlocked, we can start the backup service for it. Otherwise we
+ // will start the service when the user is unlocked as part of its unlock callback.
+ if (getUserManager().isUserUnlocked(userId)) {
+ // Clear calling identity as initialization enforces the system identity but we
+ // can be coming from shell.
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ startServiceForUser(userId);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+ } else {
+ try {
+ //TODO(b/121198006): what if this throws an exception?
+ deactivateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service inactivity");
+ }
+ //TODO(b/121198006): loop through active users that have work profile and
+ // stop them as well.
+ onStopUser(userId);
+ }
+ }
+ }
+
+ // IBackupManager binder API
+
+ /**
+ * Querying activity state of backup service.
+ *
+ * @param userId The user in which the activity state of backup service is queried.
+ * @return true if the service is active.
+ */
+ @Override
+ public boolean isBackupServiceActive(int userId) {
+ synchronized (mStateLock) {
+ return !mGlobalDisable && isBackupActivatedForUser(userId);
+ }
+ }
+
+ @Override
+ public void dataChangedForUser(int userId, String packageName) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ dataChanged(userId, packageName);
+ }
+ }
+
+ @Override
+ public void dataChanged(String packageName) throws RemoteException {
+ dataChangedForUser(binderGetCallingUserId(), packageName);
+ }
+
+ /**
+ * An app's backup agent calls this method to let the service know that there's new data to
+ * backup for their app {@code packageName}. Only used for apps participating in key-value
+ * backup.
+ */
+ public void dataChanged(@UserIdInt int userId, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "dataChanged()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dataChanged(packageName);
+ }
+ }
+
+ // ---------------------------------------------
+ // TRANSPORT OPERATIONS
+ // ---------------------------------------------
+
+ @Override
+ public void initializeTransportsForUser(
+ int userId, String[] transportNames, IBackupObserver observer) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ initializeTransports(userId, transportNames, observer);
+ }
+ }
+
+ /** Run an initialize operation for the given transports {@code transportNames}. */
+ public void initializeTransports(
+ @UserIdInt int userId, String[] transportNames, IBackupObserver observer) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "initializeTransports()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.initializeTransports(transportNames, observer);
+ }
+ }
+
+ @Override
+ public void clearBackupDataForUser(int userId, String transportName, String packageName)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ clearBackupData(userId, transportName, packageName);
+ }
+ }
+
+ /**
+ * Clear the given package {@code packageName}'s backup data from the transport {@code
+ * transportName}.
+ */
+ public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "clearBackupData()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.clearBackupData(transportName, packageName);
+ }
+ }
+
+ @Override
+ public void clearBackupData(String transportName, String packageName)
+ throws RemoteException {
+ clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName);
+ }
+
+ @Override
+ public void agentConnectedForUser(int userId, String packageName, IBinder agent)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ agentConnected(userId, packageName, agent);
+ }
+ }
+
+ @Override
+ public void agentConnected(String packageName, IBinder agent) throws RemoteException {
+ agentConnectedForUser(binderGetCallingUserId(), packageName, agent);
+ }
+
+ /**
+ * Callback: a requested backup agent has been instantiated. This should only be called from the
+ * {@link ActivityManager}.
+ */
+ public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "agentConnected()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.agentConnected(packageName, agentBinder);
+ }
+ }
+
+ @Override
+ public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ agentDisconnected(userId, packageName);
+ }
+ }
+
+ @Override
+ public void agentDisconnected(String packageName) throws RemoteException {
+ agentDisconnectedForUser(binderGetCallingUserId(), packageName);
+ }
+
+ /**
+ * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be
+ * called from the {@link ActivityManager}.
+ */
+ public void agentDisconnected(@UserIdInt int userId, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.agentDisconnected(packageName);
+ }
+ }
+
+ @Override
+ public void restoreAtInstallForUser(int userId, String packageName, int token)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ restoreAtInstall(userId, packageName, token);
+ }
+ }
+
+ @Override
+ public void restoreAtInstall(String packageName, int token) throws RemoteException {
+ restoreAtInstallForUser(binderGetCallingUserId(), packageName, token);
+ }
+
+ /**
+ * Used to run a restore pass for an application that is being installed. This should only be
+ * called from the {@link PackageManager}.
+ */
+ public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.restoreAtInstall(packageName, token);
+ }
+ }
+
+ @Override
+ public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ setBackupEnabled(userId, isEnabled);
+ }
+ }
+
+ @Override
+ public void setBackupEnabled(boolean isEnabled) throws RemoteException {
+ setBackupEnabledForUser(binderGetCallingUserId(), isEnabled);
+ }
+
+ /** Enable/disable the backup service. This is user-configurable via backup settings. */
+ public void setBackupEnabled(@UserIdInt int userId, boolean enable) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.setBackupEnabled(enable);
+ }
+ }
+
+ @Override
+ public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ setAutoRestore(userId, doAutoRestore);
+ }
+ }
+
+ @Override
+ public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
+ setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore);
+ }
+
+ /** Enable/disable automatic restore of app data at install time. */
+ public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.setAutoRestore(autoRestore);
+ }
+ }
+
+ @Override
+ public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
+ return isUserReadyForBackup(userId) && isBackupEnabled(userId);
+ }
+
+ @Override
+ public boolean isBackupEnabled() throws RemoteException {
+ return isBackupEnabledForUser(binderGetCallingUserId());
+ }
+
+ /**
+ * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}.
+ */
+ public boolean isBackupEnabled(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()");
+
+ return userBackupManagerService != null && userBackupManagerService.isBackupEnabled();
+ }
+
+ /** Sets the backup password used when running adb backup. */
+ @Override
+ public boolean setBackupPassword(String currentPassword, String newPassword) {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(
+ UserHandle.USER_SYSTEM, "setBackupPassword()");
+
+ return userBackupManagerService != null
+ && userBackupManagerService.setBackupPassword(currentPassword, newPassword);
+ }
+
+ /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */
+ @Override
+ public boolean hasBackupPassword() throws RemoteException {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(
+ UserHandle.USER_SYSTEM, "hasBackupPassword()");
+
+ return userBackupManagerService != null && userBackupManagerService.hasBackupPassword();
+ }
+
+ @Override
+ public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ backupNow(userId);
+ }
+ }
+
+ @Override
+ public void backupNow() throws RemoteException {
+ backupNowForUser(binderGetCallingUserId());
+ }
+
+ /**
+ * Run a backup pass immediately for any key-value backup applications that have declared that
+ * they have pending updates.
+ */
+ public void backupNow(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "backupNow()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.backupNow();
+ }
+ }
+
+ /**
+ * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the
+ * command line, writing the resulting data stream to the supplied {@code fd}. This method is
+ * synchronous and does not return to the caller until the backup has been completed. It
+ * requires on-screen confirmation by the user.
+ */
+ @Override
+ public void adbBackup(
+ @UserIdInt int userId,
+ ParcelFileDescriptor fd,
+ boolean includeApks,
+ boolean includeObbs,
+ boolean includeShared,
+ boolean doWidgets,
+ boolean doAllApps,
+ boolean includeSystem,
+ boolean doCompress,
+ boolean doKeyValue,
+ String[] packageNames) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "adbBackup()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.adbBackup(
+ fd,
+ includeApks,
+ includeObbs,
+ includeShared,
+ doWidgets,
+ doAllApps,
+ includeSystem,
+ doCompress,
+ doKeyValue,
+ packageNames);
+ }
+ }
+
+ @Override
+ public void fullTransportBackupForUser(int userId, String[] packageNames)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ fullTransportBackup(userId, packageNames);
+ }
+ }
+
+ /**
+ * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'.
+ */
+ public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.fullTransportBackup(packageNames);
+ }
+ }
+
+ /**
+ * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method
+ * is synchronous and does not return to the caller until the restore has been completed. It
+ * requires on-screen confirmation by the user.
+ */
+ @Override
+ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "adbRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.adbRestore(fd);
+ }
+ }
+
+ @Override
+ public void acknowledgeFullBackupOrRestoreForUser(
+ int userId,
+ int token,
+ boolean allow,
+ String curPassword,
+ String encryptionPassword,
+ IFullBackupRestoreObserver observer)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ acknowledgeAdbBackupOrRestore(userId, token, allow,
+ curPassword, encryptionPassword, observer);
+ }
+ }
+
+ /**
+ * Confirm that the previously requested adb backup/restore operation can proceed. This is used
+ * to require a user-facing disclosure about the operation.
+ */
+ public void acknowledgeAdbBackupOrRestore(
+ @UserIdInt int userId,
+ int token,
+ boolean allow,
+ String currentPassword,
+ String encryptionPassword,
+ IFullBackupRestoreObserver observer) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.acknowledgeAdbBackupOrRestore(
+ token, allow, currentPassword, encryptionPassword, observer);
+ }
+ }
+
+ @Override
+ public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
+ String encryptionPassword, IFullBackupRestoreObserver observer)
+ throws RemoteException {
+ acknowledgeFullBackupOrRestoreForUser(
+ binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer);
+ }
+
+
+ @Override
+ public String getCurrentTransportForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId)) ? getCurrentTransport(userId) : null;
+ }
+
+ @Override
+ public String getCurrentTransport() throws RemoteException {
+ return getCurrentTransportForUser(binderGetCallingUserId());
+ }
+
+ /** Return the name of the currently active transport. */
+ @Nullable
+ public String getCurrentTransport(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getCurrentTransport()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getCurrentTransport();
+ }
+
+ /**
+ * Returns the {@link ComponentName} of the host service of the selected transport or
+ * {@code null} if no transport selected or if the transport selected is not registered.
+ */
+ @Override
+ @Nullable
+ public ComponentName getCurrentTransportComponentForUser(int userId) {
+ return (isUserReadyForBackup(userId)) ? getCurrentTransportComponent(userId) : null;
+ }
+
+ /**
+ * Returns the {@link ComponentName} of the host service of the selected transport or {@code
+ * null} if no transport selected or if the transport selected is not registered.
+ */
+ @Nullable
+ public ComponentName getCurrentTransportComponent(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getCurrentTransportComponent()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getCurrentTransportComponent();
+ }
+
+ @Override
+ public String[] listAllTransportsForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId)) ? listAllTransports(userId) : null;
+ }
+
+ /** Report all known, available backup transports by name. */
+ @Nullable
+ public String[] listAllTransports(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "listAllTransports()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.listAllTransports();
+ }
+
+ @Override
+ public String[] listAllTransports() throws RemoteException {
+ return listAllTransportsForUser(binderGetCallingUserId());
+ }
+
+ @Override
+ public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId))
+ ? listAllTransportComponents(userId) : null;
+ }
+
+ /** Report all known, available backup transports by {@link ComponentName}. */
+ @Nullable
+ public ComponentName[] listAllTransportComponents(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "listAllTransportComponents()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.listAllTransportComponents();
+ }
+
+ @Override
+ public String[] getTransportWhitelist() {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return null;
+ }
+ // No permission check, intentionally.
+ String[] whitelistedTransports = new String[mTransportWhitelist.size()];
+ int i = 0;
+ for (ComponentName component : mTransportWhitelist) {
+ whitelistedTransports[i] = component.flattenToShortString();
+ i++;
+ }
+ return whitelistedTransports;
+ }
+
+ @Override
+ public void updateTransportAttributesForUser(
+ int userId,
+ ComponentName transportComponent,
+ String name,
+ @Nullable Intent configurationIntent,
+ String currentDestinationString,
+ @Nullable Intent dataManagementIntent,
+ CharSequence dataManagementLabel) {
+ if (isUserReadyForBackup(userId)) {
+ updateTransportAttributes(
+ userId,
+ transportComponent,
+ name,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+ }
+
+ /**
+ * Update the attributes of the transport identified by {@code transportComponent}. If the
+ * specified transport has not been bound at least once (for registration), this call will be
+ * ignored. Only the host process of the transport can change its description, otherwise a
+ * {@link SecurityException} will be thrown.
+ *
+ * @param transportComponent The identity of the transport being described.
+ * @param name A {@link String} with the new name for the transport. This is NOT for
+ * identification. MUST NOT be {@code null}.
+ * @param configurationIntent An {@link Intent} that can be passed to {@link
+ * Context#startActivity} in order to launch the transport's configuration UI. It may be
+ * {@code null} if the transport does not offer any user-facing configuration UI.
+ * @param currentDestinationString A {@link String} describing the destination to which the
+ * transport is currently sending data. MUST NOT be {@code null}.
+ * @param dataManagementIntent An {@link Intent} that can be passed to {@link
+ * Context#startActivity} in order to launch the transport's data-management UI. It may be
+ * {@code null} if the transport does not offer any user-facing data management UI.
+ * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's
+ * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code
+ * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}.
+ * @throws SecurityException If the UID of the calling process differs from the package UID of
+ * {@code transportComponent} or if the caller does NOT have BACKUP permission.
+ */
+ public void updateTransportAttributes(
+ @UserIdInt int userId,
+ ComponentName transportComponent,
+ String name,
+ @Nullable Intent configurationIntent,
+ String currentDestinationString,
+ @Nullable Intent dataManagementIntent,
+ CharSequence dataManagementLabel) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "updateTransportAttributes()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.updateTransportAttributes(
+ transportComponent,
+ name,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+ }
+
+ @Override
+ public String selectBackupTransportForUser(int userId, String transport)
+ throws RemoteException {
+ return (isUserReadyForBackup(userId))
+ ? selectBackupTransport(userId, transport) : null;
+ }
+
+ @Override
+ public String selectBackupTransport(String transport) throws RemoteException {
+ return selectBackupTransportForUser(binderGetCallingUserId(), transport);
+ }
+
+ /**
+ * Selects transport {@code transportName} and returns the previously selected transport.
+ *
+ * @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} instead.
+ */
+ @Deprecated
+ @Nullable
+ public String selectBackupTransport(@UserIdInt int userId, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "selectBackupTransport()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.selectBackupTransport(transportName);
+ }
+
+ @Override
+ public void selectBackupTransportAsyncForUser(int userId, ComponentName transport,
+ ISelectBackupTransportCallback listener) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ selectBackupTransportAsync(userId, transport, listener);
+ } else {
+ if (listener != null) {
+ try {
+ listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Selects transport {@code transportComponent} asynchronously and notifies {@code listener}
+ * with the result upon completion.
+ */
+ public void selectBackupTransportAsync(
+ @UserIdInt int userId,
+ ComponentName transportComponent,
+ ISelectBackupTransportCallback listener) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "selectBackupTransportAsync()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.selectBackupTransportAsync(transportComponent, listener);
+ }
+ }
+
+ @Override
+ public Intent getConfigurationIntentForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId) ? getConfigurationIntent(userId, transport)
+ : null;
+ }
+
+ @Override
+ public Intent getConfigurationIntent(String transport)
+ throws RemoteException {
+ return getConfigurationIntentForUser(binderGetCallingUserId(), transport);
+ }
+
+ /**
+ * Supply the configuration intent for the given transport. If the name is not one of the
+ * available transports, or if the transport does not supply any configuration UI, the method
+ * returns {@code null}.
+ */
+ @Nullable
+ public Intent getConfigurationIntent(@UserIdInt int userId, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getConfigurationIntent(transportName);
+ }
+
+ @Override
+ public String getDestinationStringForUser(int userId, String transport) throws RemoteException {
+ return isUserReadyForBackup(userId) ? getDestinationString(userId, transport)
+ : null;
+ }
+
+ @Override
+ public String getDestinationString(String transport) throws RemoteException {
+ return getDestinationStringForUser(binderGetCallingUserId(), transport);
+ }
+
+ /**
+ * Supply the current destination string for the given transport. If the name is not one of the
+ * registered transports the method will return null.
+ *
+ * <p>This string is used VERBATIM as the summary text of the relevant Settings item.
+ *
+ * @param transportName The name of the registered transport.
+ * @return The current destination string or null if the transport is not registered.
+ */
+ @Nullable
+ public String getDestinationString(@UserIdInt int userId, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getDestinationString()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getDestinationString(transportName);
+ }
+
+ @Override
+ public Intent getDataManagementIntentForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId)
+ ? getDataManagementIntent(userId, transport) : null;
+ }
+
+ @Override
+ public Intent getDataManagementIntent(String transport)
+ throws RemoteException {
+ return getDataManagementIntentForUser(binderGetCallingUserId(), transport);
+ }
+
+ /** Supply the manage-data intent for the given transport. */
+ @Nullable
+ public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getDataManagementIntent()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getDataManagementIntent(transportName);
+ }
+
+ @Override
+ public CharSequence getDataManagementLabelForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId) ? getDataManagementLabel(userId, transport)
+ : null;
+ }
+
+ /**
+ * Supply the menu label for affordances that fire the manage-data intent for the given
+ * transport.
+ */
+ @Nullable
+ public CharSequence getDataManagementLabel(@UserIdInt int userId, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getDataManagementLabel()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.getDataManagementLabel(transportName);
+ }
+
+ @Override
+ public IRestoreSession beginRestoreSessionForUser(
+ int userId, String packageName, String transportID) throws RemoteException {
+ return isUserReadyForBackup(userId)
+ ? beginRestoreSession(userId, packageName, transportID) : null;
+ }
+
+ /**
+ * Begin a restore for the specified package {@code packageName} using the specified transport
+ * {@code transportName}.
+ */
+ @Nullable
+ public IRestoreSession beginRestoreSession(
+ @UserIdInt int userId, String packageName, String transportName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.beginRestoreSession(packageName, transportName);
+ }
+
+ @Override
+ public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ opComplete(userId, token, result);
+ }
+ }
+
+ @Override
+ public void opComplete(int token, long result) throws RemoteException {
+ opCompleteForUser(binderGetCallingUserId(), token, result);
+ }
+
+ /**
+ * Used by a currently-active backup agent to notify the service that it has completed its given
+ * outstanding asynchronous backup/restore operation.
+ */
+ public void opComplete(@UserIdInt int userId, int token, long result) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "opComplete()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.opComplete(token, result);
+ }
+ }
+
+ @Override
+ public long getAvailableRestoreTokenForUser(int userId, String packageName) {
+ return isUserReadyForBackup(userId) ? getAvailableRestoreToken(userId, packageName) : 0;
+ }
+
+ /**
+ * Get the restore-set token for the best-available restore set for this {@code packageName}:
+ * the active set if possible, else the ancestral one. Returns zero if none available.
+ */
+ public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()");
+
+ return userBackupManagerService == null
+ ? 0
+ : userBackupManagerService.getAvailableRestoreToken(packageName);
+ }
+
+ @Override
+ public boolean isAppEligibleForBackupForUser(int userId, String packageName) {
+ return isUserReadyForBackup(userId) && isAppEligibleForBackup(userId,
+ packageName);
+ }
+
+ /** Checks if the given package {@code packageName} is eligible for backup. */
+ public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "isAppEligibleForBackup()");
+
+ return userBackupManagerService != null
+ && userBackupManagerService.isAppEligibleForBackup(packageName);
+ }
+
+ @Override
+ public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) {
+ return isUserReadyForBackup(userId) ? filterAppsEligibleForBackup(userId, packages) : null;
+ }
+
+ /**
+ * Returns from the inputted packages {@code packages}, the ones that are eligible for backup.
+ */
+ @Nullable
+ public String[] filterAppsEligibleForBackup(@UserIdInt int userId, String[] packages) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "filterAppsEligibleForBackup()");
+
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.filterAppsEligibleForBackup(packages);
+ }
+
+ @Override
+ public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
+ observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
+ if (!isUserReadyForBackup(userId)) {
+ return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
+ }
+ return requestBackup(userId, packages, observer, monitor, flags);
+ }
+
+ @Override
+ public int requestBackup(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags) throws RemoteException {
+ return requestBackupForUser(binderGetCallingUserId(), packages,
+ observer, monitor, flags);
+ }
+
+ /**
+ * Requests a backup for the inputted {@code packages} with a specified callback {@link
+ * IBackupManagerMonitor} for receiving events during the operation.
+ */
+ public int requestBackup(
+ @UserIdInt int userId,
+ String[] packages,
+ IBackupObserver observer,
+ IBackupManagerMonitor monitor,
+ int flags) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "requestBackup()");
+
+ return userBackupManagerService == null
+ ? BackupManager.ERROR_BACKUP_NOT_ALLOWED
+ : userBackupManagerService.requestBackup(packages, observer, monitor, flags);
+ }
+
+ @Override
+ public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ cancelBackups(userId);
+ }
+ }
+
+ @Override
+ public void cancelBackups() throws RemoteException {
+ cancelBackupsForUser(binderGetCallingUserId());
+ }
+
+ /** Cancel all running backup operations. */
+ public void cancelBackups(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "cancelBackups()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.cancelBackups();
+ }
+ }
+
+ /**
+ * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the
+ * serial number of its ancestral work profile or null if there is no {@link
+ * UserBackupManagerService} associated with that user.
+ *
+ * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)}
+ * and it corresponds to the profile that was used to restore to the callers profile.
+ */
+ @Override
+ @Nullable
+ public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
+ if (mGlobalDisable) {
+ return null;
+ }
+ int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ long oldId = Binder.clearCallingIdentity();
+ final int[] userIds;
+ try {
+ userIds =
+ mContext
+ .getSystemService(UserManager.class)
+ .getProfileIds(callingUserId, false);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+
+ for (int userId : userIds) {
+ UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
+ if (userBackupManagerService != null) {
+ if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) {
+ return UserHandle.of(userId);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the ancestral work profile for the calling user.
+ *
+ * <p> The ancestral work profile corresponds to the profile that was used to restore to the
+ * callers profile.
+ */
+ @Override
+ public void setAncestralSerialNumber(long ancestralSerialNumber) {
+ if (mGlobalDisable) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(
+ Binder.getCallingUserHandle().getIdentifier(),
+ "setAncestralSerialNumber()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+ return;
+ }
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ pw.println("Inactive");
+ return;
+ }
+
+ if (args != null) {
+ for (String arg : args) {
+ if ("users".equals(arg.toLowerCase())) {
+ pw.print(DUMP_RUNNING_USERS_MESSAGE);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ pw.print(" " + mUserServices.keyAt(i));
+ }
+ pw.println();
+ return;
+ }
+ }
+ }
+
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dump(fd, pw, args);
+ }
+ }
+
+ /**
+ * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we
+ * use is to perform one app backup per scheduled job execution, and to reschedule the job with
+ * zero latency as long as conditions remain right and we still have work to do.
+ *
+ * @return Whether ongoing work will continue. The return value here will be passed along as the
+ * return value to the callback {@link JobService#onStartJob(JobParameters)}.
+ */
+ public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
+
+ return userBackupManagerService != null
+ && userBackupManagerService.beginFullBackup(scheduledJob);
+ }
+
+ /**
+ * Used by the {@link JobScheduler} to end the current full backup task when conditions are no
+ * longer met for running the full backup job.
+ */
+ public void endFullBackup(@UserIdInt int userId) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.endFullBackup();
+ }
+ }
+
+ /**
+ * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
+ * If the user is not registered with the service (either the user is locked or not eligible for
+ * the backup service) then return {@code null}.
+ *
+ * @param userId The id of the user to retrieve its instance of {@link
+ * UserBackupManagerService}.
+ * @param caller A {@link String} identifying the caller for logging purposes.
+ * @throws SecurityException if {@code userId} is different from the calling user id and the
+ * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission.
+ */
+ @Nullable
+ @VisibleForTesting
+ UserBackupManagerService getServiceForUserIfCallerHasPermission(
+ @UserIdInt int userId, String caller) {
+ enforceCallingPermissionOnUserId(userId, caller);
+ UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
+ if (userBackupManagerService == null) {
+ Slog.w(TAG, "Called " + caller + " for unknown user: " + userId);
+ }
+ return userBackupManagerService;
+ }
+
+ /**
+ * If {@code userId} is different from the calling user id, then the caller must hold the
+ * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+ *
+ * @param userId User id on which the backup operation is being requested.
+ * @param message A message to include in the exception if it is thrown.
+ */
+ void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
+ if (Binder.getCallingUserHandle().getIdentifier() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ }
}
/** Implementation to receive lifecycle event callbacks for system services. */
public static class Lifecycle extends SystemService {
public Lifecycle(Context context) {
- this(context, new Trampoline(context));
+ this(context, new BackupManagerService(context));
}
@VisibleForTesting
- Lifecycle(Context context, Trampoline trampoline) {
+ Lifecycle(Context context, BackupManagerService backupManagerService) {
super(context);
- Trampoline.sInstance = trampoline;
+ sInstance = backupManagerService;
}
@Override
public void onStart() {
- publishService(Context.BACKUP_SERVICE, Trampoline.sInstance);
+ publishService(Context.BACKUP_SERVICE, BackupManagerService.sInstance);
}
@Override
public void onUnlockUser(int userId) {
- Trampoline.sInstance.onUnlockUser(userId);
+ sInstance.onUnlockUser(userId);
}
@Override
public void onStopUser(int userId) {
- Trampoline.sInstance.onStopUser(userId);
+ sInstance.onStopUser(userId);
}
@VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 19a8543..0bb25e3 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -91,7 +91,7 @@
mParamsForUser.put(userId, params);
}
- Trampoline service = Trampoline.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
return service.beginFullBackup(userId, this);
}
@@ -105,7 +105,7 @@
}
}
- Trampoline service = Trampoline.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
service.endFullBackup(userId);
return false;
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 7b5dbd7..058dcae 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -144,7 +144,7 @@
}
// Time to run a key/value backup!
- Trampoline service = Trampoline.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
try {
service.backupNowForUser(userId);
} catch (RemoteException e) {}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
deleted file mode 100644
index 4cd1e17..0000000
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ /dev/null
@@ -1,1549 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.backup.BackupManagerService.TAG;
-
-import static java.util.Collections.emptySet;
-
-import android.Manifest;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.app.backup.BackupManager;
-import android.app.backup.IBackupManager;
-import android.app.backup.IBackupManagerMonitor;
-import android.app.backup.IBackupObserver;
-import android.app.backup.IFullBackupRestoreObserver;
-import android.app.backup.IRestoreSession;
-import android.app.backup.ISelectBackupTransportCallback;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.FileUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
-import com.android.server.SystemConfig;
-import com.android.server.backup.utils.RandomAccessFileUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Set;
-
-/**
- * A proxy to the {@link BackupManagerService} implementation.
- *
- * <p>This is an external interface to the {@link BackupManagerService} which is being accessed via
- * published binder {@link BackupManagerService.Lifecycle}. This lets us turn down the heavy
- * implementation object on the fly without disturbing binders that have been cached somewhere in
- * the system.
- *
- * <p>Trampoline determines whether the backup service is available. It can be disabled in the
- * following two ways:
- *
- * <ul>
- * <li>Temporary - create the file {@link #BACKUP_SUPPRESS_FILENAME}, or
- * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
- * </ul>
- *
- * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through
- * privileged callers (currently {@link DevicePolicyManager}). This is called on {@link
- * UserHandle#USER_SYSTEM} and disables backup for all users.
- */
-public class Trampoline extends IBackupManager.Stub {
- @VisibleForTesting
- static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:";
-
- /**
- * Name of file that disables the backup service. If this file exists, then backup is disabled
- * for all users.
- */
- private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
-
- /**
- * Name of file for non-system users that enables the backup service for the user. Backup is
- * disabled by default in non-system users.
- */
- private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
-
- /**
- * Name of file for non-system users that remembers whether backup was explicitly activated or
- * deactivated with a call to setBackupServiceActive.
- */
- private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated";
-
- // Product-level suppression of backup/restore.
- private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
-
- private static final String BACKUP_THREAD = "backup";
-
- static Trampoline sInstance;
-
- static Trampoline getInstance() {
- return checkNotNull(sInstance);
- }
-
- private final Context mContext;
- private final UserManager mUserManager;
-
- private final boolean mGlobalDisable;
- // Lock to write backup suppress files.
- // TODD(b/121198006): remove this object and synchronized all methods on "this".
- private final Object mStateLock = new Object();
-
- // TODO: This is not marked as final because of test code. Since we'll merge BMS and Trampoline,
- // it doesn't make sense to refactor for final. It's never null.
- @VisibleForTesting
- protected volatile BackupManagerService mService;
- private final Handler mHandler;
- private final Set<ComponentName> mTransportWhitelist;
-
- /** Keeps track of all unlocked users registered with this service. Indexed by user id. */
- private final SparseArray<UserBackupManagerService> mUserServices;
-
- private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId > 0) { // for only non system users
- mHandler.post(() -> onRemovedNonSystemUser(userId));
- }
- }
- }
- };
-
- public Trampoline(Context context) {
- this(context, new SparseArray<>());
- }
-
- @VisibleForTesting
- Trampoline(Context context, SparseArray<UserBackupManagerService> userServices) {
- mContext = context;
- mGlobalDisable = isBackupDisabled();
- HandlerThread handlerThread =
- new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
- mUserManager = UserManager.get(context);
- mUserServices = userServices;
- mService = new BackupManagerService(mContext, this, mUserServices);
- Set<ComponentName> transportWhitelist =
- SystemConfig.getInstance().getBackupTransportWhitelist();
- mTransportWhitelist = (transportWhitelist == null) ? emptySet() : transportWhitelist;
- mContext.registerReceiver(
- mUserRemovedReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
- }
-
- // TODO: Remove this when we implement DI by injecting in the construtor.
- @VisibleForTesting
- Handler getBackupHandler() {
- return mHandler;
- }
-
- protected boolean isBackupDisabled() {
- return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
- }
-
- protected int binderGetCallingUserId() {
- return Binder.getCallingUserHandle().getIdentifier();
- }
-
- protected int binderGetCallingUid() {
- return Binder.getCallingUid();
- }
-
- /** Stored in the system user's directory. */
- protected File getSuppressFileForSystemUser() {
- return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
- BACKUP_SUPPRESS_FILENAME);
- }
-
- /** Stored in the system user's directory and the file is indexed by the user it refers to. */
- protected File getRememberActivatedFileForNonSystemUser(int userId) {
- return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId);
- }
-
- /** Stored in the system user's directory and the file is indexed by the user it refers to. */
- protected File getActivatedFileForNonSystemUser(int userId) {
- return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId);
- }
-
- /**
- * Remove backup state for non system {@code userId} when the user is removed from the device.
- * For non system users, backup state is stored in both the user's own dir and the system dir.
- * When the user is removed, the user's own dir gets removed by the OS. This method ensures that
- * the part of the user backup state which is in the system dir also gets removed.
- */
- private void onRemovedNonSystemUser(int userId) {
- Slog.i(TAG, "Removing state for non system user " + userId);
- File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId);
- if (!FileUtils.deleteContentsAndDir(dir)) {
- Slog.w(TAG, "Failed to delete state dir for removed user: " + userId);
- }
- }
-
- // TODO (b/124359804) move to util method in FileUtils
- private void createFile(File file) throws IOException {
- if (file.exists()) {
- return;
- }
-
- file.getParentFile().mkdirs();
- if (!file.createNewFile()) {
- Slog.w(TAG, "Failed to create file " + file.getPath());
- }
- }
-
- // TODO (b/124359804) move to util method in FileUtils
- private void deleteFile(File file) {
- if (!file.exists()) {
- return;
- }
-
- if (!file.delete()) {
- Slog.w(TAG, "Failed to delete file " + file.getPath());
- }
- }
-
- /**
- * Deactivates the backup service for user {@code userId}. If this is the system user, it
- * creates a suppress file which disables backup for all users. If this is a non-system user, it
- * only deactivates backup for that user by deleting its activate file.
- */
- @GuardedBy("mStateLock")
- private void deactivateBackupForUserLocked(int userId) throws IOException {
- if (userId == UserHandle.USER_SYSTEM) {
- createFile(getSuppressFileForSystemUser());
- } else {
- deleteFile(getActivatedFileForNonSystemUser(userId));
- }
- }
-
- /**
- * Enables the backup service for user {@code userId}. If this is the system user, it deletes
- * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
- * deleting the suppress file does not automatically enable backup for non-system users, they
- * need their own activate file in order to participate in the service.
- */
- @GuardedBy("mStateLock")
- private void activateBackupForUserLocked(int userId) throws IOException {
- if (userId == UserHandle.USER_SYSTEM) {
- deleteFile(getSuppressFileForSystemUser());
- } else {
- createFile(getActivatedFileForNonSystemUser(userId));
- }
- }
-
- // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
- // it's used in multiple places where I/O waits would cause system lock-ups.
- private boolean isUserReadyForBackup(int userId) {
- return mUserServices.get(UserHandle.USER_SYSTEM) != null
- && mUserServices.get(userId) != null;
- }
-
- /**
- * Backup is activated for the system user if the suppress file does not exist. Backup is
- * activated for non-system users if the suppress file does not exist AND the user's activated
- * file exists.
- */
- private boolean isBackupActivatedForUser(int userId) {
- if (getSuppressFileForSystemUser().exists()) {
- return false;
- }
-
- return userId == UserHandle.USER_SYSTEM
- || getActivatedFileForNonSystemUser(userId).exists();
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected UserManager getUserManager() {
- return mUserManager;
- }
-
- protected void postToHandler(Runnable runnable) {
- mHandler.post(runnable);
- }
-
- /**
- * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
- * Starts the backup service for this user if backup is active for this user. Offloads work onto
- * the handler thread {@link #mHandlerThread} to keep unlock time low since backup is not
- * essential for device functioning.
- */
- void onUnlockUser(int userId) {
- postToHandler(() -> startServiceForUser(userId));
- }
-
- /**
- * Starts the backup service for user {@code userId} by creating a new instance of {@link
- * UserBackupManagerService} and registering it with this service.
- */
- @VisibleForTesting
- void startServiceForUser(int userId) {
- // We know that the user is unlocked here because it is called from setBackupServiceActive
- // and unlockUser which have these guarantees. So we can check if the file exists.
- if (mGlobalDisable) {
- Slog.i(TAG, "Backup service not supported");
- return;
- }
- if (!isBackupActivatedForUser(userId)) {
- Slog.i(TAG, "Backup not activated for user " + userId);
- return;
- }
- if (mUserServices.get(userId) != null) {
- Slog.i(TAG, "userId " + userId + " already started, so not starting again");
- return;
- }
- Slog.i(TAG, "Starting service for user: " + userId);
- UserBackupManagerService userBackupManagerService =
- UserBackupManagerService.createAndInitializeService(
- userId, mContext, this, mTransportWhitelist);
- startServiceForUser(userId, userBackupManagerService);
- }
-
- /**
- * Starts the backup service for user {@code userId} by registering its instance of {@link
- * UserBackupManagerService} with this service and setting enabled state.
- */
- void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) {
- mUserServices.put(userId, userBackupManagerService);
-
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
- userBackupManagerService.initializeBackupEnableState();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
-
- /** Stops the backup service for user {@code userId} when the user is stopped. */
- @VisibleForTesting
- protected void stopServiceForUser(int userId) {
- UserBackupManagerService userBackupManagerService = mUserServices.removeReturnOld(userId);
-
- if (userBackupManagerService != null) {
- userBackupManagerService.tearDownService();
-
- KeyValueBackupJob.cancel(userId, mContext);
- FullBackupJob.cancel(userId, mContext);
- }
- }
-
- /**
- * Returns a list of users currently unlocked that have a {@link UserBackupManagerService}
- * registered.
- *
- * Warning: Do NOT modify returned object as it's used inside.
- *
- * TODO: Return a copy or only expose read-only information through other means.
- */
- @VisibleForTesting
- SparseArray<UserBackupManagerService> getUserServices() {
- return mUserServices;
- }
-
- /**
- * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped.
- * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
- */
- void onStopUser(int userId) {
- postToHandler(
- () -> {
- if (!mGlobalDisable) {
- Slog.i(TAG, "Stopping service for user: " + userId);
- stopServiceForUser(userId);
- }
- });
- }
-
- /** Returns {@link UserBackupManagerService} for user {@code userId}. */
- @Nullable
- public UserBackupManagerService getUserService(int userId) {
- return mUserServices.get(userId);
- }
-
- /**
- * The system user and managed profiles can only be acted on by callers in the system or root
- * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
- * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
- */
- private void enforcePermissionsOnUser(int userId) throws SecurityException {
- boolean isRestrictedUser =
- userId == UserHandle.USER_SYSTEM
- || getUserManager().getUserInfo(userId).isManagedProfile();
-
- if (isRestrictedUser) {
- int caller = binderGetCallingUid();
- if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
- throw new SecurityException("No permission to configure backup activity");
- }
- } else {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.BACKUP, "No permission to configure backup activity");
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "No permission to configure backup activity");
- }
- }
-
- /**
- * Only privileged callers should be changing the backup state. Deactivating backup in the
- * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
- * is unlocked at this point yet, so handle both cases.
- */
- public void setBackupServiceActive(int userId, boolean makeActive) {
- enforcePermissionsOnUser(userId);
-
- // In Q, backup is OFF by default for non-system users. In the future, we will change that
- // to ON unless backup was explicitly deactivated with a (permissioned) call to
- // setBackupServiceActive.
- // Therefore, remember this for use in the future. Basically the default in the future will
- // be: rememberFile.exists() ? rememberFile.value() : ON
- // Note that this has to be done right after the permission checks and before any other
- // action since we need to remember that a permissioned call was made irrespective of
- // whether the call changes the state or not.
- if (userId != UserHandle.USER_SYSTEM) {
- try {
- File rememberFile = getRememberActivatedFileForNonSystemUser(userId);
- createFile(rememberFile);
- RandomAccessFileUtils.writeBoolean(rememberFile, makeActive);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service activity", e);
- }
- }
-
- if (mGlobalDisable) {
- Slog.i(TAG, "Backup service not supported");
- return;
- }
-
- synchronized (mStateLock) {
- Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
- if (makeActive) {
- try {
- activateBackupForUserLocked(userId);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service activity");
- }
-
- // If the user is unlocked, we can start the backup service for it. Otherwise we
- // will start the service when the user is unlocked as part of its unlock callback.
- if (getUserManager().isUserUnlocked(userId)) {
- // Clear calling identity as initialization enforces the system identity but we
- // can be coming from shell.
- long oldId = Binder.clearCallingIdentity();
- try {
- startServiceForUser(userId);
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
- } else {
- try {
- //TODO(b/121198006): what if this throws an exception?
- deactivateBackupForUserLocked(userId);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service inactivity");
- }
- //TODO(b/121198006): loop through active users that have work profile and
- // stop them as well.
- onStopUser(userId);
- }
- }
- }
-
- // IBackupManager binder API
-
- /**
- * Querying activity state of backup service.
- *
- * @param userId The user in which the activity state of backup service is queried.
- * @return true if the service is active.
- */
- @Override
- public boolean isBackupServiceActive(int userId) {
- synchronized (mStateLock) {
- return !mGlobalDisable && isBackupActivatedForUser(userId);
- }
- }
-
- @Override
- public void dataChangedForUser(int userId, String packageName) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- dataChanged(userId, packageName);
- }
- }
-
- @Override
- public void dataChanged(String packageName) throws RemoteException {
- dataChangedForUser(binderGetCallingUserId(), packageName);
- }
-
- /**
- * An app's backup agent calls this method to let the service know that there's new data to
- * backup for their app {@code packageName}. Only used for apps participating in key-value
- * backup.
- */
- public void dataChanged(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "dataChanged()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.dataChanged(packageName);
- }
- }
-
- // ---------------------------------------------
- // TRANSPORT OPERATIONS
- // ---------------------------------------------
-
- @Override
- public void initializeTransportsForUser(
- int userId, String[] transportNames, IBackupObserver observer) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- initializeTransports(userId, transportNames, observer);
- }
- }
-
- /** Run an initialize operation for the given transports {@code transportNames}. */
- public void initializeTransports(
- @UserIdInt int userId, String[] transportNames, IBackupObserver observer) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "initializeTransports()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.initializeTransports(transportNames, observer);
- }
- }
-
- @Override
- public void clearBackupDataForUser(int userId, String transportName, String packageName)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- clearBackupData(userId, transportName, packageName);
- }
- }
-
- /**
- * Clear the given package {@code packageName}'s backup data from the transport {@code
- * transportName}.
- */
- public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "clearBackupData()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.clearBackupData(transportName, packageName);
- }
- }
-
- @Override
- public void clearBackupData(String transportName, String packageName)
- throws RemoteException {
- clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName);
- }
-
- @Override
- public void agentConnectedForUser(int userId, String packageName, IBinder agent)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- agentConnected(userId, packageName, agent);
- }
- }
-
- @Override
- public void agentConnected(String packageName, IBinder agent) throws RemoteException {
- agentConnectedForUser(binderGetCallingUserId(), packageName, agent);
- }
-
- /**
- * Callback: a requested backup agent has been instantiated. This should only be called from the
- * {@link ActivityManager}.
- */
- public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "agentConnected()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.agentConnected(packageName, agentBinder);
- }
- }
-
- @Override
- public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- agentDisconnected(userId, packageName);
- }
- }
-
- @Override
- public void agentDisconnected(String packageName) throws RemoteException {
- agentDisconnectedForUser(binderGetCallingUserId(), packageName);
- }
-
- /**
- * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be
- * called from the {@link ActivityManager}.
- */
- public void agentDisconnected(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.agentDisconnected(packageName);
- }
- }
-
- @Override
- public void restoreAtInstallForUser(int userId, String packageName, int token)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- restoreAtInstall(userId, packageName, token);
- }
- }
-
- @Override
- public void restoreAtInstall(String packageName, int token) throws RemoteException {
- restoreAtInstallForUser(binderGetCallingUserId(), packageName, token);
- }
-
- /**
- * Used to run a restore pass for an application that is being installed. This should only be
- * called from the {@link PackageManager}.
- */
- public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.restoreAtInstall(packageName, token);
- }
- }
-
- @Override
- public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- setBackupEnabled(userId, isEnabled);
- }
- }
-
- @Override
- public void setBackupEnabled(boolean isEnabled) throws RemoteException {
- setBackupEnabledForUser(binderGetCallingUserId(), isEnabled);
- }
-
- /** Enable/disable the backup service. This is user-configurable via backup settings. */
- public void setBackupEnabled(@UserIdInt int userId, boolean enable) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.setBackupEnabled(enable);
- }
- }
-
- @Override
- public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- setAutoRestore(userId, doAutoRestore);
- }
- }
-
- @Override
- public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
- setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore);
- }
-
- /** Enable/disable automatic restore of app data at install time. */
- public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.setAutoRestore(autoRestore);
- }
- }
-
- @Override
- public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
- return isUserReadyForBackup(userId) && isBackupEnabled(userId);
- }
-
- @Override
- public boolean isBackupEnabled() throws RemoteException {
- return isBackupEnabledForUser(binderGetCallingUserId());
- }
-
- /**
- * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}.
- */
- public boolean isBackupEnabled(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()");
-
- return userBackupManagerService != null && userBackupManagerService.isBackupEnabled();
- }
-
- /** Sets the backup password used when running adb backup. */
- @Override
- public boolean setBackupPassword(String currentPassword, String newPassword) {
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- return false;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- UserHandle.USER_SYSTEM, "setBackupPassword()");
-
- return userBackupManagerService != null
- && userBackupManagerService.setBackupPassword(currentPassword, newPassword);
- }
-
- /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */
- @Override
- public boolean hasBackupPassword() throws RemoteException {
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- return false;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- UserHandle.USER_SYSTEM, "hasBackupPassword()");
-
- return userBackupManagerService != null && userBackupManagerService.hasBackupPassword();
- }
-
- @Override
- public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- backupNow(userId);
- }
- }
-
- @Override
- public void backupNow() throws RemoteException {
- backupNowForUser(binderGetCallingUserId());
- }
-
- /**
- * Run a backup pass immediately for any key-value backup applications that have declared that
- * they have pending updates.
- */
- public void backupNow(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "backupNow()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.backupNow();
- }
- }
-
- /**
- * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the
- * command line, writing the resulting data stream to the supplied {@code fd}. This method is
- * synchronous and does not return to the caller until the backup has been completed. It
- * requires on-screen confirmation by the user.
- */
- @Override
- public void adbBackup(
- @UserIdInt int userId,
- ParcelFileDescriptor fd,
- boolean includeApks,
- boolean includeObbs,
- boolean includeShared,
- boolean doWidgets,
- boolean doAllApps,
- boolean includeSystem,
- boolean doCompress,
- boolean doKeyValue,
- String[] packageNames) {
- if (!isUserReadyForBackup(userId)) {
- return;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "adbBackup()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.adbBackup(
- fd,
- includeApks,
- includeObbs,
- includeShared,
- doWidgets,
- doAllApps,
- includeSystem,
- doCompress,
- doKeyValue,
- packageNames);
- }
- }
-
- @Override
- public void fullTransportBackupForUser(int userId, String[] packageNames)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- fullTransportBackup(userId, packageNames);
- }
- }
-
- /**
- * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'.
- */
- public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.fullTransportBackup(packageNames);
- }
- }
-
- /**
- * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method
- * is synchronous and does not return to the caller until the restore has been completed. It
- * requires on-screen confirmation by the user.
- */
- @Override
- public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
- if (!isUserReadyForBackup(userId)) {
- return;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "adbRestore()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.adbRestore(fd);
- }
- }
-
- @Override
- public void acknowledgeFullBackupOrRestoreForUser(
- int userId,
- int token,
- boolean allow,
- String curPassword,
- String encryptionPassword,
- IFullBackupRestoreObserver observer)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- acknowledgeAdbBackupOrRestore(userId, token, allow,
- curPassword, encryptionPassword, observer);
- }
- }
-
- /**
- * Confirm that the previously requested adb backup/restore operation can proceed. This is used
- * to require a user-facing disclosure about the operation.
- */
- public void acknowledgeAdbBackupOrRestore(
- @UserIdInt int userId,
- int token,
- boolean allow,
- String currentPassword,
- String encryptionPassword,
- IFullBackupRestoreObserver observer) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.acknowledgeAdbBackupOrRestore(
- token, allow, currentPassword, encryptionPassword, observer);
- }
- }
-
- @Override
- public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
- String encryptionPassword, IFullBackupRestoreObserver observer)
- throws RemoteException {
- acknowledgeFullBackupOrRestoreForUser(
- binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer);
- }
-
-
- @Override
- public String getCurrentTransportForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId)) ? getCurrentTransport(userId) : null;
- }
-
- @Override
- public String getCurrentTransport() throws RemoteException {
- return getCurrentTransportForUser(binderGetCallingUserId());
- }
-
- /** Return the name of the currently active transport. */
- @Nullable
- public String getCurrentTransport(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getCurrentTransport()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getCurrentTransport();
- }
-
- /**
- * Returns the {@link ComponentName} of the host service of the selected transport or
- * {@code null} if no transport selected or if the transport selected is not registered.
- */
- @Override
- @Nullable
- public ComponentName getCurrentTransportComponentForUser(int userId) {
- return (isUserReadyForBackup(userId)) ? getCurrentTransportComponent(userId) : null;
- }
-
- /**
- * Returns the {@link ComponentName} of the host service of the selected transport or {@code
- * null} if no transport selected or if the transport selected is not registered.
- */
- @Nullable
- public ComponentName getCurrentTransportComponent(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getCurrentTransportComponent()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getCurrentTransportComponent();
- }
-
- @Override
- public String[] listAllTransportsForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId)) ? listAllTransports(userId) : null;
- }
-
- /** Report all known, available backup transports by name. */
- @Nullable
- public String[] listAllTransports(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "listAllTransports()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.listAllTransports();
- }
-
- @Override
- public String[] listAllTransports() throws RemoteException {
- return listAllTransportsForUser(binderGetCallingUserId());
- }
-
- @Override
- public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId))
- ? listAllTransportComponents(userId) : null;
- }
-
- /** Report all known, available backup transports by {@link ComponentName}. */
- @Nullable
- public ComponentName[] listAllTransportComponents(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "listAllTransportComponents()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.listAllTransportComponents();
- }
-
- @Override
- public String[] getTransportWhitelist() {
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- return null;
- }
- // No permission check, intentionally.
- String[] whitelistedTransports = new String[mTransportWhitelist.size()];
- int i = 0;
- for (ComponentName component : mTransportWhitelist) {
- whitelistedTransports[i] = component.flattenToShortString();
- i++;
- }
- return whitelistedTransports;
- }
-
- @Override
- public void updateTransportAttributesForUser(
- int userId,
- ComponentName transportComponent,
- String name,
- @Nullable Intent configurationIntent,
- String currentDestinationString,
- @Nullable Intent dataManagementIntent,
- CharSequence dataManagementLabel) {
- if (isUserReadyForBackup(userId)) {
- updateTransportAttributes(
- userId,
- transportComponent,
- name,
- configurationIntent,
- currentDestinationString,
- dataManagementIntent,
- dataManagementLabel);
- }
- }
-
- /**
- * Update the attributes of the transport identified by {@code transportComponent}. If the
- * specified transport has not been bound at least once (for registration), this call will be
- * ignored. Only the host process of the transport can change its description, otherwise a
- * {@link SecurityException} will be thrown.
- *
- * @param transportComponent The identity of the transport being described.
- * @param name A {@link String} with the new name for the transport. This is NOT for
- * identification. MUST NOT be {@code null}.
- * @param configurationIntent An {@link Intent} that can be passed to {@link
- * Context#startActivity} in order to launch the transport's configuration UI. It may be
- * {@code null} if the transport does not offer any user-facing configuration UI.
- * @param currentDestinationString A {@link String} describing the destination to which the
- * transport is currently sending data. MUST NOT be {@code null}.
- * @param dataManagementIntent An {@link Intent} that can be passed to {@link
- * Context#startActivity} in order to launch the transport's data-management UI. It may be
- * {@code null} if the transport does not offer any user-facing data management UI.
- * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's
- * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code
- * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}.
- * @throws SecurityException If the UID of the calling process differs from the package UID of
- * {@code transportComponent} or if the caller does NOT have BACKUP permission.
- */
- public void updateTransportAttributes(
- @UserIdInt int userId,
- ComponentName transportComponent,
- String name,
- @Nullable Intent configurationIntent,
- String currentDestinationString,
- @Nullable Intent dataManagementIntent,
- CharSequence dataManagementLabel) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "updateTransportAttributes()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.updateTransportAttributes(
- transportComponent,
- name,
- configurationIntent,
- currentDestinationString,
- dataManagementIntent,
- dataManagementLabel);
- }
- }
-
- @Override
- public String selectBackupTransportForUser(int userId, String transport)
- throws RemoteException {
- return (isUserReadyForBackup(userId))
- ? selectBackupTransport(userId, transport) : null;
- }
-
- @Override
- public String selectBackupTransport(String transport) throws RemoteException {
- return selectBackupTransportForUser(binderGetCallingUserId(), transport);
- }
-
- /**
- * Selects transport {@code transportName} and returns the previously selected transport.
- *
- * @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
- * ISelectBackupTransportCallback)} instead.
- */
- @Deprecated
- @Nullable
- public String selectBackupTransport(@UserIdInt int userId, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "selectBackupTransport()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.selectBackupTransport(transportName);
- }
-
- @Override
- public void selectBackupTransportAsyncForUser(int userId, ComponentName transport,
- ISelectBackupTransportCallback listener) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- selectBackupTransportAsync(userId, transport, listener);
- } else {
- if (listener != null) {
- try {
- listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
- } catch (RemoteException ex) {
- // ignore
- }
- }
- }
- }
-
- /**
- * Selects transport {@code transportComponent} asynchronously and notifies {@code listener}
- * with the result upon completion.
- */
- public void selectBackupTransportAsync(
- @UserIdInt int userId,
- ComponentName transportComponent,
- ISelectBackupTransportCallback listener) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "selectBackupTransportAsync()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.selectBackupTransportAsync(transportComponent, listener);
- }
- }
-
- @Override
- public Intent getConfigurationIntentForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId) ? getConfigurationIntent(userId, transport)
- : null;
- }
-
- @Override
- public Intent getConfigurationIntent(String transport)
- throws RemoteException {
- return getConfigurationIntentForUser(binderGetCallingUserId(), transport);
- }
-
- /**
- * Supply the configuration intent for the given transport. If the name is not one of the
- * available transports, or if the transport does not supply any configuration UI, the method
- * returns {@code null}.
- */
- @Nullable
- public Intent getConfigurationIntent(@UserIdInt int userId, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getConfigurationIntent(transportName);
- }
-
- @Override
- public String getDestinationStringForUser(int userId, String transport) throws RemoteException {
- return isUserReadyForBackup(userId) ? getDestinationString(userId, transport)
- : null;
- }
-
- @Override
- public String getDestinationString(String transport) throws RemoteException {
- return getDestinationStringForUser(binderGetCallingUserId(), transport);
- }
-
- /**
- * Supply the current destination string for the given transport. If the name is not one of the
- * registered transports the method will return null.
- *
- * <p>This string is used VERBATIM as the summary text of the relevant Settings item.
- *
- * @param transportName The name of the registered transport.
- * @return The current destination string or null if the transport is not registered.
- */
- @Nullable
- public String getDestinationString(@UserIdInt int userId, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getDestinationString()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getDestinationString(transportName);
- }
-
- @Override
- public Intent getDataManagementIntentForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId)
- ? getDataManagementIntent(userId, transport) : null;
- }
-
- @Override
- public Intent getDataManagementIntent(String transport)
- throws RemoteException {
- return getDataManagementIntentForUser(binderGetCallingUserId(), transport);
- }
-
- /** Supply the manage-data intent for the given transport. */
- @Nullable
- public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getDataManagementIntent()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getDataManagementIntent(transportName);
- }
-
- @Override
- public CharSequence getDataManagementLabelForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId) ? getDataManagementLabel(userId, transport)
- : null;
- }
-
- /**
- * Supply the menu label for affordances that fire the manage-data intent for the given
- * transport.
- */
- @Nullable
- public CharSequence getDataManagementLabel(@UserIdInt int userId, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getDataManagementLabel()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.getDataManagementLabel(transportName);
- }
-
- @Override
- public IRestoreSession beginRestoreSessionForUser(
- int userId, String packageName, String transportID) throws RemoteException {
- return isUserReadyForBackup(userId)
- ? beginRestoreSession(userId, packageName, transportID) : null;
- }
-
- /**
- * Begin a restore for the specified package {@code packageName} using the specified transport
- * {@code transportName}.
- */
- @Nullable
- public IRestoreSession beginRestoreSession(
- @UserIdInt int userId, String packageName, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.beginRestoreSession(packageName, transportName);
- }
-
- @Override
- public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- opComplete(userId, token, result);
- }
- }
-
- @Override
- public void opComplete(int token, long result) throws RemoteException {
- opCompleteForUser(binderGetCallingUserId(), token, result);
- }
-
- /**
- * Used by a currently-active backup agent to notify the service that it has completed its given
- * outstanding asynchronous backup/restore operation.
- */
- public void opComplete(@UserIdInt int userId, int token, long result) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "opComplete()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.opComplete(token, result);
- }
- }
-
- @Override
- public long getAvailableRestoreTokenForUser(int userId, String packageName) {
- return isUserReadyForBackup(userId) ? getAvailableRestoreToken(userId, packageName) : 0;
- }
-
- /**
- * Get the restore-set token for the best-available restore set for this {@code packageName}:
- * the active set if possible, else the ancestral one. Returns zero if none available.
- */
- public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()");
-
- return userBackupManagerService == null
- ? 0
- : userBackupManagerService.getAvailableRestoreToken(packageName);
- }
-
- @Override
- public boolean isAppEligibleForBackupForUser(int userId, String packageName) {
- return isUserReadyForBackup(userId) && isAppEligibleForBackup(userId,
- packageName);
- }
-
- /** Checks if the given package {@code packageName} is eligible for backup. */
- public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "isAppEligibleForBackup()");
-
- return userBackupManagerService != null
- && userBackupManagerService.isAppEligibleForBackup(packageName);
- }
-
- @Override
- public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) {
- return isUserReadyForBackup(userId) ? filterAppsEligibleForBackup(userId, packages) : null;
- }
-
- /**
- * Returns from the inputted packages {@code packages}, the ones that are eligible for backup.
- */
- @Nullable
- public String[] filterAppsEligibleForBackup(@UserIdInt int userId, String[] packages) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "filterAppsEligibleForBackup()");
-
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.filterAppsEligibleForBackup(packages);
- }
-
- @Override
- public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
- observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
- if (!isUserReadyForBackup(userId)) {
- return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
- }
- return requestBackup(userId, packages, observer, monitor, flags);
- }
-
- @Override
- public int requestBackup(String[] packages, IBackupObserver observer,
- IBackupManagerMonitor monitor, int flags) throws RemoteException {
- return requestBackupForUser(binderGetCallingUserId(), packages,
- observer, monitor, flags);
- }
-
- /**
- * Requests a backup for the inputted {@code packages} with a specified callback {@link
- * IBackupManagerMonitor} for receiving events during the operation.
- */
- public int requestBackup(
- @UserIdInt int userId,
- String[] packages,
- IBackupObserver observer,
- IBackupManagerMonitor monitor,
- int flags) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "requestBackup()");
-
- return userBackupManagerService == null
- ? BackupManager.ERROR_BACKUP_NOT_ALLOWED
- : userBackupManagerService.requestBackup(packages, observer, monitor, flags);
- }
-
- @Override
- public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- cancelBackups(userId);
- }
- }
-
- @Override
- public void cancelBackups() throws RemoteException {
- cancelBackupsForUser(binderGetCallingUserId());
- }
-
- /** Cancel all running backup operations. */
- public void cancelBackups(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "cancelBackups()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.cancelBackups();
- }
- }
-
- /**
- * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the
- * serial number of its ancestral work profile or null if there is no {@link
- * UserBackupManagerService} associated with that user.
- *
- * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)}
- * and it corresponds to the profile that was used to restore to the callers profile.
- */
- @Override
- @Nullable
- public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
- if (mGlobalDisable) {
- return null;
- }
- int callingUserId = Binder.getCallingUserHandle().getIdentifier();
- long oldId = Binder.clearCallingIdentity();
- final int[] userIds;
- try {
- userIds =
- mContext
- .getSystemService(UserManager.class)
- .getProfileIds(callingUserId, false);
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
-
- for (int userId : userIds) {
- UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
- if (userBackupManagerService != null) {
- if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) {
- return UserHandle.of(userId);
- }
- }
- }
-
- return null;
- }
-
- /**
- * Sets the ancestral work profile for the calling user.
- *
- * <p> The ancestral work profile corresponds to the profile that was used to restore to the
- * callers profile.
- */
- @Override
- public void setAncestralSerialNumber(long ancestralSerialNumber) {
- if (mGlobalDisable) {
- return;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- Binder.getCallingUserHandle().getIdentifier(),
- "setAncestralSerialNumber()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber);
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
- return;
- }
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- pw.println("Inactive");
- return;
- }
-
- if (args != null) {
- for (String arg : args) {
- if ("users".equals(arg.toLowerCase())) {
- pw.print(DUMP_RUNNING_USERS_MESSAGE);
- for (int i = 0; i < mUserServices.size(); i++) {
- pw.print(" " + mUserServices.keyAt(i));
- }
- pw.println();
- return;
- }
- }
- }
-
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.dump(fd, pw, args);
- }
- }
-
- /**
- * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we
- * use is to perform one app backup per scheduled job execution, and to reschedule the job with
- * zero latency as long as conditions remain right and we still have work to do.
- *
- * @return Whether ongoing work will continue. The return value here will be passed along as the
- * return value to the callback {@link JobService#onStartJob(JobParameters)}.
- */
- public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
- if (!isUserReadyForBackup(userId)) {
- return false;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
-
- return userBackupManagerService != null
- && userBackupManagerService.beginFullBackup(scheduledJob);
- }
-
- /**
- * Used by the {@link JobScheduler} to end the current full backup task when conditions are no
- * longer met for running the full backup job.
- */
- public void endFullBackup(@UserIdInt int userId) {
- if (!isUserReadyForBackup(userId)) {
- return;
- }
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.endFullBackup();
- }
- }
-
- /**
- * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
- * If the user is not registered with the service (either the user is locked or not eligible for
- * the backup service) then return {@code null}.
- *
- * @param userId The id of the user to retrieve its instance of {@link
- * UserBackupManagerService}.
- * @param caller A {@link String} identifying the caller for logging purposes.
- * @throws SecurityException if {@code userId} is different from the calling user id and the
- * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission.
- */
- @Nullable
- @VisibleForTesting
- UserBackupManagerService getServiceForUserIfCallerHasPermission(
- @UserIdInt int userId, String caller) {
- enforceCallingPermissionOnUserId(userId, caller);
- UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
- if (userBackupManagerService == null) {
- Slog.w(TAG, "Called " + caller + " for unknown user: " + userId);
- }
- return userBackupManagerService;
- }
-
- /**
- * If {@code userId} is different from the calling user id, then the caller must hold the
- * android.permission.INTERACT_ACROSS_USERS_FULL permission.
- *
- * @param userId User id on which the backup operation is being requested.
- * @param message A message to include in the exception if it is thrown.
- */
- void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
- if (Binder.getCallingUserHandle().getIdentifier() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 0e81e07..77888db 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -421,13 +421,13 @@
* Creates an instance of {@link UserBackupManagerService} and initializes state for it. This
* includes setting up the directories where we keep our bookkeeping and transport management.
*
- * @see #createAndInitializeService(int, Context, Trampoline, HandlerThread, File, File,
- * TransportManager)
+ * @see #createAndInitializeService(int, Context, BackupManagerService, HandlerThread, File,
+ * File, TransportManager)
*/
static UserBackupManagerService createAndInitializeService(
@UserIdInt int userId,
Context context,
- Trampoline trampoline,
+ BackupManagerService backupManagerService,
Set<ComponentName> transportWhitelist) {
String currentTransport =
Settings.Secure.getStringForUser(
@@ -455,7 +455,7 @@
return createAndInitializeService(
userId,
context,
- trampoline,
+ backupManagerService,
userBackupThread,
baseStateDir,
dataDir,
@@ -467,7 +467,7 @@
*
* @param userId The user which this service is for.
* @param context The system server context.
- * @param trampoline A reference to the proxy to {@link BackupManagerService}.
+ * @param backupManagerService A reference to the proxy to {@link BackupManagerService}.
* @param userBackupThread The thread running backup/restore operations for the user.
* @param baseStateDir The directory we store the user's persistent bookkeeping data.
* @param dataDir The directory we store the user's temporary staging data.
@@ -478,7 +478,7 @@
public static UserBackupManagerService createAndInitializeService(
@UserIdInt int userId,
Context context,
- Trampoline trampoline,
+ BackupManagerService backupManagerService,
HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
@@ -486,7 +486,7 @@
return new UserBackupManagerService(
userId,
context,
- trampoline,
+ backupManagerService,
userBackupThread,
baseStateDir,
dataDir,
@@ -509,7 +509,7 @@
private UserBackupManagerService(
@UserIdInt int userId,
Context context,
- Trampoline parent,
+ BackupManagerService parent,
HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
@@ -525,8 +525,8 @@
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
- checkNotNull(parent, "trampoline cannot be null");
- mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+ checkNotNull(parent, "parent cannot be null");
+ mBackupManagerBinder = BackupManagerService.asInterface(parent.asBinder());
mAgentTimeoutParameters = new
BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
diff --git a/services/core/java/com/android/server/AnimationThread.java b/services/core/java/com/android/server/AnimationThread.java
index c86042b..c607b1e 100644
--- a/services/core/java/com/android/server/AnimationThread.java
+++ b/services/core/java/com/android/server/AnimationThread.java
@@ -21,6 +21,8 @@
import android.os.Handler;
import android.os.Trace;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Thread for handling all legacy window animations, or anything that's directly impacting
* animations like starting windows or traversals.
@@ -55,4 +57,20 @@
return sHandler;
}
}
+
+ /**
+ * Disposes current animation thread if it's initialized. Should only be used in tests to set up
+ * a new environment.
+ */
+ @VisibleForTesting
+ public static void dispose() {
+ synchronized (DisplayThread.class) {
+ if (sInstance == null) {
+ return;
+ }
+
+ getHandler().runWithScissors(() -> sInstance.quit(), 0 /* timeout */);
+ sInstance = null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 6a9f5b6..d18b4f6 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1235,14 +1235,21 @@
}
@Override
public void scheduleUpdate() throws RemoteException {
- traceBegin("HealthScheduleUpdate");
- try {
- IHealth service = mHealthServiceWrapper.getLastService();
- if (service == null) throw new RemoteException("no health service");
- service.update();
- } finally {
- traceEnd();
- }
+ mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> {
+ traceBegin("HealthScheduleUpdate");
+ try {
+ IHealth service = mHealthServiceWrapper.getLastService();
+ if (service == null) {
+ Slog.e(TAG, "no health service");
+ return;
+ }
+ service.update();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Cannot call update on health HAL", ex);
+ } finally {
+ traceEnd();
+ }
+ });
}
}
@@ -1319,7 +1326,7 @@
Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
private final IServiceNotification mNotification = new Notification();
- private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
+ private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
// These variables are fixed after init.
private Callback mCallback;
private IHealthSupplier mHealthSupplier;
diff --git a/services/core/java/com/android/server/DisplayThread.java b/services/core/java/com/android/server/DisplayThread.java
index 85c799c..a07ade0 100644
--- a/services/core/java/com/android/server/DisplayThread.java
+++ b/services/core/java/com/android/server/DisplayThread.java
@@ -20,6 +20,8 @@
import android.os.Process;
import android.os.Trace;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Shared singleton foreground thread for the system. This is a thread for
* operations that affect what's on the display, which needs to have a minimum
@@ -58,4 +60,20 @@
return sHandler;
}
}
+
+ /**
+ * Disposes current display thread if it's initialized. Should only be used in tests to set up a
+ * new environment.
+ */
+ @VisibleForTesting
+ public static void dispose() {
+ synchronized (DisplayThread.class) {
+ if (sInstance == null) {
+ return;
+ }
+
+ getHandler().runWithScissors(() -> sInstance.quit(), 0 /* timeout */);
+ sInstance = null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index e531412..18009e1 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -25,6 +25,7 @@
import android.os.Environment;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -195,7 +196,20 @@
}
@Override
- public boolean write(byte[] buf) throws RemoteException {
- return getGsiService().commitGsiChunkFromMemory(buf);
+ public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
+ try {
+ return getGsiService().setGsiAshmem(ashmem, size);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ @Override
+ public boolean submitFromAshmem(long size) {
+ try {
+ return getGsiService().commitGsiChunkFromAshmem(size);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
}
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index ebe23f6..b085946 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -212,129 +212,67 @@
* Starts the given user.
*/
public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
- t.traceBegin("ssm.startUser-" + userHandle);
- Slog.i(TAG, "Calling onStartUser u" + userHandle);
- final UserInfo userInfo = getUserInfo(userHandle);
- final int serviceLen = mServices.size();
- for (int i = 0; i < serviceLen; i++) {
- final SystemService service = mServices.get(i);
- final String serviceName = service.getClass().getName();
- t.traceBegin("onStartUser-" + userHandle + " " + serviceName);
- long time = SystemClock.elapsedRealtime();
- try {
- service.onStartUser(userInfo);
- } catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting start of user " + userHandle
- + " to service " + service.getClass().getName(), ex);
- }
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStartUser ");
- t.traceEnd();
- }
- t.traceEnd();
+ onUser(t, "Start", userHandle, (s, u) -> s.onStartUser(u));
}
/**
* Unlocks the given user.
*/
public void unlockUser(final @UserIdInt int userHandle) {
- final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
- t.traceBegin("ssm.unlockUser-" + userHandle);
- Slog.i(TAG, "Calling onUnlockUser u" + userHandle);
- final UserInfo userInfo = getUserInfo(userHandle);
- final int serviceLen = mServices.size();
- for (int i = 0; i < serviceLen; i++) {
- final SystemService service = mServices.get(i);
- final String serviceName = service.getClass().getName();
- t.traceBegin("onUnlockUser-" + userHandle + " " + serviceName);
- long time = SystemClock.elapsedRealtime();
- try {
- service.onUnlockUser(userInfo);
- } catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting unlock of user " + userHandle
- + " to service " + serviceName, ex);
- }
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onUnlockUser ");
- t.traceEnd();
- }
- t.traceEnd();
+ onUser("Unlock", userHandle, (s, u) -> s.onUnlockUser(u));
}
/**
* Switches to the given user.
*/
public void switchUser(final @UserIdInt int userHandle) {
- final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
- t.traceBegin("ssm.switchUser-" + userHandle);
- Slog.i(TAG, "Calling switchUser u" + userHandle);
- final UserInfo userInfo = getUserInfo(userHandle);
- final int serviceLen = mServices.size();
- for (int i = 0; i < serviceLen; i++) {
- final SystemService service = mServices.get(i);
- final String serviceName = service.getClass().getName();
- t.traceBegin("onSwitchUser-" + userHandle + " " + serviceName);
- long time = SystemClock.elapsedRealtime();
- try {
- service.onSwitchUser(userInfo);
- } catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
- + " to service " + serviceName, ex);
- }
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onSwitchUser");
- t.traceEnd();
- }
- t.traceEnd();
+ onUser("Switch", userHandle, (s, u) -> s.onSwitchUser(u));
}
/**
* Stops the given user.
*/
public void stopUser(final @UserIdInt int userHandle) {
- final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
- t.traceBegin("ssm.stopUser-" + userHandle);
- Slog.i(TAG, "Calling onStopUser u" + userHandle);
- final UserInfo userInfo = getUserInfo(userHandle);
- final int serviceLen = mServices.size();
- for (int i = 0; i < serviceLen; i++) {
- final SystemService service = mServices.get(i);
- final String serviceName = service.getClass().getName();
- t.traceBegin("onStopUser-" + userHandle + " " + serviceName);
- long time = SystemClock.elapsedRealtime();
- try {
- service.onStopUser(userInfo);
- } catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
- + " to service " + serviceName, ex);
- }
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStopUser");
- t.traceEnd();
- }
- t.traceEnd();
+ onUser("Stop", userHandle, (s, u) -> s.onStopUser(u));
}
/**
* Cleans up the given user.
*/
public void cleanupUser(final @UserIdInt int userHandle) {
- final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog();
- t.traceBegin("ssm.cleanupUser-" + userHandle);
- Slog.i(TAG, "Calling onCleanupUser u" + userHandle);
+ onUser("Cleanup", userHandle, (s, u) -> s.onCleanupUser(u));
+ }
+
+ private interface ServiceVisitor {
+ void visit(@NonNull SystemService service, @NonNull UserInfo userInfo);
+ }
+
+ private void onUser(@NonNull String onWhat, @UserIdInt int userHandle,
+ @NonNull ServiceVisitor visitor) {
+ onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle, visitor);
+ }
+
+ private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
+ @UserIdInt int userHandle, @NonNull ServiceVisitor visitor) {
+ t.traceBegin("ssm." + onWhat + "User-" + userHandle);
+ Slog.i(TAG, "Calling on" + onWhat + "User u" + userHandle);
final UserInfo userInfo = getUserInfo(userHandle);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
final String serviceName = service.getClass().getName();
- t.traceBegin("onCleanupUser-" + userHandle + " " + serviceName);
+ t.traceBegin("ssm.on" + onWhat + "User-" + userHandle + " " + serviceName);
long time = SystemClock.elapsedRealtime();
try {
- service.onCleanupUser(userInfo);
+ visitor.visit(service, userInfo);
} catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+ Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + userHandle
+ " to service " + serviceName, ex);
}
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onCleanupUser");
- t.traceEnd();
+ warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "on" + onWhat + "User ");
+ t.traceEnd(); // what on service
}
- t.traceEnd();
+ t.traceEnd(); // main entry
}
/** Sets the safe mode flag for services to query. */
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e66e596..f7e825e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1027,7 +1027,12 @@
log(str);
}
mLocalLog.log(str);
- if (validatePhoneId(phoneId)) {
+ // for service state updates, don't notify clients when subId is invalid. This prevents
+ // us from sending incorrect notifications like b/133140128
+ // In the future, we can remove this logic for every notification here and add a
+ // callback so listeners know when their PhoneStateListener's subId becomes invalid, but
+ // for now we use the simplest fix.
+ if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
mServiceState[phoneId] = state;
for (Record r : mRecords) {
@@ -1059,7 +1064,8 @@
}
}
} else {
- log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId);
+ log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
+ + " or subId=" + subId);
}
handleRemoveListLocked();
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 0748279..9936d73 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -1188,6 +1188,7 @@
private static boolean isNotification(int usageHint) {
switch (usageHint) {
case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19c818f..5c0fe4e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -282,6 +282,7 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
import android.util.PrintWriterPrinter;
@@ -378,7 +379,7 @@
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -437,6 +438,10 @@
// need not be the case.
public static final String ACTION_TRIGGER_IDLE = "com.android.server.ACTION_TRIGGER_IDLE";
+ private static final String INTENT_BUGREPORT_REQUESTED =
+ "com.android.internal.intent.action.BUGREPORT_REQUESTED";
+ private static final String SHELL_APP_PACKAGE = "com.android.shell";
+
/** Control over CPU and battery monitoring */
// write battery stats every 30 minutes.
static final long BATTERY_STATS_TIME = 30 * 60 * 1000;
@@ -555,6 +560,10 @@
OomAdjuster mOomAdjuster;
final LowMemDetector mLowMemDetector;
+ static final String EXTRA_TITLE = "android.intent.extra.TITLE";
+ static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
+ static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
+
/** All system services */
SystemServiceManager mSystemServiceManager;
@@ -1480,8 +1489,9 @@
public ActivityTaskManagerService mActivityTaskManager;
@VisibleForTesting
public ActivityTaskManagerInternal mAtmInternal;
+ UriGrantsManagerInternal mUgmInternal;
@VisibleForTesting
- public UriGrantsManagerInternal mUgmInternal;
+ public final ActivityManagerInternal mInternal;
final ActivityThread mSystemThread;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -2413,6 +2423,8 @@
mProcStartHandler = null;
mHiddenApiBlacklist = null;
mFactoryTest = FACTORY_TEST_OFF;
+ mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
+ mInternal = new LocalService();
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2566,6 +2578,7 @@
Slog.w(TAG, "Setting background thread cpuset failed");
}
+ mInternal = new LocalService();
}
public void setSystemServiceManager(SystemServiceManager mgr) {
@@ -2583,7 +2596,7 @@
mBatteryStatsService.publish();
mAppOpsService.publish(mContext);
Slog.d("AppOps", "AppOpsService published");
- LocalServices.addService(ActivityManagerInternal.class, new LocalService());
+ LocalServices.addService(ActivityManagerInternal.class, mInternal);
mActivityTaskManager.onActivityManagerInternalAdded();
mUgmInternal.onActivityManagerInternalAdded();
mPendingIntentController.onActivityManagerInternalAdded();
@@ -8197,39 +8210,7 @@
@Deprecated
@Override
public void requestBugReport(int bugreportType) {
- String extraOptions = null;
- switch (bugreportType) {
- case ActivityManager.BUGREPORT_OPTION_FULL:
- extraOptions = "bugreportfull";
- break;
- case ActivityManager.BUGREPORT_OPTION_INTERACTIVE:
- extraOptions = "bugreportplus";
- break;
- case ActivityManager.BUGREPORT_OPTION_REMOTE:
- extraOptions = "bugreportremote";
- break;
- case ActivityManager.BUGREPORT_OPTION_WEAR:
- extraOptions = "bugreportwear";
- break;
- case ActivityManager.BUGREPORT_OPTION_TELEPHONY:
- extraOptions = "bugreporttelephony";
- break;
- case ActivityManager.BUGREPORT_OPTION_WIFI:
- extraOptions = "bugreportwifi";
- break;
- default:
- throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
- + bugreportType);
- }
- // Always log caller, even if it does not have permission to dump.
- String type = extraOptions == null ? "bugreport" : extraOptions;
- Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid());
-
- enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
- if (extraOptions != null) {
- SystemProperties.set("dumpstate.options", extraOptions);
- }
- SystemProperties.set("ctl.start", "bugreport");
+ requestBugReportWithDescription(null, null, bugreportType);
}
/**
@@ -8238,37 +8219,78 @@
* No new code should be calling it.
*/
@Deprecated
- private void requestBugReportWithDescription(String shareTitle, String shareDescription,
- int bugreportType) {
+ public void requestBugReportWithDescription(@Nullable String shareTitle,
+ @Nullable String shareDescription, int bugreportType) {
+ String type = null;
+ switch (bugreportType) {
+ case ActivityManager.BUGREPORT_OPTION_FULL:
+ type = "bugreportfull";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_INTERACTIVE:
+ type = "bugreportplus";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_REMOTE:
+ type = "bugreportremote";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_WEAR:
+ type = "bugreportwear";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_TELEPHONY:
+ type = "bugreporttelephony";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_WIFI:
+ type = "bugreportwifi";
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Provided bugreport type is not correct, value: "
+ + bugreportType);
+ }
+ // Always log caller, even if it does not have permission to dump.
+ Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid());
+ enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
+
if (!TextUtils.isEmpty(shareTitle)) {
if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) {
String errorStr = "shareTitle should be less than " +
MAX_BUGREPORT_TITLE_SIZE + " characters";
throw new IllegalArgumentException(errorStr);
- } else {
- if (!TextUtils.isEmpty(shareDescription)) {
- int length;
- try {
- length = shareDescription.getBytes("UTF-8").length;
- } catch (UnsupportedEncodingException e) {
- String errorStr = "shareDescription: UnsupportedEncodingException";
- throw new IllegalArgumentException(errorStr);
- }
- if (length > SystemProperties.PROP_VALUE_MAX) {
- String errorStr = "shareTitle should be less than " +
- SystemProperties.PROP_VALUE_MAX + " bytes";
- throw new IllegalArgumentException(errorStr);
- } else {
- SystemProperties.set("dumpstate.options.description", shareDescription);
- }
- }
- SystemProperties.set("dumpstate.options.title", shareTitle);
}
+ if (!TextUtils.isEmpty(shareDescription)) {
+ int length = shareDescription.getBytes(StandardCharsets.UTF_8).length;
+ if (length > SystemProperties.PROP_VALUE_MAX) {
+ String errorStr = "shareTitle should be less than " +
+ SystemProperties.PROP_VALUE_MAX + " bytes";
+ throw new IllegalArgumentException(errorStr);
+ } else {
+ SystemProperties.set("dumpstate.options.description", shareDescription);
+ }
+ }
+ SystemProperties.set("dumpstate.options.title", shareTitle);
+ Slog.d(TAG, "Bugreport notification title " + shareTitle
+ + " description " + shareDescription);
}
+ final boolean useApi = FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.USE_BUGREPORT_API);
- Slog.d(TAG, "Bugreport notification title " + shareTitle
- + " description " + shareDescription);
- requestBugReport(bugreportType);
+ if (useApi && bugreportType == ActivityManager.BUGREPORT_OPTION_INTERACTIVE) {
+ // Create intent to trigger Bugreport API via Shell
+ Intent triggerShellBugreport = new Intent();
+ triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED);
+ triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
+ triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
+ if (shareTitle != null) {
+ triggerShellBugreport.putExtra(EXTRA_TITLE, shareTitle);
+ }
+ if (shareDescription != null) {
+ triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
+ }
+ // Send broadcast to shell to trigger bugreport using Bugreport API
+ mContext.sendBroadcast(triggerShellBugreport);
+ } else {
+ SystemProperties.set("dumpstate.options", type);
+ SystemProperties.set("ctl.start", "bugreport");
+ }
}
/**
@@ -12209,11 +12231,8 @@
proto.write(MemInfoDumpProto.MemItem.IS_PROC, mi.isProc);
proto.write(MemInfoDumpProto.MemItem.ID, mi.id);
proto.write(MemInfoDumpProto.MemItem.HAS_ACTIVITIES, mi.hasActivities);
- if (dumpPss) {
- proto.write(MemInfoDumpProto.MemItem.PSS_KB, mi.pss);
- } else {
- proto.write(MemInfoDumpProto.MemItem.RSS_KB, mi.mRss);
- }
+ proto.write(MemInfoDumpProto.MemItem.PSS_KB, mi.pss);
+ proto.write(MemInfoDumpProto.MemItem.RSS_KB, mi.mRss);
if (dumpSwapPss) {
proto.write(MemInfoDumpProto.MemItem.SWAP_PSS_KB, mi.swapPss);
}
@@ -12511,6 +12530,13 @@
if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
opts.dumpDetails = true;
}
+ final int numProcs = procs.size();
+ final boolean collectNative = !opts.isCheckinRequest && numProcs > 1 && !opts.packages;
+ if (collectNative) {
+ // If we are showing aggregations, also look for native processes to
+ // include so that our aggregations are more accurate.
+ updateCpuStatsNow();
+ }
dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact);
@@ -12549,7 +12575,7 @@
boolean hasSwapPss = false;
Debug.MemoryInfo mi = null;
- for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+ for (int i = numProcs - 1; i >= 0; i--) {
final ProcessRecord r = procs.get(i);
final IApplicationThread thread;
final int pid;
@@ -12703,10 +12729,7 @@
long nativeProcTotalPss = 0;
- if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) {
- // If we are showing aggregations, also look for native processes to
- // include so that our aggregations are more accurate.
- updateCpuStatsNow();
+ if (collectNative) {
mi = null;
synchronized (mProcessCpuTracker) {
final int N = mProcessCpuTracker.countStats();
@@ -13074,6 +13097,13 @@
if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
opts.dumpDetails = true;
}
+ final int numProcs = procs.size();
+ final boolean collectNative = numProcs > 1 && !opts.packages;
+ if (collectNative) {
+ // If we are showing aggregations, also look for native processes to
+ // include so that our aggregations are more accurate.
+ updateCpuStatsNow();
+ }
ProtoOutputStream proto = new ProtoOutputStream(fd);
@@ -13115,7 +13145,7 @@
boolean hasSwapPss = false;
Debug.MemoryInfo mi = null;
- for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+ for (int i = numProcs - 1; i >= 0; i--) {
final ProcessRecord r = procs.get(i);
final IApplicationThread thread;
final int pid;
@@ -13262,10 +13292,7 @@
long nativeProcTotalPss = 0;
- if (procs.size() > 1 && !opts.packages) {
- // If we are showing aggregations, also look for native processes to
- // include so that our aggregations are more accurate.
- updateCpuStatsNow();
+ if (collectNative) {
mi = null;
synchronized (mProcessCpuTracker) {
final int N = mProcessCpuTracker.countStats();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5465309..c0af814 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -65,7 +65,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
@@ -101,11 +100,8 @@
import dalvik.system.VMRuntime;
-import libcore.io.IoUtils;
-
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
@@ -2087,10 +2083,10 @@
}
}
}
- if (lrui <= mLruProcessActivityStart) {
+ if (lrui < mLruProcessActivityStart) {
mLruProcessActivityStart--;
}
- if (lrui <= mLruProcessServiceStart) {
+ if (lrui < mLruProcessServiceStart) {
mLruProcessServiceStart--;
}
mLruProcesses.remove(lrui);
@@ -2622,7 +2618,7 @@
if (!moved) {
// Goes to the end of the group.
mLruProcesses.remove(i);
- mLruProcesses.add(endIndex - 1, subProc);
+ mLruProcesses.add(endIndex, subProc);
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Moving " + subProc
+ " from position " + i + " to end of group @ "
@@ -2867,15 +2863,6 @@
pos--;
}
mLruProcesses.add(pos, app);
- if (pos == mLruProcessActivityStart) {
- mLruProcessActivityStart++;
- }
- if (pos == mLruProcessServiceStart) {
- // Unless {@code #hasService} is implemented, currently the starting position
- // for activity and service are the same, so the incoming position may equal to
- // the starting position of service.
- mLruProcessServiceStart++;
- }
// If this process is part of a group, need to pull up any other processes
// in that group to be with it.
int endIndex = pos - 1;
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 65472c9..bd129f7 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -405,7 +405,7 @@
@GuardedBy("MediaFocusControl.mAudioFocusLock")
private boolean frameworkHandleFocusLoss(int focusLoss, @NonNull final FocusRequester frWinner,
boolean forceDuck) {
- if (frWinner.mCallingUid != this.mCallingUid) {
+ if (frWinner.mCallingUid == this.mCallingUid) {
// the focus change is within the same app, so let the dispatching
// happen as if the framework was not involved.
return false;
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 4a9ccde..766e5c4 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -209,8 +209,7 @@
// will show briefly and be replaced by "device locked out" message.
if (listener != null) {
if (isBiometricPrompt()) {
- listener.onAuthenticationFailedInternal(getCookie(),
- getRequireConfirmation());
+ listener.onAuthenticationFailedInternal();
} else {
listener.onAuthenticationFailed(getHalDeviceId());
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index af2f24f..24e6a75 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -25,11 +25,9 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
-import android.app.IActivityTaskManager;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
-import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
@@ -69,6 +67,7 @@
import android.util.StatsLog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
@@ -88,9 +87,8 @@
private static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
- private static final int MSG_ON_TASK_STACK_CHANGED = 1;
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
- private static final int MSG_ON_AUTHENTICATION_FAILED = 3;
+ private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
private static final int MSG_ON_ERROR = 4;
private static final int MSG_ON_ACQUIRED = 5;
private static final int MSG_ON_DISMISSED = 6;
@@ -101,6 +99,7 @@
private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11;
private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12;
private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13;
+ private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 14;
private static final int[] FEATURE_ID = {
TYPE_FINGERPRINT,
@@ -112,33 +111,41 @@
* Authentication either just called and we have not transitioned to the CALLED state, or
* authentication terminated (success or error).
*/
- private static final int STATE_AUTH_IDLE = 0;
+ static final int STATE_AUTH_IDLE = 0;
/**
* Authentication was called and we are waiting for the <Biometric>Services to return their
* cookies before starting the hardware and showing the BiometricPrompt.
*/
- private static final int STATE_AUTH_CALLED = 1;
+ static final int STATE_AUTH_CALLED = 1;
/**
* Authentication started, BiometricPrompt is showing and the hardware is authenticating.
*/
- private static final int STATE_AUTH_STARTED = 2;
+ static final int STATE_AUTH_STARTED = 2;
/**
* Authentication is paused, waiting for the user to press "try again" button. Only
* passive modalities such as Face or Iris should have this state. Note that for passive
* modalities, the HAL enters the idle state after onAuthenticated(false) which differs from
* fingerprint.
*/
- private static final int STATE_AUTH_PAUSED = 3;
+ static final int STATE_AUTH_PAUSED = 3;
/**
* Authentication is successful, but we're waiting for the user to press "confirm" button.
*/
- private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+ static final int STATE_AUTH_PENDING_CONFIRM = 5;
/**
* Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential
*/
- private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6;
+ static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6;
+ /**
+ * Biometric authenticated, waiting for SysUI to finish animation
+ */
+ static final int STATE_AUTHENTICATED_PENDING_SYSUI = 7;
+ /**
+ * Biometric error, waiting for SysUI to finish animation
+ */
+ static final int STATE_ERROR_PENDING_SYSUI = 8;
- private final class AuthSession implements IBinder.DeathRecipient {
+ final class AuthSession implements IBinder.DeathRecipient {
// Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
// <Biometric>Services before we can start authenticating. Pairs that have been returned
// are moved to mModalitiesMatched.
@@ -165,10 +172,13 @@
final boolean mRequireConfirmation;
// The current state, which can be either idle, called, or started
- private int mState = STATE_AUTH_IDLE;
+ int mState = STATE_AUTH_IDLE;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
byte[] mTokenEscrow;
+ // Waiting for SystemUI to complete animation
+ int mErrorEscrow;
+ String mErrorStringEscrow;
// Timestamp when authentication started
private long mStartTimeMs;
@@ -244,42 +254,37 @@
}
}
- private final class BiometricTaskStackListener extends TaskStackListener {
- @Override
- public void onTaskStackChanged() {
- mHandler.sendEmptyMessage(MSG_ON_TASK_STACK_CHANGED);
- }
- }
-
+ private final Injector mInjector;
+ @VisibleForTesting
+ final IBiometricService.Stub mImpl;
private final AppOpsManager mAppOps;
private final boolean mHasFeatureFingerprint;
private final boolean mHasFeatureIris;
private final boolean mHasFeatureFace;
- private final SettingObserver mSettingObserver;
+ @VisibleForTesting
+ SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
- private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener();
private final Random mRandom = new Random();
- private IFingerprintService mFingerprintService;
- private IFaceService mFaceService;
- private IActivityTaskManager mActivityTaskManager;
- private IStatusBarService mStatusBarService;
+ @VisibleForTesting
+ IFingerprintService mFingerprintService;
+ @VisibleForTesting
+ IFaceService mFaceService;
+ @VisibleForTesting
+ IStatusBarService mStatusBarService;
+ @VisibleForTesting
+ KeyStore mKeyStore;
// Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
// polymorphism :/
final ArrayList<Authenticator> mAuthenticators = new ArrayList<>();
- // Cache the current service that's being used. This is the service which
- // cancelAuthentication() must be forwarded to. This is just a cache, and the actual
- // check (is caller the current client) is done in the <Biometric>Service.
- // Since Settings/System (not application) is responsible for changing preference, this
- // should be safe.
- private int mCurrentModality;
-
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
- private AuthSession mCurrentAuthSession;
- private AuthSession mPendingAuthSession;
+ @VisibleForTesting
+ AuthSession mCurrentAuthSession;
+ @VisibleForTesting
+ AuthSession mPendingAuthSession;
// TODO(b/123378871): Remove when moved.
// When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
@@ -289,15 +294,11 @@
// to this receiver.
private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @VisibleForTesting
+ final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_ON_TASK_STACK_CHANGED: {
- handleTaskStackChanged();
- break;
- }
-
case MSG_ON_AUTHENTICATION_SUCCEEDED: {
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticationSucceeded(
@@ -307,8 +308,8 @@
break;
}
- case MSG_ON_AUTHENTICATION_FAILED: {
- handleAuthenticationFailed((String) msg.obj /* failureReason */);
+ case MSG_ON_AUTHENTICATION_REJECTED: {
+ handleAuthenticationRejected((String) msg.obj /* failureReason */);
break;
}
@@ -397,6 +398,11 @@
break;
}
+ case MSG_ON_AUTHENTICATION_TIMED_OUT: {
+ handleAuthenticationTimedOut((String) msg.obj /* errorMessage */);
+ break;
+ }
+
default:
Slog.e(TAG, "Unknown message: " + msg);
break;
@@ -422,7 +428,8 @@
}
}
- private final class SettingObserver extends ContentObserver {
+ @VisibleForTesting
+ public static class SettingObserver extends ContentObserver {
private static final boolean DEFAULT_KEYGUARD_ENABLED = true;
private static final boolean DEFAULT_APP_ENABLED = true;
@@ -436,6 +443,7 @@
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
private final ContentResolver mContentResolver;
+ private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
private Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>();
private Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>();
@@ -446,13 +454,15 @@
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
- SettingObserver(Handler handler) {
+ public SettingObserver(Context context, Handler handler,
+ List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
super(handler);
- mContentResolver = getContext().getContentResolver();
+ mContentResolver = context.getContentResolver();
+ mCallbacks = callbacks;
updateContentObserver();
}
- void updateContentObserver() {
+ public void updateContentObserver() {
mContentResolver.unregisterContentObserver(this);
mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
false /* notifyForDescendents */,
@@ -495,7 +505,7 @@
}
}
- boolean getFaceEnabledOnKeyguard() {
+ public boolean getFaceEnabledOnKeyguard() {
final int user = ActivityManager.getCurrentUser();
if (!mFaceEnabledOnKeyguard.containsKey(user)) {
onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user);
@@ -503,22 +513,23 @@
return mFaceEnabledOnKeyguard.get(user);
}
- boolean getFaceEnabledForApps(int userId) {
+ public boolean getFaceEnabledForApps(int userId) {
+ Slog.e(TAG, "getFaceEnabledForApps: " + userId, new Exception());
if (!mFaceEnabledForApps.containsKey(userId)) {
onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
}
return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
}
- boolean getFaceAlwaysRequireConfirmation(int userId) {
+ public boolean getFaceAlwaysRequireConfirmation(int userId) {
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, userId);
}
return mFaceAlwaysRequireConfirmation.get(userId);
}
- void notifyEnabledOnKeyguardCallbacks(int userId) {
- List<EnabledOnKeyguardCallback> callbacks = mEnabledOnKeyguardCallbacks;
+ public void notifyEnabledOnKeyguardCallbacks(int userId) {
+ List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notify(BiometricSourceType.FACE,
mFaceEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
@@ -527,7 +538,7 @@
}
}
- private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
+ final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
private final IBiometricEnabledOnKeyguardCallback mCallback;
@@ -559,7 +570,8 @@
}
// Wrap the client's receiver so we can do things with the BiometricDialog first
- private final IBiometricServiceReceiverInternal mInternalReceiver =
+ @VisibleForTesting
+ final IBiometricServiceReceiverInternal mInternalReceiver =
new IBiometricServiceReceiverInternal.Stub() {
@Override
public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
@@ -571,10 +583,11 @@
}
@Override
- public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
+ public void onAuthenticationFailed()
throws RemoteException {
String failureReason = getContext().getString(R.string.biometric_not_recognized);
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, failureReason).sendToTarget();
+ Slog.v(TAG, "onAuthenticationFailed: " + failureReason);
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget();
}
@Override
@@ -583,7 +596,7 @@
// soft errors and we should allow the user to try authenticating again instead of
// dismissing BiometricPrompt.
if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, message).sendToTarget();
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget();
} else {
SomeArgs args = SomeArgs.obtain();
args.argi1 = cookie;
@@ -873,6 +886,44 @@
}
}
+ @VisibleForTesting
+ static class Injector {
+ IActivityManager getActivityManagerService() {
+ return ActivityManager.getService();
+ }
+
+ IStatusBarService getStatusBarService() {
+ return IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ IFingerprintService getFingerprintService() {
+ return IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+ }
+
+ IFaceService getFaceService() {
+ return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE));
+ }
+
+ SettingObserver getSettingObserver(Context context, Handler handler,
+ List<EnabledOnKeyguardCallback> callbacks) {
+ return new SettingObserver(context, handler, callbacks);
+ }
+
+ KeyStore getKeyStore() {
+ return KeyStore.getInstance();
+ }
+
+ boolean isDebugEnabled(Context context, int userId) {
+ return Utils.isDebugEnabled(context, userId);
+ }
+
+ void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
+ service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
+ }
+ }
+
/**
* Initializes the system service.
* <p>
@@ -883,11 +934,19 @@
* @param context The system server context.
*/
public BiometricService(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ BiometricService(Context context, Injector injector) {
super(context);
+ mInjector = injector;
+ mImpl = new BiometricServiceWrapper();
mAppOps = context.getSystemService(AppOpsManager.class);
mEnabledOnKeyguardCallbacks = new ArrayList<>();
- mSettingObserver = new SettingObserver(mHandler);
+ mSettingObserver = mInjector.getSettingObserver(context, mHandler,
+ mEnabledOnKeyguardCallbacks);
final PackageManager pm = context.getPackageManager();
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -895,7 +954,7 @@
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
try {
- ActivityManager.getService().registerUserSwitchObserver(
+ injector.getActivityManagerService().registerUserSwitchObserver(
new UserSwitchObserver() {
@Override
public void onUserSwitchComplete(int newUserId) {
@@ -913,17 +972,14 @@
public void onStart() {
// TODO: maybe get these on-demand
if (mHasFeatureFingerprint) {
- mFingerprintService = IFingerprintService.Stub.asInterface(
- ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+ mFingerprintService = mInjector.getFingerprintService();
}
if (mHasFeatureFace) {
- mFaceService = IFaceService.Stub.asInterface(
- ServiceManager.getService(Context.FACE_SERVICE));
+ mFaceService = mInjector.getFaceService();
}
- mActivityTaskManager = ActivityTaskManager.getService();
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mKeyStore = mInjector.getKeyStore();
+ mStatusBarService = mInjector.getStatusBarService();
// Cache the authenticators
for (int i = 0; i < FEATURE_ID.length; i++) {
@@ -934,7 +990,7 @@
}
}
- publishBinderService(Context.BIOMETRIC_SERVICE, new BiometricServiceWrapper());
+ mInjector.publishBinderService(this, mImpl);
}
/**
@@ -1068,7 +1124,7 @@
}
private void logDialogDismissed(int reason) {
- if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+ if (reason == BiometricPrompt.DISMISSED_REASON_CONFIRMED) {
// Explicit auth, authentication confirmed.
// Latency in this case is authenticated -> confirmed. <Biometric>Service
// should have the first half (first acquired -> authenticated).
@@ -1094,7 +1150,7 @@
mCurrentAuthSession.mRequireConfirmation,
StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
latency,
- Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId));
+ mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId));
} else {
final long latency = System.currentTimeMillis() - mCurrentAuthSession.mStartTimeMs;
@@ -1122,7 +1178,7 @@
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
error,
0 /* vendorCode */,
- Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId),
+ mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId),
latency);
}
}
@@ -1145,51 +1201,22 @@
return modality;
}
- private void handleTaskStackChanged() {
- try {
- final List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (mCurrentAuthSession != null
- && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) {
- mStatusBarService.hideBiometricDialog();
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- mCurrentAuthSession.mClientReceiver.onError(
- BiometricConstants.BIOMETRIC_ERROR_CANCELED,
- getContext().getString(
- com.android.internal.R.string.biometric_error_canceled)
- );
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
- }
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to get running tasks", e);
- }
- }
-
private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) {
-
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null");
+ Slog.e(TAG, "handleAuthenticationSucceeded: Auth session is null");
return;
}
+ // Store the auth token and submit it to keystore after the dialog is confirmed /
+ // animating away.
+ mCurrentAuthSession.mTokenEscrow = token;
if (!requireConfirmation) {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- KeyStore.getInstance().addAuthToken(token);
- mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
+ mCurrentAuthSession.mState = STATE_AUTHENTICATED_PENDING_SYSUI;
} else {
mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis();
- // Store the auth token and submit it to keystore after the confirmation
- // button has been pressed.
- mCurrentAuthSession.mTokenEscrow = token;
mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM;
}
@@ -1201,12 +1228,13 @@
}
}
- private void handleAuthenticationFailed(String failureReason) {
+ private void handleAuthenticationRejected(String failureReason) {
+ Slog.v(TAG, "handleAuthenticationRejected: " + failureReason);
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onAuthenticationFailed(): Auth session is null");
+ Slog.e(TAG, "handleAuthenticationRejected: Auth session is null");
return;
}
@@ -1225,16 +1253,31 @@
}
}
+ private void handleAuthenticationTimedOut(String message) {
+ Slog.v(TAG, "handleAuthenticationTimedOut: " + message);
+ try {
+ // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
+ // after user dismissed/canceled dialog).
+ if (mCurrentAuthSession == null) {
+ Slog.e(TAG, "handleAuthenticationTimedOut: Auth session is null");
+ return;
+ }
+
+ mStatusBarService.onBiometricAuthenticated(false, message);
+ mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
private void handleOnConfirmDeviceCredentialSuccess() {
if (mConfirmDeviceCredentialReceiver == null) {
- Slog.w(TAG, "onCDCASuccess null!");
+ Slog.w(TAG, "handleOnConfirmDeviceCredentialSuccess null!");
return;
}
try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
if (mCurrentAuthSession != null) {
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
@@ -1245,14 +1288,13 @@
private void handleOnConfirmDeviceCredentialError(int error, String message) {
if (mConfirmDeviceCredentialReceiver == null) {
- Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+ Slog.w(TAG, "handleOnConfirmDeviceCredentialError null! Error: "
+ + error + " " + message);
return;
}
try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mConfirmDeviceCredentialReceiver.onError(error, message);
if (mCurrentAuthSession != null) {
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
@@ -1272,7 +1314,7 @@
}
private void handleOnError(int cookie, int error, String message) {
- Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
+ Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
// it becomes the current auth session. Similarly, the current auth session may
@@ -1282,6 +1324,9 @@
try {
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
+ mCurrentAuthSession.mErrorEscrow = error;
+ mCurrentAuthSession.mErrorStringEscrow = message;
+
if (mCurrentAuthSession.isFromConfirmDeviceCredential()) {
// If we were invoked by ConfirmDeviceCredential, do not delete the current
// auth session since we still need to respond to cancel signal while
@@ -1293,39 +1338,18 @@
mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC;
mStatusBarService.hideBiometricDialog();
} else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
- mStatusBarService.onBiometricError(message);
+ mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
- mActivityTaskManager.unregisterTaskStackListener(
- mTaskStackListener);
- mCurrentAuthSession.mClientReceiver.onError(error, message);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} else {
- // Send errors after the dialog is dismissed.
- mHandler.postDelayed(() -> {
- try {
- if (mCurrentAuthSession != null) {
- mActivityTaskManager.unregisterTaskStackListener(
- mTaskStackListener);
- mCurrentAuthSession.mClientReceiver.onError(error,
- message);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }, BiometricPrompt.HIDE_DIALOG_DELAY);
+ mStatusBarService.onBiometricError(message);
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
- // the client and and clean up.
+ // the client and and clean up. The only error we should get here is
+ // ERROR_CANCELED due to another client kicking us out.
mCurrentAuthSession.mClientReceiver.onError(error, message);
- mStatusBarService.onBiometricError(message);
- mActivityTaskManager.unregisterTaskStackListener(
- mTaskStackListener);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mStatusBarService.hideBiometricDialog();
mCurrentAuthSession = null;
} else {
Slog.e(TAG, "Impossible session error state: "
@@ -1335,7 +1359,6 @@
&& mPendingAuthSession.containsCookie(cookie)) {
if (mPendingAuthSession.mState == STATE_AUTH_CALLED) {
mPendingAuthSession.mClientReceiver.onError(error, message);
- mPendingAuthSession.mState = STATE_AUTH_IDLE;
mPendingAuthSession = null;
} else {
Slog.e(TAG, "Impossible pending session error state: "
@@ -1370,42 +1393,50 @@
private void handleOnDismissed(int reason) {
if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null");
+ Slog.e(TAG, "onDismissed: " + reason + ", auth session null");
return;
}
logDialogDismissed(reason);
try {
- if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
- // Positive button is used by passive modalities as a "confirm" button,
- // do not send to client
- mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
- // Cancel authentication. Skip the token/package check since we are cancelling
- // from system server. The interface is permission protected so this is fine.
- cancelInternal(null /* token */, null /* package */, false /* fromClient */);
- }
- if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
- mCurrentAuthSession.mClientReceiver.onError(
- BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
- getContext().getString(
- com.android.internal.R.string.biometric_error_user_canceled));
- } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
- // Have the service send the token to KeyStore, and send onAuthenticated
- // to the application
- KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
- mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ switch (reason) {
+ case BiometricPrompt.DISMISSED_REASON_CONFIRMED:
+ case BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED:
+ mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ break;
+
+ case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
+ mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
+ // Cancel authentication. Skip the token/package check since we are cancelling
+ // from system server. The interface is permission protected so this is fine.
+ cancelInternal(null /* token */, null /* package */, false /* fromClient */);
+ break;
+
+ case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
+ mCurrentAuthSession.mClientReceiver.onError(
+ BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+ getContext().getString(R.string.biometric_error_user_canceled));
+ // Cancel authentication. Skip the token/package check since we are cancelling
+ // from system server. The interface is permission protected so this is fine.
+ cancelInternal(null /* token */, null /* package */, false /* fromClient */);
+ break;
+
+ case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
+ case BiometricPrompt.DISMISSED_REASON_ERROR:
+ mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow,
+ mCurrentAuthSession.mErrorStringEscrow);
+ break;
+
+ default:
+ Slog.w(TAG, "Unhandled reason: " + reason);
+ break;
}
- // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the
- // STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when
- // ConfirmDeviceCredential is confirmed or canceled.
- // TODO(b/123378871): Remove when moved
- if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
- }
+ // Dialog is gone, auth session is done.
+ mCurrentAuthSession = null;
+
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -1472,8 +1503,8 @@
if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
- mInternalReceiver, modality, requireConfirmation, userId);
- mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ mInternalReceiver, modality, requireConfirmation, userId,
+ mCurrentAuthSession.mOpPackageName);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -1517,8 +1548,6 @@
return;
}
- mCurrentModality = modality;
-
// Start preparing for authentication. Authentication starts when
// all modalities requested have invoked onReadyForAuthentication.
authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
@@ -1610,7 +1639,6 @@
com.android.internal.R.string.biometric_error_user_canceled)
);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} catch (RemoteException e) {
@@ -1637,25 +1665,31 @@
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
- mHandler.post(() -> {
- try {
- // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
- // drivers have canceled authentication.
- if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
- mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId, fromClient);
- }
- if ((mCurrentModality & TYPE_IRIS) != 0) {
- Slog.w(TAG, "Iris unsupported");
- }
- if ((mCurrentModality & TYPE_FACE) != 0) {
- mFaceService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId, fromClient);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to cancel authentication");
- }
- });
- }
+ try {
+ if (mCurrentAuthSession == null) {
+ Slog.w(TAG, "Skipping cancelInternal");
+ return;
+ } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+ Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
+ return;
+ }
+
+ // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
+ // drivers have canceled authentication.
+ if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) {
+ mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
+ callingUid, callingPid, callingUserId, fromClient);
+ }
+ if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) {
+ Slog.w(TAG, "Iris unsupported");
+ }
+ if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
+ mFaceService.cancelAuthenticationFromService(token, opPackageName,
+ callingUid, callingPid, callingUserId, fromClient);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to cancel authentication");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index f3f9754..2de18c3 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -420,7 +420,7 @@
throw new UnsupportedOperationException("Stub!");
}
- default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+ default void onAuthenticationFailedInternal()
throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
@@ -457,10 +457,10 @@
}
@Override
- public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+ public void onAuthenticationFailedInternal()
throws RemoteException {
if (getWrapperReceiver() != null) {
- getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
+ getWrapperReceiver().onAuthenticationFailed();
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index a706521..ee49f58 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -223,7 +223,8 @@
@Override
public boolean wasUserDetected() {
- return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED;
+ return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
+ && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 53890a4..a0eafb4 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -35,6 +35,8 @@
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.RadioManager;
import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.MutableInt;
import android.util.Slog;
@@ -45,6 +47,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -56,6 +59,7 @@
@NonNull public final RadioManager.ModuleProperties mProperties;
private final Object mLock = new Object();
+ @NonNull private final Handler mHandler;
@GuardedBy("mLock")
private ITunerSession mHalTunerSession;
@@ -77,22 +81,24 @@
private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
@Override
public void onTuneFailed(int result, ProgramSelector programSelector) {
- fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal(
- programSelector)));
+ lockAndFireLater(() -> {
+ android.hardware.radio.ProgramSelector csel =
+ Convert.programSelectorFromHal(programSelector);
+ fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+ });
}
@Override
public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
- RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo);
- synchronized (mLock) {
- mCurrentProgramInfo = programInfo;
- fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo));
- }
+ lockAndFireLater(() -> {
+ mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo);
+ fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo));
+ });
}
@Override
public void onProgramListUpdated(ProgramListChunk programListChunk) {
- synchronized (mLock) {
+ lockAndFireLater(() -> {
android.hardware.radio.ProgramList.Chunk chunk =
Convert.programListChunkFromHal(programListChunk);
mProgramInfoCache.filterAndApplyChunk(chunk);
@@ -100,20 +106,23 @@
for (TunerSession tunerSession : mAidlTunerSessions) {
tunerSession.onMergedProgramListUpdateFromHal(chunk);
}
- }
+ });
}
@Override
public void onAntennaStateChange(boolean connected) {
- synchronized (mLock) {
+ lockAndFireLater(() -> {
mAntennaConnected = connected;
fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
- }
+ });
}
@Override
public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
- fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
+ lockAndFireLater(() -> {
+ Map<String, String> cparam = Convert.vendorInfoFromHal(parameters);
+ fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+ });
}
};
@@ -126,6 +135,7 @@
@NonNull RadioManager.ModuleProperties properties) {
mProperties = Objects.requireNonNull(properties);
mService = Objects.requireNonNull(service);
+ mHandler = new Handler(Looper.getMainLooper());
}
public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
@@ -310,15 +320,22 @@
}
}
+ // add to mHandler queue, but ensure the runnable holds mLock when it gets executed
+ private void lockAndFireLater(Runnable r) {
+ mHandler.post(() -> {
+ synchronized (mLock) {
+ r.run();
+ }
+ });
+ }
+
interface AidlCallbackRunnable {
void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
}
// Invokes runnable with each TunerSession currently open.
void fanoutAidlCallback(AidlCallbackRunnable runnable) {
- synchronized (mLock) {
- fanoutAidlCallbackLocked(runnable);
- }
+ lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable));
}
private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 3e1817b..36e872a 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -461,7 +461,7 @@
}
synchronized (mCache) {
- final String providerPackageName = getProviderPackageName(uri);
+ final String providerPackageName = getProviderPackageName(uri, userHandle);
invalidateCacheLocked(userHandle, providerPackageName, uri);
}
} finally {
@@ -1145,9 +1145,9 @@
}
}
- private @Nullable String getProviderPackageName(Uri uri) {
- final ProviderInfo pi = mContext.getPackageManager()
- .resolveContentProvider(uri.getAuthority(), 0);
+ private @Nullable String getProviderPackageName(Uri uri, int userId) {
+ final ProviderInfo pi = mContext.getPackageManager().resolveContentProviderAsUser(
+ uri.getAuthority(), 0, userId);
return (pi != null) ? pi.packageName : null;
}
@@ -1200,7 +1200,7 @@
mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
packageName);
- final String providerPackageName = getProviderPackageName(key);
+ final String providerPackageName = getProviderPackageName(key, userId);
final Pair<String, Uri> fullKey = Pair.create(packageName, key);
synchronized (mCache) {
@@ -1222,7 +1222,7 @@
mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
packageName);
- final String providerPackageName = getProviderPackageName(key);
+ final String providerPackageName = getProviderPackageName(key, userId);
final Pair<String, Uri> fullKey = Pair.create(packageName, key);
synchronized (mCache) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aa548f2..30b2245 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -315,7 +315,7 @@
static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
- static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
+ static final int INVALID_UID = -1;
static final boolean ENABLE_BLOCKED_TOASTS = true;
@@ -621,6 +621,8 @@
mConditionProviders.readXml(
parser, mAllowedManagedServicePackages, forRestore, userId);
migratedManagedServices = true;
+ } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) {
+ mSnoozeHelper.readXml(parser);
}
if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
if (forRestore && userId != UserHandle.USER_SYSTEM) {
@@ -709,6 +711,7 @@
mPreferencesHelper.writeXml(out, forBackup, userId);
mListeners.writeXml(out, forBackup, userId);
mAssistants.writeXml(out, forBackup, userId);
+ mSnoozeHelper.writeXml(out);
mConditionProviders.writeXml(out, forBackup, userId);
if (!forBackup || userId == UserHandle.USER_SYSTEM) {
writeSecureNotificationsPolicy(out);
@@ -908,8 +911,22 @@
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
int id, int uid, int initialPid, String message, int userId) {
+ final boolean fgService;
+ synchronized (mNotificationLock) {
+ NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+ fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
+ }
cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
REASON_ERROR, null);
+ if (fgService) {
+ // Still crash for foreground services, preventing the not-crash behaviour abused
+ // by apps to give us a garbage notification and silently start a fg service.
+ Binder.withCleanCallingIdentity(
+ () -> mAm.crashApplication(uid, initialPid, pkg, -1,
+ "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
+ + message));
+ }
}
@Override
@@ -1753,6 +1770,7 @@
com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes);
mStripRemoteViewsSizeBytes = getContext().getResources().getInteger(
com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
+
}
@Override
@@ -4695,6 +4713,12 @@
// ensure opPkg is delegate if does not match pkg
int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+ if (uid == INVALID_UID) {
+ Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification "
+ + "for nonexistent pkg " + pkg + " in user " + userId);
+ return;
+ }
+
// if opPkg is not the same as pkg, make sure the notification given was posted
// by opPkg
if (!Objects.equals(pkg, opPkg)) {
@@ -4739,6 +4763,11 @@
// as "pkg"
final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+ if (notificationUid == INVALID_UID) {
+ throw new SecurityException("Caller " + opPkg + ":" + callingUid
+ + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
+ }
+
checkRestrictedCategories(notification);
// Fix the notification as best we can.
@@ -5059,8 +5088,7 @@
}
@VisibleForTesting
- int resolveNotificationUid(String callingPkg, String targetPkg,
- int callingUid, int userId) {
+ int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
if (userId == UserHandle.USER_ALL) {
userId = USER_SYSTEM;
}
@@ -5071,16 +5099,16 @@
return callingUid;
}
- int targetUid = -1;
+ int targetUid = INVALID_UID;
try {
targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
} catch (NameNotFoundException e) {
- /* ignore */
+ /* ignore, handled by caller */
}
// posted from app A on behalf of app B
- if (targetUid != -1 && (isCallerAndroid(callingPkg, callingUid)
+ if (isCallerAndroid(callingPkg, callingUid)
|| mPreferencesHelper.isDelegateAllowed(
- targetPkg, targetUid, callingPkg, callingUid))) {
+ targetPkg, targetUid, callingPkg, callingUid)) {
return targetUid;
}
@@ -5284,7 +5312,7 @@
updateLightsLocked();
if (mSnoozeCriterionId != null) {
mAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId);
- mSnoozeHelper.snooze(r);
+ mSnoozeHelper.snooze(r, mSnoozeCriterionId);
} else {
mSnoozeHelper.snooze(r, mDuration);
}
@@ -5387,6 +5415,27 @@
@Override
public void run() {
synchronized (mNotificationLock) {
+ final Long snoozeAt =
+ mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r.getUser().getIdentifier(),
+ r.sbn.getPackageName(), r.sbn.getKey());
+ final long currentTime = System.currentTimeMillis();
+ if (snoozeAt.longValue() > currentTime) {
+ (new SnoozeNotificationRunnable(r.sbn.getKey(),
+ snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);
+ return;
+ }
+
+ final String contextId =
+ mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ r.getUser().getIdentifier(),
+ r.sbn.getPackageName(), r.sbn.getKey());
+ if (contextId != null) {
+ (new SnoozeNotificationRunnable(r.sbn.getKey(),
+ 0, contextId)).snoozeLocked(r);
+ return;
+ }
+
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
@@ -6937,6 +6986,7 @@
if (DBG) {
Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName));
}
+ mSnoozeHelper.cleanupPersistedContext(key);
mSnoozeHelper.repost(key);
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 91f497c..8125d0d 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -55,6 +55,21 @@
* NotificationManagerService helper for handling snoozed notifications.
*/
public class SnoozeHelper {
+ public static final String XML_SNOOZED_NOTIFICATION_VERSION = "1";
+
+ protected static final String XML_TAG_NAME = "snoozed-notifications";
+
+ private static final String XML_SNOOZED_NOTIFICATION = "notification";
+ private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
+ private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg";
+ private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id";
+ private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
+ //the time the snoozed notification should be reposted
+ private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
+ private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
+ private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
+
+
private static final String TAG = "SnoozeHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String INDENT = " ";
@@ -72,6 +87,17 @@
// User id : package name : notification key : record.
private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
mSnoozedNotifications = new ArrayMap<>();
+ // User id : package name : notification key : time-milliseconds .
+ // This member stores persisted snoozed notification trigger times. it persists through reboots
+ // It should have the notifications that haven't expired or re-posted yet
+ private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>>
+ mPersistedSnoozedNotifications = new ArrayMap<>();
+ // User id : package name : notification key : creation ID .
+ // This member stores persisted snoozed notification trigger context for the assistant
+ // it persists through reboots.
+ // It should have the notifications that haven't expired or re-posted yet
+ private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>>
+ mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
// notification key : package.
private ArrayMap<String, String> mPackages = new ArrayMap<>();
// key : userId
@@ -89,6 +115,34 @@
mUserProfiles = userProfiles;
}
+ void cleanupPersistedContext(String key){
+ int userId = mUsers.get(key);
+ String pkg = mPackages.get(key);
+ synchronized (mPersistedSnoozedNotificationsWithContext) {
+ removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
+ }
+ }
+
+ //This function has a side effect of removing the time from the list of persisted notifications.
+ //IT IS NOT IDEMPOTENT!
+ @NonNull
+ protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
+ Long time;
+ synchronized (mPersistedSnoozedNotifications) {
+ time = removeRecord(pkg, key, userId, mPersistedSnoozedNotifications);
+ }
+ if (time == null) {
+ return 0L;
+ }
+ return time;
+ }
+
+ protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) {
+ synchronized (mPersistedSnoozedNotificationsWithContext) {
+ return removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
+ }
+ }
+
protected boolean isSnoozed(int userId, String pkg, String key) {
return mSnoozedNotifications.containsKey(userId)
&& mSnoozedNotifications.get(userId).containsKey(pkg)
@@ -169,32 +223,82 @@
* Snoozes a notification and schedules an alarm to repost at that time.
*/
protected void snooze(NotificationRecord record, long duration) {
+ String pkg = record.sbn.getPackageName();
+ String key = record.getKey();
+ int userId = record.getUser().getIdentifier();
+
snooze(record);
- scheduleRepost(record.sbn.getPackageName(), record.getKey(), record.getUserId(), duration);
+ scheduleRepost(pkg, key, userId, duration);
+ Long activateAt = System.currentTimeMillis() + duration;
+ synchronized (mPersistedSnoozedNotifications) {
+ storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
+ }
}
/**
* Records a snoozed notification.
*/
- protected void snooze(NotificationRecord record) {
+ protected void snooze(NotificationRecord record, String contextId) {
+ int userId = record.getUser().getIdentifier();
+ if (contextId != null) {
+ synchronized (mPersistedSnoozedNotificationsWithContext) {
+ storeRecord(record.sbn.getPackageName(), record.getKey(),
+ userId, mPersistedSnoozedNotificationsWithContext, contextId);
+ }
+ }
+ snooze(record);
+ }
+
+ private void snooze(NotificationRecord record) {
int userId = record.getUser().getIdentifier();
if (DEBUG) {
Slog.d(TAG, "Snoozing " + record.getKey());
}
- ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
- mSnoozedNotifications.get(userId);
+ storeRecord(record.sbn.getPackageName(), record.getKey(),
+ userId, mSnoozedNotifications, record);
+ mPackages.put(record.getKey(), record.sbn.getPackageName());
+ mUsers.put(record.getKey(), userId);
+ }
+
+ private <T> void storeRecord(String pkg, String key, Integer userId,
+ ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) {
+
+ ArrayMap<String, ArrayMap<String, T>> records =
+ targets.get(userId);
if (records == null) {
records = new ArrayMap<>();
}
- ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName());
+ ArrayMap<String, T> pkgRecords = records.get(pkg);
if (pkgRecords == null) {
pkgRecords = new ArrayMap<>();
}
- pkgRecords.put(record.getKey(), record);
- records.put(record.sbn.getPackageName(), pkgRecords);
- mSnoozedNotifications.put(userId, records);
- mPackages.put(record.getKey(), record.sbn.getPackageName());
- mUsers.put(record.getKey(), userId);
+ pkgRecords.put(key, object);
+ records.put(pkg, pkgRecords);
+ targets.put(userId, records);
+
+ }
+
+ private <T> T removeRecord(String pkg, String key, Integer userId,
+ ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) {
+ T object = null;
+
+ ArrayMap<String, ArrayMap<String, T>> records =
+ targets.get(userId);
+ if (records == null) {
+ return null;
+ }
+ ArrayMap<String, T> pkgRecords = records.get(pkg);
+ if (pkgRecords == null) {
+ return null;
+ }
+ object = pkgRecords.remove(key);
+ if (pkgRecords.size() == 0) {
+ records.remove(pkg);
+ }
+ if (records.size() == 0) {
+ targets.remove(userId);
+ }
+ return object;
}
protected boolean cancel(int userId, String pkg, String tag, int id) {
@@ -414,13 +518,121 @@
}
}
- protected void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
-
+ protected void writeXml(XmlSerializer out) throws IOException {
+ final long currentTime = System.currentTimeMillis();
+ out.startTag(null, XML_TAG_NAME);
+ writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION,
+ value -> {
+ if (value < currentTime) {
+ return;
+ }
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME,
+ value.toString());
+ });
+ writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT,
+ value -> {
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID,
+ value);
+ });
+ out.endTag(null, XML_TAG_NAME);
}
- public void readXml(XmlPullParser parser, boolean forRestore)
- throws XmlPullParserException, IOException {
+ private interface Inserter<T> {
+ void insert(T t) throws IOException;
+ }
+ private <T> void writeXml(XmlSerializer out,
+ ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag,
+ Inserter<T> attributeInserter)
+ throws IOException {
+ synchronized (targets) {
+ final int M = targets.size();
+ for (int i = 0; i < M; i++) {
+ final ArrayMap<String, ArrayMap<String, T>> packages =
+ targets.valueAt(i);
+ if (packages == null) {
+ continue;
+ }
+ final int N = packages.size();
+ for (int j = 0; j < N; j++) {
+ final ArrayMap<String, T> keyToValue = packages.valueAt(j);
+ if (keyToValue == null) {
+ continue;
+ }
+ final int O = keyToValue.size();
+ for (int k = 0; k < O; k++) {
+ T value = keyToValue.valueAt(k);
+ out.startTag(null, tag);
+
+ attributeInserter.insert(value);
+
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
+ XML_SNOOZED_NOTIFICATION_VERSION);
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k));
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j));
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
+ targets.keyAt(i).toString());
+
+ out.endTag(null, tag);
+
+ }
+ }
+ }
+ }
+ }
+
+ protected void readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ String tag = parser.getName();
+ if (type == XmlPullParser.END_TAG
+ && XML_TAG_NAME.equals(tag)) {
+ break;
+ }
+ if (type == XmlPullParser.START_TAG
+ && (XML_SNOOZED_NOTIFICATION.equals(tag)
+ || tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT))
+ && parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL)
+ .equals(XML_SNOOZED_NOTIFICATION_VERSION)) {
+ try {
+ final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
+ final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
+ final int userId = Integer.parseInt(
+ parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_USER_ID));
+ if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
+ final Long time = Long.parseLong(
+ parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_TIME));
+ if (time > System.currentTimeMillis()) { //only read new stuff
+ synchronized (mPersistedSnoozedNotifications) {
+ storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time);
+ }
+ scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
+ }
+ continue;
+ }
+ if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
+ final String creationId = parser.getAttributeValue(
+ null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID);
+ synchronized (mPersistedSnoozedNotificationsWithContext) {
+ storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
+ creationId);
+ }
+ continue;
+ }
+
+
+ } catch (Exception e) {
+ //we dont cre if it is a number format exception or a null pointer exception.
+ //we just want to debug it and continue with our lives
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Exception in reading snooze data from policy xml: "
+ + e.getMessage());
+ }
+ }
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 91824c3..20a4c75 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -129,11 +129,11 @@
}
}
- static void startIdmapService() {
+ private static void startIdmapService() {
SystemProperties.set("ctl.start", IDMAP_DAEMON);
}
- static void stopIdmapService() {
+ private static void stopIdmapService() {
SystemProperties.set("ctl.stop", IDMAP_DAEMON);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8171a8d..965ddc9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -262,7 +262,6 @@
initIfNeeded();
onSwitchUser(UserHandle.USER_SYSTEM);
- IdmapDaemon.stopIdmapService();
publishBinderService(Context.OVERLAY_SERVICE, mService);
publishLocalService(OverlayManagerService.class, this);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0032e9a..3aeb2b1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1184,9 +1184,26 @@
synchronized (mSessions) {
pw.println("Active install sessions:");
pw.increaseIndent();
+
+ List<PackageInstallerSession> finalizedSessions = new ArrayList<>();
int N = mSessions.size();
for (int i = 0; i < N; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
+ if (session.isStagedAndInTerminalState()) {
+ finalizedSessions.add(session);
+ continue;
+ }
+ session.dump(pw);
+ pw.println();
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("Finalized install sessions:");
+ pw.increaseIndent();
+ N = finalizedSessions.size();
+ for (int i = 0; i < N; i++) {
+ final PackageInstallerSession session = finalizedSessions.get(i);
session.dump(pw);
pw.println();
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4eddb930..b720290 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2337,6 +2337,7 @@
pw.printPair("mInstallerPackageName", mInstallerPackageName);
pw.printPair("mInstallerUid", mInstallerUid);
pw.printPair("createdMillis", createdMillis);
+ pw.printPair("updatedMillis", updatedMillis);
pw.printPair("stageDir", stageDir);
pw.printPair("stageCid", stageCid);
pw.println();
@@ -2356,6 +2357,13 @@
pw.printPair("mFinalMessage", mFinalMessage);
pw.printPair("params.isMultiPackage", params.isMultiPackage);
pw.printPair("params.isStaged", params.isStaged);
+ pw.printPair("mParentSessionId", mParentSessionId);
+ pw.printPair("mChildSessionIds", mChildSessionIds);
+ pw.printPair("mStagedSessionApplied", mStagedSessionApplied);
+ pw.printPair("mStagedSessionFailed", mStagedSessionFailed);
+ pw.printPair("mStagedSessionReady", mStagedSessionReady);
+ pw.printPair("mStagedSessionErrorCode", mStagedSessionErrorCode);
+ pw.printPair("mStagedSessionErrorMessage", mStagedSessionErrorMessage);
pw.println();
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index e2644ff..fda798c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -43,7 +43,6 @@
import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.permission.PermissionManagerService.killUid;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -782,6 +781,14 @@
@Override
public int checkPermission(String permName, String pkgName, int userId) {
+ // Not using Preconditions.checkNotNull() here for compatibility reasons.
+ if (permName == null || pkgName == null) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ if (!mUserManagerInt.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
final CheckPermissionDelegate checkPermissionDelegate;
synchronized (mLock) {
if (mCheckPermissionDelegate == null) {
@@ -794,43 +801,70 @@
}
private int checkPermissionImpl(String permName, String pkgName, int userId) {
- return checkPermissionInternal(permName, pkgName, getCallingUid(), userId);
- }
-
- private int checkPermissionInternal(String permName, String packageName, int callingUid,
- int userId) {
- if (!mUserManagerInt.exists(userId)) {
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+ if (pkg == null) {
return PackageManager.PERMISSION_DENIED;
}
- final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
- if (pkg != null && pkg.mExtras != null) {
+ return checkPermissionInternal(pkg, true, permName, userId);
+ }
+
+ private int checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
+ @NonNull String permissionName, @UserIdInt int userId) {
+ final int callingUid = getCallingUid();
+ if (isPackageExplicit || pkg.mSharedUserId == null) {
if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
return PackageManager.PERMISSION_DENIED;
}
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- final boolean instantApp = ps.getInstantApp(userId);
- final PermissionsState permissionsState = ps.getPermissionsState();
- if (permissionsState.hasPermission(permName, userId)) {
- if (instantApp) {
- synchronized (mLock) {
- BasePermission bp = mSettings.getPermissionLocked(permName);
- if (bp != null && bp.isInstant()) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- } else {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
- return PackageManager.PERMISSION_GRANTED;
+ } else {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return PackageManager.PERMISSION_DENIED;
}
}
+
+ final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ final PermissionsState permissionsState = ps.getPermissionsState();
+
+ if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+ if (fullerPermissionName != null
+ && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
return PackageManager.PERMISSION_DENIED;
}
+ private boolean checkSinglePermissionInternal(int uid,
+ @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
+ if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
+ return false;
+ }
+
+ if (mPackageManagerInt.getInstantAppPackageName(uid) != null) {
+ return mSettings.isPermissionInstant(permissionName);
+ }
+
+ return true;
+ }
+
@Override
public int checkUidPermission(String permName, int uid) {
+ // Not using Preconditions.checkNotNull() here for compatibility reasons.
+ if (permName == null) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ if (!mUserManagerInt.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
final CheckPermissionDelegate checkPermissionDelegate;
synchronized (mLock) {
if (mCheckPermissionDelegate == null) {
@@ -842,11 +876,9 @@
PermissionManagerService.this::checkUidPermissionImpl);
}
- private int checkUidPermissionImpl(@NonNull String permName, int uid) {
+ private int checkUidPermissionImpl(String permName, int uid) {
final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
- synchronized (mLock) {
- return checkUidPermissionInternal(permName, pkg, uid, getCallingUid());
- }
+ return checkUidPermissionInternal(pkg, uid, permName);
}
/**
@@ -856,55 +888,33 @@
*
* @see SystemConfig#getSystemPermissions()
*/
- private int checkUidPermissionInternal(@NonNull String permName,
- @Nullable PackageParser.Package pkg, int uid, int callingUid) {
- final int callingUserId = UserHandle.getUserId(callingUid);
- final boolean isCallerInstantApp =
- mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
- final boolean isUidInstantApp =
- mPackageManagerInt.getInstantAppPackageName(uid) != null;
- final int userId = UserHandle.getUserId(uid);
- if (!mUserManagerInt.exists(userId)) {
- return PackageManager.PERMISSION_DENIED;
+ private int checkUidPermissionInternal(@Nullable Package pkg, int uid,
+ @NonNull String permissionName) {
+ if (pkg != null) {
+ final int userId = UserHandle.getUserId(uid);
+ return checkPermissionInternal(pkg, false, permissionName, userId);
}
- if (pkg != null) {
- if (pkg.mSharedUserId != null) {
- if (isCallerInstantApp) {
- return PackageManager.PERMISSION_DENIED;
- }
- } else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
- return PackageManager.PERMISSION_DENIED;
- }
- final PermissionsState permissionsState =
- ((PackageSetting) pkg.mExtras).getPermissionsState();
- if (permissionsState.hasPermission(permName, userId)) {
- if (isUidInstantApp) {
- if (mSettings.isPermissionInstant(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- ArraySet<String> perms = mSystemPermissions.get(uid);
- if (perms != null) {
- if (perms.contains(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- if (FULLER_PERMISSION_MAP.containsKey(permName)
- && perms.contains(FULLER_PERMISSION_MAP.get(permName))) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
+ if (checkSingleUidPermissionInternal(uid, permissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
}
+
+ final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+ if (fullerPermissionName != null
+ && checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
return PackageManager.PERMISSION_DENIED;
}
+ private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) {
+ synchronized (mLock) {
+ ArraySet<String> permissions = mSystemPermissions.get(uid);
+ return permissions != null && permissions.contains(permissionName);
+ }
+ }
+
@Override
public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
mContext.enforceCallingOrSelfPermission(
@@ -1962,18 +1972,6 @@
}
}
- /**
- * Returns {@code true} if the permission can be implied from another granted permission.
- * <p>Some permissions, such as ACCESS_FINE_LOCATION, imply other permissions,
- * such as ACCESS_COURSE_LOCATION. If the caller holds an umbrella permission, give
- * it access to any implied permissions.
- */
- private static boolean isImpliedPermissionGranted(PermissionsState permissionsState,
- String permName, int userId) {
- return FULLER_PERMISSION_MAP.containsKey(permName)
- && permissionsState.hasPermission(FULLER_PERMISSION_MAP.get(permName), userId);
- }
-
private int adjustPermissionProtectionFlagsLocked(
int protectionLevel, String packageName, int uid) {
// Signature permission flags area always reported
@@ -2062,8 +2060,8 @@
final int numPackages = allPackageNames.size();
for (int packageNum = 0; packageNum < numPackages; packageNum++) {
final String packageName = allPackageNames.get(packageNum);
- final int permissionState = checkPermissionInternal(
- permissionName, packageName, UserHandle.USER_SYSTEM, userId);
+ final int permissionState = checkPermission(permissionName, packageName,
+ userId);
if (permissionState == PackageManager.PERMISSION_GRANTED) {
EventLog.writeEvent(0x534e4554, "72710897",
newPackage.applicationInfo.uid,
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 02f98b4..bfd280c 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -271,33 +271,9 @@
}, filter, null, getHandler());
}
- /**
- * This method posts a blocking call to the handler thread, so it should not be called from
- * that same thread.
- * @throws {@link IllegalStateException} if called from {@link #mHandlerThread}
- */
@Override
public ParceledListSlice getAvailableRollbacks() {
enforceManageRollbacks("getAvailableRollbacks");
- if (Thread.currentThread().equals(mHandlerThread)) {
- Slog.wtf(TAG, "Calling getAvailableRollbacks from mHandlerThread "
- + "causes a deadlock");
- throw new IllegalStateException("Cannot call RollbackManager#getAvailableRollbacks "
- + "from the handler thread!");
- }
-
- // Wait for the handler thread to get the list of available rollbacks
- // to get the most up-to-date results. This is intended to reduce test
- // flakiness when checking available rollbacks immediately after
- // installing a package with rollback enabled.
- CountDownLatch latch = new CountDownLatch(1);
- getHandler().post(() -> latch.countDown());
- try {
- latch.await();
- } catch (InterruptedException ie) {
- throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
- }
-
synchronized (mLock) {
List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mRollbacks.size(); ++i) {
@@ -306,6 +282,15 @@
rollbacks.add(rollback.info);
}
}
+
+ // Also return new rollbacks for which the PackageRollbackInfo is complete.
+ for (NewRollback newRollback : mNewRollbacks) {
+ if (newRollback.rollback.info.getPackages().size()
+ == newRollback.packageSessionIds.length
+ && !newRollback.isCancelled) {
+ rollbacks.add(newRollback.rollback.info);
+ }
+ }
return new ParceledListSlice<>(rollbacks);
}
}
@@ -562,6 +547,14 @@
}
}
}
+ for (NewRollback newRollback : mNewRollbacks) {
+ for (PackageRollbackInfo info : newRollback.rollback.info.getPackages()) {
+ if (info.getPackageName().equals(packageName)) {
+ newRollback.isCancelled = true;
+ break;
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d67048f..8897eca 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -610,11 +610,12 @@
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId) {
+ int type, boolean requireConfirmation, int userId, String opPackageName) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation, userId);
+ mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation, userId,
+ opPackageName);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 89a5305..4c93564 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -142,11 +142,12 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
+ Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
userState.mService.onSuggestSelection(sessionId, request, callback);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("suggestSelection",
() -> onSuggestSelection(sessionId, request, callback),
callback::onFailure, callback.asBinder(), this, userState));
}
@@ -166,11 +167,12 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
+ Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
userState.mService.onClassifyText(sessionId, request, callback);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("classifyText",
() -> onClassifyText(sessionId, request, callback),
callback::onFailure, callback.asBinder(), this, userState));
}
@@ -190,11 +192,12 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
+ Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
userState.mService.onGenerateLinks(sessionId, request, callback);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("generateLinks",
() -> onGenerateLinks(sessionId, request, callback),
callback::onFailure, callback.asBinder(), this, userState));
}
@@ -214,7 +217,7 @@
if (userState.isBoundLocked()) {
userState.mService.onSelectionEvent(sessionId, event);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("selectionEvent",
() -> onSelectionEvent(sessionId, event),
null /* onServiceFailure */, null /* binder */, this, userState));
}
@@ -238,7 +241,7 @@
if (userState.isBoundLocked()) {
userState.mService.onTextClassifierEvent(sessionId, event);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("textClassifierEvent",
() -> onTextClassifierEvent(sessionId, event),
null /* onServiceFailure */, null /* binder */, this, userState));
}
@@ -258,11 +261,12 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
+ Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
userState.mService.onDetectLanguage(sessionId, request, callback);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("detectLanguage",
() -> onDetectLanguage(sessionId, request, callback),
callback::onFailure, callback.asBinder(), this, userState));
}
@@ -282,11 +286,13 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
+ Slog.d(LOG_TAG,
+ "Unable to bind TextClassifierService at suggestConversationActions.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
userState.mService.onSuggestConversationActions(sessionId, request, callback);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("suggestConversationActions",
() -> onSuggestConversationActions(sessionId, request, callback),
callback::onFailure, callback.asBinder(), this, userState));
}
@@ -309,7 +315,7 @@
classificationContext, sessionId);
mSessionUserIds.put(sessionId, userId);
} else {
- userState.mPendingRequests.add(new PendingRequest(
+ userState.mPendingRequests.add(new PendingRequest("createTextClassificationSession",
() -> onCreateTextClassificationSession(classificationContext, sessionId),
null /* onServiceFailure */, null /* binder */, this, userState));
}
@@ -332,9 +338,10 @@
userState.mService.onDestroyTextClassificationSession(sessionId);
mSessionUserIds.remove(sessionId);
} else {
- userState.mPendingRequests.add(new PendingRequest(
- () -> onDestroyTextClassificationSession(sessionId),
- null /* onServiceFailure */, null /* binder */, this, userState));
+ userState.mPendingRequests.add(
+ new PendingRequest("destroyTextClassificationSession",
+ () -> onDestroyTextClassificationSession(sessionId),
+ null /* onServiceFailure */, null /* binder */, this, userState));
}
}
}
@@ -379,6 +386,7 @@
private static final class PendingRequest implements IBinder.DeathRecipient {
+ @Nullable private final String mName;
@Nullable private final IBinder mBinder;
@NonNull private final Runnable mRequest;
@Nullable private final Runnable mOnServiceFailure;
@@ -394,11 +402,12 @@
* @param service
* @param owningUser
*/
- PendingRequest(
+ PendingRequest(@Nullable String name,
@NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure,
@Nullable IBinder binder,
TextClassificationManagerService service,
UserState owningUser) {
+ mName = name;
mRequest =
logOnFailure(Preconditions.checkNotNull(request), "handling pending request");
mOnServiceFailure =
@@ -499,6 +508,8 @@
request.mRequest.run();
} else {
if (request.mOnServiceFailure != null) {
+ Slog.d(LOG_TAG, "Unable to bind TextClassifierService for PendingRequest "
+ + request.mName);
request.mOnServiceFailure.run();
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a2134be..8e66b14 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -842,6 +842,10 @@
private void setStateLocked(String inputId, int state, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
TvInputState inputState = userState.inputMap.get(inputId);
+ if (inputState == null) {
+ Slog.e(TAG, "failed to setStateLocked - unknown input id " + inputId);
+ return;
+ }
ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
int oldState = inputState.state;
inputState.state = state;
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index f1cd721..e76078f 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -41,6 +41,7 @@
import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
import static com.android.server.am.ActivityDisplayProto.STACKS;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
@@ -173,18 +174,10 @@
mService = root.mService;
mDisplayId = display.getDisplayId();
mDisplay = display;
- mDisplayContent = createDisplayContent();
+ mDisplayContent = mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this);
mDisplayContent.reconfigureDisplayLocked();
- updateBounds();
- }
-
- protected DisplayContent createDisplayContent() {
- return mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this);
- }
-
- private void updateBounds() {
- mDisplay.getRealSize(mTmpDisplaySize);
- setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
+ onRequestedOverrideConfigurationChanged(
+ mDisplayContent.getRequestedOverrideConfiguration());
}
void onDisplayChanged() {
@@ -200,7 +193,8 @@
}
}
- updateBounds();
+ mDisplay.getRealSize(mTmpDisplaySize);
+ setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
if (mDisplayContent != null) {
mDisplayContent.updateDisplayInfo();
mService.mWindowManager.requestTraversal();
@@ -368,7 +362,7 @@
return null;
}
- private boolean alwaysCreateStack(int windowingMode, int activityType) {
+ boolean alwaysCreateStack(int windowingMode, int activityType) {
// Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
// modes so that we can manage visual ordering and return types correctly.
return activityType == ACTIVITY_TYPE_STANDARD
@@ -496,10 +490,6 @@
return null;
}
- ActivityStack getNextFocusableStack() {
- return getNextFocusableStack(null /* currentFocus */, false /* ignoreCurrent */);
- }
-
ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
final int currentWindowingMode = currentFocus != null
? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
@@ -1541,6 +1531,17 @@
return mSingleTaskInstance;
}
+ @VisibleForTesting
+ void removeAllTasks() {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getChildAt(i);
+ final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ for (int j = tasks.size() - 1; j >= 0; --j) {
+ stack.removeTask(tasks.get(j), "removeAllTasks", REMOVE_TASK_MODE_DESTROYING);
+ }
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size()
+ (mSingleTaskInstance ? " mSingleTaskInstance" : ""));
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 31e8bbdab..fc36e99 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -106,6 +106,7 @@
import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
import static com.android.server.wm.ActivityStack.ActivityState.FINISHING;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
@@ -115,13 +116,13 @@
import static com.android.server.wm.ActivityStack.ActivityState.STARTED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
-import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
+import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
@@ -133,6 +134,8 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS;
@@ -177,8 +180,10 @@
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
+import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.MoveToDisplayItem;
import android.app.servertransaction.MultiWindowModeChangeItem;
import android.app.servertransaction.NewIntentItem;
@@ -198,12 +203,12 @@
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
-import android.os.Message;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -260,6 +265,8 @@
*/
final class ActivityRecord extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
+ private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
+ private static final String TAG_APP = TAG + POSTFIX_APP;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
@@ -282,6 +289,9 @@
private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
+ // How many activities have to be scheduled to stop to force a stop pass.
+ private static final int MAX_STOPPING_TO_FORCE = 3;
+
final ActivityTaskManagerService mAtmService; // owner
final IApplicationToken.Stub appToken; // window manager token
// TODO: Remove after unification
@@ -1372,16 +1382,23 @@
return stack != null ? stack.getDisplay() : null;
}
- boolean changeWindowTranslucency(boolean toOpaque) {
- if (fullscreen == toOpaque) {
- return false;
+ boolean setOccludesParent(boolean occludesParent) {
+ final boolean changed = mAppWindowToken.setOccludesParent(occludesParent);
+ if (changed) {
+ if (!occludesParent) {
+ getActivityStack().convertActivityToTranslucent(this);
+ }
+ // Keep track of the number of fullscreen activities in this task.
+ task.numFullscreen += occludesParent ? +1 : -1;
+ fullscreen = occludesParent;
}
-
- // Keep track of the number of fullscreen activities in this task.
- task.numFullscreen += toOpaque ? +1 : -1;
-
- fullscreen = toOpaque;
- return true;
+ // Always ensure visibility if this activity doesn't occlude parent, so the
+ // {@link #returningOptions} of the activity under this one can be applied in
+ // {@link #handleAlreadyVisible()}.
+ if (changed || !occludesParent) {
+ mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ }
+ return changed;
}
void takeFromHistory() {
@@ -1590,8 +1607,7 @@
if (!Objects.equals(cur.taskAffinity, taskAffinity)) {
break;
}
- cur.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "request-affinity", true /* oomAdj */);
+ cur.finishIfPossible("request-affinity", true /* oomAdj */);
}
}
@@ -1648,14 +1664,17 @@
@interface FinishRequest {}
/**
- * See {@link #finishActivityLocked(int, Intent, String, boolean, boolean)}
+ * See {@link #finishIfPossible(int, Intent, String, boolean, boolean)}
*/
- @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason,
- boolean oomAdj) {
- return finishActivityLocked(resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
+ @FinishRequest int finishIfPossible(String reason, boolean oomAdj) {
+ return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
+ oomAdj, !PAUSE_IMMEDIATELY);
}
/**
+ * Finish activity if possible. If activity was resumed - we must first pause it to make the
+ * activity below resumed. Otherwise we will try to complete the request immediately by calling
+ * {@link #completeFinishing(String)}.
* @return One of {@link FinishRequest} values:
* {@link #FINISH_RESULT_REMOVED} if this activity has been removed from the history list.
* {@link #FINISH_RESULT_REQUESTED} if removal process was started, but it is still in the list
@@ -1663,7 +1682,7 @@
* {@link #FINISH_RESULT_CANCELLED} if activity is already finishing or in invalid state and the
* request to finish it was not ignored.
*/
- @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason,
+ @FinishRequest int finishIfPossible(int resultCode, Intent resultData, String reason,
boolean oomAdj, boolean pauseImmediately) {
if (DEBUG_RESULTS || DEBUG_STATES) {
Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode
@@ -1702,20 +1721,24 @@
pauseKeyDispatchingLocked();
final ActivityStack stack = getActivityStack();
- stack.adjustFocusedActivityStack(this, "finishActivity");
+ stack.adjustFocusedActivityStack(this, "finishIfPossible");
finishActivityResults(resultCode, resultData);
final boolean endTask = index <= 0 && !task.isClearingToReuseTask();
final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE;
- if (stack.getResumedActivity() == this) {
- if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
- Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
- }
+ if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
task.getTaskInfo());
}
+ // Prepare app close transition, but don't execute just yet. It is possible that
+ // an activity that will be made resumed in place of this one will immediately
+ // launch another new activity. In this case current closing transition will be
+ // combined with open transition for the new activity.
+ if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
+ Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
+ }
getDisplay().mDisplayContent.prepareAppTransition(transit, false);
// When finishing the activity preemptively take the snapshot before the app window
@@ -1742,17 +1765,17 @@
mAtmService.getLockTaskController().clearLockedTask(task);
}
} else if (!isState(PAUSING)) {
- // If the activity is PAUSING, we will complete the finish once
- // it is done pausing; else we can just directly finish it here.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + this);
if (visible) {
+ // Prepare and execute close transition.
prepareActivityHideTransitionAnimation(transit);
}
- final int finishMode = (visible || nowVisible) ? FINISH_AFTER_VISIBLE
- : FINISH_AFTER_PAUSE;
- final boolean removedActivity = finishCurrentActivityLocked(finishMode, oomAdj,
- "finishActivityLocked") == null;
+ final boolean removedActivity = completeFinishing("finishIfPossible") == null;
+ // Performance optimization - only invoke OOM adjustment if the state changed to
+ // 'STOPPING'. Otherwise it will not change the OOM scores.
+ if (oomAdj && isState(STOPPING)) {
+ mAtmService.updateOomAdj();
+ }
// The following code is an optimization. When the last non-task overlay activity
// is removed from the task, we remove the entire task from the stack. However,
@@ -1769,6 +1792,7 @@
taskOverlay.prepareActivityHideTransitionAnimation(transit);
}
}
+
return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED;
} else {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + this);
@@ -1787,101 +1811,298 @@
dc.executeAppTransition();
}
- static final int FINISH_IMMEDIATELY = 0;
- private static final int FINISH_AFTER_PAUSE = 1;
- static final int FINISH_AFTER_VISIBLE = 2;
+ /**
+ * Complete activity finish request that was initiated earlier. If the activity is still
+ * pausing we will wait for it to complete its transition. If the activity that should appear in
+ * place of this one is not visible yet - we'll wait for it first. Otherwise - activity can be
+ * destroyed right away.
+ * @param reason Reason for finishing the activity.
+ * @return Flag indicating whether the activity was removed from history.
+ */
+ ActivityRecord completeFinishing(String reason) {
+ if (!finishing || isState(RESUMED)) {
+ throw new IllegalArgumentException(
+ "Activity must be finishing and not resumed to complete, r=" + this
+ + ", finishing=" + finishing + ", state=" + mState);
+ }
- ActivityRecord finishCurrentActivityLocked(int mode, boolean oomAdj, String reason) {
- // First things first: if this activity is currently visible,
- // and the resumed activity is not yet visible, then hold off on
- // finishing until the resumed one becomes visible.
+ if (isState(PAUSING)) {
+ // Activity is marked as finishing and will be processed once it completes.
+ return this;
+ }
+ boolean activityRemoved = false;
+
+ // If this activity is currently visible, and the resumed activity is not yet visible, then
+ // hold off on finishing until the resumed one becomes visible.
// The activity that we are finishing may be over the lock screen. In this case, we do not
// want to consider activities that cannot be shown on the lock screen as running and should
// proceed with finishing the activity if there is no valid next top running activity.
// Note that if this finishing activity is floating task, we don't need to wait the
// next activity resume and can destroy it directly.
+ // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere
+ final ActivityRecord next = getDisplay().topRunningActivity(
+ true /* considerKeyguardState */);
+ final boolean isVisible = visible || nowVisible;
final ActivityStack stack = getActivityStack();
- final ActivityDisplay display = getDisplay();
- final ActivityRecord next = display.topRunningActivity(true /* considerKeyguardState */);
- final boolean isFloating = getConfiguration().windowConfiguration.tasksAreFloating();
-
- if (mode == FINISH_AFTER_VISIBLE && (visible || nowVisible)
- && next != null && !next.nowVisible && !isFloating) {
- if (!mStackSupervisor.mStoppingActivities.contains(this)) {
- stack.addToStopping(this, false /* scheduleIdle */, false /* idleDelayed */,
- "finishCurrentActivityLocked");
- }
+ final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack();
+ if (isVisible && next != null && !next.nowVisible) {
+ addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
+ "completeFinishing");
if (DEBUG_STATES) {
Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (finish requested)");
}
- setState(STOPPING, "finishCurrentActivityLocked");
- if (oomAdj) {
- mAtmService.updateOomAdj();
- }
- return this;
- }
-
- // make sure the record is cleaned out of other places.
- mStackSupervisor.mStoppingActivities.remove(this);
- mStackSupervisor.mGoingToSleepActivities.remove(this);
- final ActivityState prevState = getState();
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + this);
-
- setState(FINISHING, "finishCurrentActivityLocked");
-
- // Don't destroy activity immediately if the display contains home stack, although there is
- // no next activity at the moment but another home activity should be started later. Keep
- // this activity alive until next home activity is resumed then user won't see a temporary
- // black screen.
- final boolean noRunningStack = next == null && display.topRunningActivity() == null
- && display.getHomeStack() == null;
- final boolean noFocusedStack = stack != display.getFocusedStack();
- final boolean finishingInNonFocusedStackOrNoRunning = mode == FINISH_AFTER_VISIBLE
- && prevState == PAUSED && (noFocusedStack || noRunningStack);
-
- if (mode == FINISH_IMMEDIATELY
- || (prevState == PAUSED && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
- || finishingInNonFocusedStackOrNoRunning
- || prevState == STARTED
- || prevState == STOPPING
- || prevState == STOPPED
- || prevState == ActivityState.INITIALIZING) {
- makeFinishingLocked();
- boolean activityRemoved = stack.destroyActivityLocked(this, true /* removeFromApp */,
- "finish-imm:" + reason);
-
- if (finishingInNonFocusedStackOrNoRunning) {
- // Finishing activity that was in paused state and it was in not currently focused
- // stack, need to make something visible in its place. Also if the display does not
- // have running activity, the configuration may need to be updated for restoring
- // original orientation of the display.
+ setState(STOPPING, "completeFinishing");
+ if (notFocusedStack) {
mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(),
false /* markFrozenIfConfigChanged */, true /* deferResume */);
}
- if (activityRemoved) {
- mRootActivityContainer.resumeFocusedStacksTopActivities();
- }
- if (DEBUG_CONTAINERS) {
- Slog.d(TAG_CONTAINERS, "destroyActivityLocked: finishCurrentActivityLocked r="
- + this + " destroy returned removed=" + activityRemoved);
- }
- return activityRemoved ? null : this;
+ } else if (isVisible && isState(PAUSED) && getActivityStack().isFocusedStackOnDisplay()
+ && !inPinnedWindowingMode()) {
+ // TODO(b/137329632): Currently non-focused stack is handled differently.
+ addToFinishingAndWaitForIdle();
+ } else {
+ // Not waiting for the next one to become visible - finish right away.
+ activityRemoved = destroyIfPossible(reason);
}
- // Need to go through the full pause cycle to get this
- // activity into the stopped state and then finish it.
- if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + this);
+ return activityRemoved ? null : this;
+ }
+
+ /**
+ * Destroy and cleanup the activity both on client and server if possible. If activity is the
+ * last one left on display with home stack and there is no other running activity - delay
+ * destroying it until the next one starts.
+ */
+ boolean destroyIfPossible(String reason) {
+ setState(FINISHING, "destroyIfPossible");
+
+ // Make sure the record is cleaned out of other places.
+ mStackSupervisor.mStoppingActivities.remove(this);
+ mStackSupervisor.mGoingToSleepActivities.remove(this);
+
+ final ActivityStack stack = getActivityStack();
+ final ActivityDisplay display = getDisplay();
+ // TODO(b/137329632): Exclude current activity when looking for the next one with
+ // ActivityDisplay#topRunningActivity().
+ final ActivityRecord next = display.topRunningActivity();
+ final boolean isLastStackOverEmptyHome =
+ next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null;
+ if (isLastStackOverEmptyHome) {
+ // Don't destroy activity immediately if this is the last activity on the display and
+ // the display contains home stack. Although there is no next activity at the moment,
+ // another home activity should be started later. Keep this activity alive until next
+ // home activity is resumed. This way the user won't see a temporary black screen.
+ addToFinishingAndWaitForIdle();
+ return false;
+ }
+ makeFinishingLocked();
+
+ final boolean activityRemoved = destroyImmediately(true /* removeFromApp */,
+ "finish-imm:" + reason);
+
+ // If the display does not have running activity, the configuration may need to be
+ // updated for restoring original orientation of the display.
+ if (next == null) {
+ mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ false /* markFrozenIfConfigChanged */, true /* deferResume */);
+ }
+ if (activityRemoved) {
+ mRootActivityContainer.resumeFocusedStacksTopActivities();
+ }
+
+ if (DEBUG_CONTAINERS) {
+ Slog.d(TAG_CONTAINERS, "destroyIfPossible: r=" + this + " destroy returned removed="
+ + activityRemoved);
+ }
+
+ return activityRemoved;
+ }
+
+ @VisibleForTesting
+ void addToFinishingAndWaitForIdle() {
+ if (DEBUG_STATES) Slog.v(TAG, "Enqueueing pending finish: " + this);
+ setState(FINISHING, "addToFinishingAndWaitForIdle");
mStackSupervisor.mFinishingActivities.add(this);
resumeKeyDispatchingLocked();
mRootActivityContainer.resumeFocusedStacksTopActivities();
- // If activity was not paused at this point - explicitly pause it to start finishing
- // process. Finishing will be completed once it reports pause back.
- if (isState(RESUMED) && stack.mPausingActivity != null) {
- stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- next /* resuming */, false /* dontWait */);
+ }
+
+ /**
+ * Destroy the current CLIENT SIDE instance of an activity. This may be called both when
+ * actually finishing an activity, or when performing a configuration switch where we destroy
+ * the current client-side object but then create a new client-side object for this same
+ * HistoryRecord.
+ * Normally the server-side record will be removed when the client reports back after
+ * destruction. If, however, at this point there is no client process attached, the record will
+ * removed immediately.
+ */
+ boolean destroyImmediately(boolean removeFromApp, String reason) {
+ if (DEBUG_SWITCH || DEBUG_CLEANUP) {
+ Slog.v(TAG_SWITCH, "Removing activity from " + reason + ": token=" + this
+ + ", app=" + (hasProcess() ? app.mName : "(null)"));
}
- return this;
+
+ if (isState(DESTROYING, DESTROYED)) {
+ if (DEBUG_STATES) {
+ Slog.v(TAG_STATES, "activity " + this + " already destroying."
+ + "skipping request with reason:" + reason);
+ }
+ return false;
+ }
+
+ EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, mUserId,
+ System.identityHashCode(this), getTaskRecord().taskId, shortComponentName, reason);
+
+ boolean removedFromHistory = false;
+
+ cleanUp(false /* cleanServices */, false /* setState */);
+
+ final ActivityStack stack = getActivityStack();
+ final boolean hadApp = hasProcess();
+
+ if (hadApp) {
+ if (removeFromApp) {
+ app.removeActivity(this);
+ if (!app.hasActivities()) {
+ mAtmService.clearHeavyWeightProcessIfEquals(app);
+ // Update any services we are bound to that might care about whether
+ // their client may have activities.
+ // No longer have activities, so update LRU list and oom adj.
+ app.updateProcessInfo(true /* updateServiceConnectionActivities */,
+ false /* activityChange */, true /* updateOomAdj */);
+ }
+ }
+
+ boolean skipDestroy = false;
+
+ try {
+ if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ DestroyActivityItem.obtain(finishing, configChangeFlags));
+ } catch (Exception e) {
+ // We can just ignore exceptions here... if the process has crashed, our death
+ // notification will clean things up.
+ if (finishing) {
+ removeFromHistory(reason + " exceptionInScheduleDestroy");
+ removedFromHistory = true;
+ skipDestroy = true;
+ }
+ }
+
+ nowVisible = false;
+
+ // If the activity is finishing, we need to wait on removing it from the list to give it
+ // a chance to do its cleanup. During that time it may make calls back with its token
+ // so we need to be able to find it on the list and so we don't want to remove it from
+ // the list yet. Otherwise, we can just immediately put it in the destroyed state since
+ // we are not removing it from the list.
+ if (finishing && !skipDestroy) {
+ if (DEBUG_STATES) {
+ Slog.v(TAG_STATES, "Moving to DESTROYING: " + this + " (destroy requested)");
+ }
+ setState(DESTROYING,
+ "destroyActivityLocked. finishing and not skipping destroy");
+ stack.scheduleDestroyTimeoutForActivity(this);
+ } else {
+ if (DEBUG_STATES) {
+ Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (destroy skipped)");
+ }
+ setState(DESTROYED,
+ "destroyActivityLocked. not finishing or skipping destroy");
+ if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this);
+ app = null;
+ }
+ } else {
+ // Remove this record from the history.
+ if (finishing) {
+ removeFromHistory(reason + " hadNoApp");
+ removedFromHistory = true;
+ } else {
+ if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (no app)");
+ setState(DESTROYED, "destroyActivityLocked. not finishing and had no app");
+ }
+ }
+
+ configChangeFlags = 0;
+
+ if (!stack.removeActivityFromLRUList(this) && hadApp) {
+ Slog.w(TAG, "Activity " + this + " being finished, but not in LRU list");
+ }
+
+ return removedFromHistory;
+ }
+
+ boolean safelyDestroy(String reason) {
+ if (isDestroyable()) {
+ if (DEBUG_SWITCH) {
+ final ActivityStack stack = getActivityStack();
+ Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
+ + " resumed=" + stack.mResumedActivity
+ + " pausing=" + stack.mPausingActivity
+ + " for reason " + reason);
+ }
+ return destroyImmediately(true /* removeFromApp */, reason);
+ }
+ return false;
+ }
+
+ /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */
+ void removeFromHistory(String reason) {
+ finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */);
+ makeFinishingLocked();
+ if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE) {
+ Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + " from stack callers="
+ + Debug.getCallers(5));
+ }
+
+ takeFromHistory();
+ final ActivityStack stack = getActivityStack();
+ stack.removeTimeoutsForActivity(this);
+ if (DEBUG_STATES) {
+ Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (removed from history)");
+ }
+ setState(DESTROYED, "removeFromHistory");
+ if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this);
+ app = null;
+ removeWindowContainer();
+ final TaskRecord task = getTaskRecord();
+ final boolean lastActivity = task.removeActivity(this);
+ // If we are removing the last activity in the task, not including task overlay activities,
+ // then fall through into the block below to remove the entire task itself
+ final boolean onlyHasTaskOverlays =
+ task.onlyHasTaskOverlayActivities(false /* excludingFinishing */);
+
+ if (lastActivity || onlyHasTaskOverlays) {
+ if (DEBUG_STATES) {
+ Slog.i(TAG, "removeFromHistory: last activity removed from " + this
+ + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
+ }
+
+ // The following block can be executed multiple times if there is more than one overlay.
+ // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
+ // of the task by id and exiting early if not found.
+ if (onlyHasTaskOverlays) {
+ // When destroying a task, tell the supervisor to remove it so that any activity it
+ // has can be cleaned up correctly. This is currently the only place where we remove
+ // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays
+ // state into removeTask(), we just clear the task here before the other residual
+ // work.
+ // TODO: If the callers to removeTask() changes such that we have multiple places
+ // where we are destroying the task, move this back into removeTask()
+ mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
+ !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
+ }
+
+ // We must keep the task around until all activities are destroyed. The following
+ // statement will only execute once since overlays are also considered activities.
+ if (lastActivity) {
+ stack.removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
+ }
+ }
+
+ cleanUpActivityServices();
+ removeUriPermissionsLocked();
}
void makeFinishingLocked() {
@@ -1898,6 +2119,96 @@
}
}
+ /**
+ * This method is to only be called from the client via binder when the activity is destroyed
+ * AND finished.
+ */
+ void destroyed(String reason) {
+ getActivityStack().removeDestroyTimeoutForActivity(this);
+
+ if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + this);
+
+ if (!isState(DESTROYING, DESTROYED)) {
+ throw new IllegalStateException(
+ "Reported destroyed for activity that is not destroying: r=" + this);
+ }
+
+ if (isInStackLocked()) {
+ cleanUp(true /* cleanServices */, false /* setState */);
+ removeFromHistory(reason);
+ }
+
+ mRootActivityContainer.resumeFocusedStacksTopActivities();
+ }
+
+ /**
+ * Perform the common clean-up of an activity record. This is called both as part of
+ * destroyActivityLocked() (when destroying the client-side representation) and cleaning things
+ * up as a result of its hosting processing going away, in which case there is no remaining
+ * client-side state to destroy so only the cleanup here is needed.
+ *
+ * Note: Call before {@link #removeFromHistory(String)}.
+ */
+ void cleanUp(boolean cleanServices, boolean setState) {
+ final ActivityStack stack = getActivityStack();
+ stack.onActivityRemovedFromStack(this);
+
+ deferRelaunchUntilPaused = false;
+ frozenBeforeDestroy = false;
+
+ if (setState) {
+ setState(DESTROYED, "cleanUp");
+ if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this);
+ app = null;
+ }
+
+ // Inform supervisor the activity has been removed.
+ mStackSupervisor.cleanupActivity(this);
+
+ // Remove any pending results.
+ if (finishing && pendingResults != null) {
+ for (WeakReference<PendingIntentRecord> apr : pendingResults) {
+ PendingIntentRecord rec = apr.get();
+ if (rec != null) {
+ mAtmService.mPendingIntentController.cancelIntentSender(rec,
+ false /* cleanActivity */);
+ }
+ }
+ pendingResults = null;
+ }
+
+ if (cleanServices) {
+ cleanUpActivityServices();
+ }
+
+ // Get rid of any pending idle timeouts.
+ stack.removeTimeoutsForActivity(this);
+ // Clean-up activities are no longer relaunching (e.g. app process died). Notify window
+ // manager so it can update its bookkeeping.
+ mAtmService.mWindowManager.notifyAppRelaunchesCleared(appToken);
+ }
+
+ /**
+ * Perform clean-up of service connections in an activity record.
+ */
+ private void cleanUpActivityServices() {
+ if (mServiceConnectionsHolder == null) {
+ return;
+ }
+ // Throw away any services that have been bound by this activity.
+ mServiceConnectionsHolder.disconnectActivityFromServices();
+ }
+
+ void logStartActivity(int tag, TaskRecord task) {
+ final Uri data = intent.getData();
+ final String strData = data != null ? data.toSafeString() : null;
+
+ EventLog.writeEvent(tag,
+ mUserId, System.identityHashCode(this), task.taskId,
+ shortComponentName, intent.getAction(),
+ intent.getType(), strData, intent.getFlags());
+ }
+
UriPermissionOwner getUriPermissionsLocked() {
if (uriPermissions == null) {
uriPermissions = new UriPermissionOwner(mAtmService.mUgmInternal, this);
@@ -1934,6 +2245,33 @@
}
}
+ void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+ Intent data) {
+ if (callingUid > 0) {
+ mAtmService.mUgmInternal.grantUriPermissionFromIntent(callingUid, packageName,
+ data, getUriPermissionsLocked(), mUserId);
+ }
+
+ if (DEBUG_RESULTS) {
+ Slog.v(TAG, "Send activity result to " + this
+ + " : who=" + resultWho + " req=" + requestCode
+ + " res=" + resultCode + " data=" + data);
+ }
+ if (isState(RESUMED) && attachedToProcess()) {
+ try {
+ final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
+ list.add(new ResultInfo(resultWho, requestCode, resultCode, data));
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ ActivityResultItem.obtain(list));
+ return;
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception thrown sending result to " + this, e);
+ }
+ }
+
+ addResultLocked(null /* from */, resultWho, requestCode, resultCode, data);
+ }
+
private void addNewIntentLocked(ReferrerIntent intent) {
if (newIntents == null) {
newIntents = new ArrayList<>();
@@ -2413,6 +2751,65 @@
}
}
+ void makeInvisible() {
+ if (!visible) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this);
+ return;
+ }
+ // Now for any activities that aren't visible to the user, make sure they no longer are
+ // keeping the screen frozen.
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+ }
+ try {
+ final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
+ "makeInvisible", true /* beforeStopping */);
+ // Defer telling the client it is hidden if it can enter Pip and isn't current paused,
+ // stopped or stopping. This gives it a chance to enter Pip in onPause().
+ // TODO: There is still a question surrounding activities in multi-window mode that want
+ // to enter Pip after they are paused, but are still visible. I they should be okay to
+ // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and
+ // the current contract for "auto-Pip" is that the app should enter it before onPause
+ // returns. Just need to confirm this reasoning makes sense.
+ final boolean deferHidingClient = canEnterPictureInPicture
+ && !isState(STOPPING, STOPPED, PAUSED);
+ setDeferHidingClient(deferHidingClient);
+ setVisible(false);
+
+ switch (getState()) {
+ case STOPPING:
+ case STOPPED:
+ if (attachedToProcess()) {
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this);
+ }
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
+ appToken, WindowVisibilityItem.obtain(false /* showWindow */));
+ }
+
+ // Reset the flag indicating that an app can enter picture-in-picture once the
+ // activity is hidden
+ supportsEnterPipOnTaskSwitch = false;
+ break;
+
+ case INITIALIZING:
+ case RESUMED:
+ case PAUSING:
+ case PAUSED:
+ case STARTED:
+ addToStopping(true /* scheduleIdle */,
+ canEnterPictureInPicture /* idleDelayed */, "makeInvisible");
+ break;
+
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making hidden: " + intent.getComponent(), e);
+ }
+ }
+
/**
* Make activity resumed or paused if needed.
* @param activeActivity an activity that is resumed or just completed pause action.
@@ -2603,13 +3000,75 @@
}
}
+ void stopIfPossible() {
+ if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
+ final ActivityStack stack = getActivityStack();
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
+ || (info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0) {
+ if (!finishing) {
+ if (!stack.shouldSleepActivities()) {
+ if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this);
+ if (finishIfPossible("stop-no-history", false /* oomAdj */)
+ != FINISH_RESULT_CANCELLED) {
+ // {@link adjustFocusedActivityStack} must have been already called.
+ resumeKeyDispatchingLocked();
+ return;
+ }
+ } else {
+ if (DEBUG_STATES) {
+ Slog.d(TAG_STATES, "Not finishing noHistory " + this
+ + " on stop because we're just sleeping");
+ }
+ }
+ }
+ }
+
+ if (!attachedToProcess()) {
+ return;
+ }
+ stack.adjustFocusedActivityStack(this, "stopActivity");
+ resumeKeyDispatchingLocked();
+ try {
+ stopped = false;
+ if (DEBUG_STATES) {
+ Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (stop requested)");
+ }
+ setState(STOPPING, "stopIfPossible");
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Stopping visible=" + visible + " for " + this);
+ }
+ if (!visible) {
+ setVisible(false);
+ }
+ EventLogTags.writeAmStopActivity(
+ mUserId, System.identityHashCode(this), shortComponentName);
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ StopActivityItem.obtain(visible, configChangeFlags));
+ if (stack.shouldSleepOrShutDownActivities()) {
+ setSleeping(true);
+ }
+ stack.scheduleStopTimeoutForActivity(this);
+ } catch (Exception e) {
+ // Maybe just ignore exceptions here... if the process has crashed, our death
+ // notification will clean things up.
+ Slog.w(TAG, "Exception thrown during pause", e);
+ // Just in case, assume it to be stopped.
+ stopped = true;
+ if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + this);
+ setState(STOPPED, "stopIfPossible");
+ if (deferRelaunchUntilPaused) {
+ destroyImmediately(true /* removeFromApp */, "stop-except");
+ }
+ }
+ }
+
final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState,
CharSequence description) {
final ActivityStack stack = getActivityStack();
final boolean isStopping = mState == STOPPING;
if (!isStopping && mState != RESTARTING_PROCESS) {
Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this);
- stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
+ stack.removeStopTimeoutForActivity(this);
return;
}
if (newPersistentState != null) {
@@ -2627,7 +3086,7 @@
if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + mIcicle);
if (!stopped) {
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)");
- stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
+ stack.removeStopTimeoutForActivity(this);
stopped = true;
if (isStopping) {
setState(STOPPED, "activityStoppedLocked");
@@ -2641,7 +3100,7 @@
clearOptionsLocked();
} else {
if (deferRelaunchUntilPaused) {
- stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config");
+ destroyImmediately(true /* removeFromApp */, "stop-config");
mRootActivityContainer.resumeFocusedStacksTopActivities();
} else {
mRootActivityContainer.updatePreviousProcess(this);
@@ -2650,6 +3109,34 @@
}
}
+ void addToStopping(boolean scheduleIdle, boolean idleDelayed, String reason) {
+ if (!mStackSupervisor.mStoppingActivities.contains(this)) {
+ EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, mUserId,
+ System.identityHashCode(this), shortComponentName, reason);
+ mStackSupervisor.mStoppingActivities.add(this);
+ }
+
+ final ActivityStack stack = getActivityStack();
+ // If we already have a few activities waiting to stop, then give up on things going idle
+ // and start clearing them out. Or if r is the last of activity of the last task the stack
+ // will be empty and must be cleared immediately.
+ boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE
+ || (isRootOfTask() && stack.getChildCount() <= 1);
+ if (scheduleIdle || forceIdle) {
+ if (DEBUG_PAUSE) {
+ Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" + forceIdle
+ + "immediate=" + !idleDelayed);
+ }
+ if (!idleDelayed) {
+ mStackSupervisor.scheduleIdleLocked();
+ } else {
+ mStackSupervisor.scheduleIdleTimeoutLocked(this);
+ }
+ } else {
+ stack.checkReadyForSleep();
+ }
+ }
+
void startLaunchTickingLocked() {
if (Build.IS_USER) {
return;
@@ -2670,18 +3157,18 @@
return false;
}
- Message msg = stack.mHandler.obtainMessage(LAUNCH_TICK_MSG, this);
- stack.mHandler.removeMessages(LAUNCH_TICK_MSG);
- stack.mHandler.sendMessageDelayed(msg, LAUNCH_TICK);
+ stack.removeLaunchTickMessages();
+ stack.scheduleLaunchTickForActivity(this);
return true;
}
void finishLaunchTickingLocked() {
launchTickTime = 0;
final ActivityStack stack = getActivityStack();
- if (stack != null) {
- stack.mHandler.removeMessages(LAUNCH_TICK_MSG);
+ if (stack == null) {
+ return;
}
+ stack.removeLaunchTickMessages();
}
// IApplicationToken
@@ -3658,7 +4145,7 @@
if (!attachedToProcess()) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is destroying non-running " + this);
- stack.destroyActivityLocked(this, true, "config");
+ destroyImmediately(true /* removeFromApp */, "config");
} else if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
@@ -3842,7 +4329,7 @@
} else {
final ActivityStack stack = getActivityStack();
if (stack != null) {
- stack.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
+ stack.removePauseTimeoutForActivity(this);
}
setState(PAUSED, "relaunchActivityLocked");
}
@@ -3882,7 +4369,7 @@
if (!visible || mHaveState) {
// Kill its process immediately because the activity should be in background.
// The activity state will be update to {@link #DESTROYED} in
- // {@link ActivityStack#cleanUpActivityLocked} when handling process died.
+ // {@link ActivityStack#cleanUp} when handling process died.
mAtmService.mH.post(() -> {
final WindowProcessController wpc;
synchronized (mAtmService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 12eab50..8bdedff 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -56,12 +56,7 @@
import static com.android.server.am.ActivityStackProto.TASKS;
import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM;
import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
-import static com.android.server.wm.ActivityRecord.FINISH_AFTER_VISIBLE;
-import static com.android.server.wm.ActivityRecord.FINISH_IMMEDIATELY;
-import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
-import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
-import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
@@ -70,14 +65,12 @@
import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -91,7 +84,6 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
@@ -108,6 +100,7 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.RootActivityContainer.FindTaskResult;
+import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static java.lang.Integer.MAX_VALUE;
@@ -124,12 +117,9 @@
import android.app.WindowConfiguration.WindowingMode;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
-import android.app.servertransaction.StopActivityItem;
-import android.app.servertransaction.WindowVisibilityItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -164,11 +154,9 @@
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.AppTimeTracker;
import com.android.server.am.EventLogTags;
-import com.android.server.am.PendingIntentRecord;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -182,7 +170,6 @@
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
- private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
@@ -195,7 +182,7 @@
private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
// Ticks during which we check progress while waiting for an app to launch.
- static final int LAUNCH_TICK = 500;
+ private static final int LAUNCH_TICK = 500;
// How long we wait until giving up on the last activity to pause. This
// is short because it directly impacts the responsiveness of starting the
@@ -223,9 +210,6 @@
// convertToTranslucent().
private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
- // How many activities have to be scheduled to stop to force a stop pass.
- private static final int MAX_STOPPING_TO_FORCE = 3;
-
@IntDef(prefix = {"STACK_VISIBILITY"}, value = {
STACK_VISIBILITY_VISIBLE,
STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -412,12 +396,12 @@
private boolean mTopActivityOccludesKeyguard;
private ActivityRecord mTopDismissingKeyguardActivity;
- static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1;
- static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2;
- static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3;
- static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4;
- static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5;
- static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6;
+ private static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1;
+ private static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2;
+ private static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3;
+ private static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4;
+ private static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5;
+ private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6;
// TODO: remove after unification.
TaskStack mTaskStack;
@@ -431,7 +415,7 @@
}
}
- final Handler mHandler;
+ private final Handler mHandler;
private class ActivityStackHandler extends Handler {
@@ -468,7 +452,9 @@
// so we need to be conservative and assume it isn't.
Slog.w(TAG, "Activity destroy timeout for " + r);
synchronized (mService.mGlobalLock) {
- activityDestroyedLocked(r != null ? r.appToken : null, "destroyTimeout");
+ if (r != null) {
+ r.destroyed("destroyTimeout");
+ }
}
} break;
case STOP_TIMEOUT_MSG: {
@@ -1215,12 +1201,17 @@
return display != null && display.isSingleTaskInstance();
}
- final void removeActivitiesFromLRUListLocked(TaskRecord task) {
+ private void removeActivitiesFromLRUList(TaskRecord task) {
for (ActivityRecord r : task.mActivities) {
mLRUActivities.remove(r);
}
}
+ /** @return {@code true} if LRU list contained the specified activity. */
+ final boolean removeActivityFromLRUList(ActivityRecord activity) {
+ return mLRUActivities.remove(activity);
+ }
+
final boolean updateLRUListLocked(ActivityRecord r) {
final boolean hadit = mLRUActivities.remove(r);
mLRUActivities.add(r);
@@ -1617,18 +1608,6 @@
}
/**
- * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because
- * this directly impacts the responsiveness seen by the user.
- */
- private void schedulePauseTimeout(ActivityRecord r) {
- final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);
- msg.obj = r;
- r.pauseTime = SystemClock.uptimeMillis();
- mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
- }
-
- /**
* Start pausing the currently resumed activity. It is an error to call this if there
* is already an activity being paused or there is no resumed activity.
*
@@ -1728,7 +1707,7 @@
return false;
} else {
- schedulePauseTimeout(prev);
+ schedulePauseTimeoutForActivity(prev);
return true;
}
@@ -1771,8 +1750,7 @@
if (r.finishing) {
if (DEBUG_PAUSE) Slog.v(TAG,
"Executing finish of failed to pause activity: " + r);
- r.finishCurrentActivityLocked(FINISH_AFTER_VISIBLE, false,
- "activityPausedLocked");
+ r.completeFinishing("activityPausedLocked");
}
}
}
@@ -1790,8 +1768,7 @@
prev.setState(PAUSED, "completePausedLocked");
if (prev.finishing) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
- prev = prev.finishCurrentActivityLocked(FINISH_AFTER_VISIBLE, false /* oomAdj */,
- "completePausedLocked");
+ prev = prev.completeFinishing("completePausedLocked");
} else if (prev.hasProcess()) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev
+ " wasStopping=" + wasStopping + " visible=" + prev.visible);
@@ -1810,7 +1787,7 @@
prev.setDeferHidingClient(false);
// If we were visible then resumeTopActivities will release resources before
// stopping.
- addToStopping(prev, true /* scheduleIdle */, false /* idleDelayed */,
+ prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
"completePauseLocked");
}
} else {
@@ -1872,32 +1849,6 @@
mRootActivityContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
}
- void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed, String reason) {
- if (!mStackSupervisor.mStoppingActivities.contains(r)) {
- EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, r.mUserId,
- System.identityHashCode(r), r.shortComponentName, reason);
- mStackSupervisor.mStoppingActivities.add(r);
- }
-
- // If we already have a few activities waiting to stop, then give up
- // on things going idle and start clearing them out. Or if r is the
- // last of activity of the last task the stack will be empty and must
- // be cleared immediately.
- boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE
- || (r.isRootOfTask() && mTaskHistory.size() <= 1);
- if (scheduleIdle || forceIdle) {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle="
- + forceIdle + "immediate=" + !idleDelayed);
- if (!idleDelayed) {
- mStackSupervisor.scheduleIdleLocked();
- } else {
- mStackSupervisor.scheduleIdleTimeoutLocked(r);
- }
- } else {
- checkReadyForSleep();
- }
- }
-
/**
* Returns true if the stack is translucent and can have other contents visible behind it if
* needed. A stack is considered translucent if it don't contain a visible or
@@ -2207,7 +2158,7 @@
+ " stackShouldBeVisible=" + stackShouldBeVisible
+ " behindFullscreenActivity=" + behindFullscreenActivity
+ " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
- makeInvisible(r);
+ r.makeInvisible();
}
}
final int windowingMode = getWindowingMode();
@@ -2384,63 +2335,6 @@
return false;
}
- // TODO: Should probably be moved into ActivityRecord.
- private void makeInvisible(ActivityRecord r) {
- if (!r.visible) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
- return;
- }
- // Now for any activities that aren't visible to the user, make sure they no longer are
- // keeping the screen frozen.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.getState());
- try {
- final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
- "makeInvisible", true /* beforeStopping */);
- // Defer telling the client it is hidden if it can enter Pip and isn't current paused,
- // stopped or stopping. This gives it a chance to enter Pip in onPause().
- // TODO: There is still a question surrounding activities in multi-window mode that want
- // to enter Pip after they are paused, but are still visible. I they should be okay to
- // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and
- // the current contract for "auto-Pip" is that the app should enter it before onPause
- // returns. Just need to confirm this reasoning makes sense.
- final boolean deferHidingClient = canEnterPictureInPicture
- && !r.isState(STOPPING, STOPPED, PAUSED);
- r.setDeferHidingClient(deferHidingClient);
- r.setVisible(false);
-
- switch (r.getState()) {
- case STOPPING:
- case STOPPED:
- if (r.attachedToProcess()) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Scheduling invisibility: " + r);
- mService.getLifecycleManager().scheduleTransaction(r.app.getThread(),
- r.appToken, WindowVisibilityItem.obtain(false /* showWindow */));
- }
-
- // Reset the flag indicating that an app can enter picture-in-picture once the
- // activity is hidden
- r.supportsEnterPipOnTaskSwitch = false;
- break;
-
- case INITIALIZING:
- case RESUMED:
- case PAUSING:
- case PAUSED:
- case STARTED:
- addToStopping(r, true /* scheduleIdle */,
- canEnterPictureInPicture /* idleDelayed */, "makeInvisible");
- break;
-
- default:
- break;
- }
- } catch (Exception e) {
- // Just skip on any failure; we'll make it visible when it next restarts.
- Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e);
- }
- }
-
private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
ActivityRecord r) {
if (r.fullscreen) {
@@ -2492,7 +2386,7 @@
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWindowManager.setWindowOpaque(waitingActivity.appToken, false);
+ mWindowManager.setWindowOpaqueLocked(waitingActivity.appToken, false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
@@ -2782,8 +2676,7 @@
!mLastNoHistoryActivity.finishing) {
if (DEBUG_STATES) Slog.d(TAG_STATES,
"no-history finish of " + mLastNoHistoryActivity + " on new resume");
- mLastNoHistoryActivity.finishActivityLocked(Activity.RESULT_CANCELED,
- null /* resultData */, "resume-no-history", false /* oomAdj */);
+ mLastNoHistoryActivity.finishIfPossible("resume-no-history", false /* oomAdj */);
mLastNoHistoryActivity = null;
}
@@ -2813,7 +2706,7 @@
// Launching this app's activity, make sure the app is no longer
// considered stopped.
try {
- AppGlobals.getPackageManager().setPackageStoppedState(
+ mService.getPackageManager().setPackageStoppedState(
next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
} catch (RemoteException e1) {
} catch (IllegalArgumentException e) {
@@ -3018,8 +2911,7 @@
// If any exception gets thrown, toss away this
// activity and try the next one.
Slog.w(TAG, "Exception thrown during resume of " + next, e);
- next.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "resume-exception", true /* oomAdj */);
+ next.finishIfPossible("resume-exception", true /* oomAdj */);
return true;
}
} else {
@@ -3322,6 +3214,7 @@
// to activities in the same chain will depend on what the end activity of the chain needs.
int replyChainEnd = -1;
boolean canMoveOptions = true;
+ int numTasksCreated = 0;
// We only do this for activities that are not the root of the task (since if we finish
// the root, we may no longer have the task!).
@@ -3386,6 +3279,7 @@
target.info, null /* intent */, null /* voiceSession */,
null /* voiceInteractor */, false /* toTop */);
targetTask.affinityIntent = target.intent;
+ numTasksCreated++;
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
+ " out to new task " + targetTask);
}
@@ -3414,6 +3308,7 @@
}
positionChildWindowContainerAtBottom(targetTask);
+ mStackSupervisor.mRecentTasks.add(targetTask);
replyChainEnd = -1;
} else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
// If the activity should just be removed -- either
@@ -3446,8 +3341,8 @@
}
if (DEBUG_TASKS) Slog.w(TAG_TASKS,
"resetTaskIntendedTask: calling finishActivity on " + p);
- if (p.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "reset-task", false /* oomAdj */) == FINISH_RESULT_REMOVED) {
+ if (p.finishIfPossible("reset-task", false /* oomAdj */)
+ == FINISH_RESULT_REMOVED) {
end--;
srcPos--;
}
@@ -3461,6 +3356,27 @@
}
}
+ // Create target stack for the newly created tasks if necessary
+ if (numTasksCreated > 0) {
+ ActivityDisplay display = getDisplay();
+ final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
+ if (singleTaskInstanceDisplay) {
+ display = mRootActivityContainer.getDefaultDisplay();
+ }
+
+ if (singleTaskInstanceDisplay || display.alwaysCreateStack(getWindowingMode(),
+ getActivityType())) {
+ for (int index = numTasksCreated - 1; index >= 0; index--) {
+ final TaskRecord targetTask = mTaskHistory.get(index);
+ final ActivityStack targetStack = display.getOrCreateStack(getWindowingMode(),
+ getActivityType(), false /* onTop */);
+ targetTask.reparent(targetStack, false /* toTop */,
+ REPARENT_LEAVE_STACK_IN_PLACE, false /* animate */,
+ true /* deferResume */, "resetTargetTask");
+ }
+ }
+ }
+
return topOptions;
}
@@ -3531,8 +3447,7 @@
if (p.finishing) {
continue;
}
- p.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "move-affinity", false /* oomAdj */);
+ p.finishIfPossible("move-affinity", false /* oomAdj */);
}
} else {
if (taskInsertionPoint < 0) {
@@ -3566,8 +3481,7 @@
if (targetNdx > 0) {
final ActivityRecord p = taskActivities.get(targetNdx - 1);
if (p.intent.getComponent().equals(target.intent.getComponent())) {
- p.finishActivityLocked(Activity.RESULT_CANCELED,
- null /* resultData */, "replace", false /* oomAdj */);
+ p.finishIfPossible("replace", false /* oomAdj */);
}
}
}
@@ -3579,6 +3493,16 @@
return taskInsertionPoint;
}
+ /**
+ * Reset the task by reparenting the activities that have same affinity to the task or
+ * reparenting the activities that have different affinityies out of the task, while these
+ * activities allow task reparenting.
+ *
+ * @param taskTop Top activity of the task might be reset.
+ * @param newActivity The activity that going to be started.
+ * @return The non-finishing top activity of the task after reset or the original task top
+ * activity if all activities within the task are finishing.
+ */
final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
ActivityRecord newActivity) {
final boolean forceReset =
@@ -3609,9 +3533,10 @@
int taskNdx = mTaskHistory.indexOf(task);
if (taskNdx >= 0) {
- do {
- taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
- } while (taskTop == null && taskNdx >= 0);
+ ActivityRecord newTop = mTaskHistory.get(taskNdx).getTopActivity();
+ if (newTop != null) {
+ taskTop = newTop;
+ }
}
if (topOptions != null) {
@@ -3627,49 +3552,6 @@
return taskTop;
}
- void sendActivityResultLocked(int callingUid, ActivityRecord r,
- String resultWho, int requestCode, int resultCode, Intent data) {
-
- if (callingUid > 0) {
- mService.mUgmInternal.grantUriPermissionFromIntent(callingUid, r.packageName,
- data, r.getUriPermissionsLocked(), r.mUserId);
- }
-
- if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r
- + " : who=" + resultWho + " req=" + requestCode
- + " res=" + resultCode + " data=" + data);
- if (mResumedActivity == r && r.attachedToProcess()) {
- try {
- ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
- list.add(new ResultInfo(resultWho, requestCode,
- resultCode, data));
- mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken,
- ActivityResultItem.obtain(list));
- return;
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending result to " + r, e);
- }
- }
-
- r.addResultLocked(null, resultWho, requestCode, resultCode, data);
- }
-
- /** Returns true if the task is one of the task finishing on-top of the top running task. */
- private boolean isATopFinishingTask(TaskRecord task) {
- for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
- final TaskRecord current = mTaskHistory.get(i);
- final ActivityRecord r = current.topRunningActivityLocked();
- if (r != null) {
- // We got a top running activity, so there isn't a top finishing task...
- return false;
- }
- if (current == task) {
- return true;
- }
- }
- return false;
- }
-
void adjustFocusedActivityStack(ActivityRecord r, String reason) {
if (!mRootActivityContainer.isTopDisplayFocusedStack(this) ||
((mResumedActivity != r) && (mResumedActivity != null))) {
@@ -3748,64 +3630,6 @@
return stack;
}
- final void stopActivityLocked(ActivityRecord r) {
- if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + r);
- if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
- || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
- if (!r.finishing) {
- if (!shouldSleepActivities()) {
- if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + r);
- if (r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "stop-no-history", false /* oomAdj */) != FINISH_RESULT_CANCELLED) {
- // {@link adjustFocusedActivityStack} must have been already called.
- r.resumeKeyDispatchingLocked();
- return;
- }
- } else {
- if (DEBUG_STATES) Slog.d(TAG_STATES, "Not finishing noHistory " + r
- + " on stop because we're just sleeping");
- }
- }
- }
-
- if (r.attachedToProcess()) {
- adjustFocusedActivityStack(r, "stopActivity");
- r.resumeKeyDispatchingLocked();
- try {
- r.stopped = false;
- if (DEBUG_STATES) Slog.v(TAG_STATES,
- "Moving to STOPPING: " + r + " (stop requested)");
- r.setState(STOPPING, "stopActivityLocked");
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Stopping visible=" + r.visible + " for " + r);
- if (!r.visible) {
- r.setVisible(false);
- }
- EventLogTags.writeAmStopActivity(
- r.mUserId, System.identityHashCode(r), r.shortComponentName);
- mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken,
- StopActivityItem.obtain(r.visible, r.configChangeFlags));
- if (shouldSleepOrShutDownActivities()) {
- r.setSleeping(true);
- }
- Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r);
- mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
- } catch (Exception e) {
- // Maybe just ignore exceptions here... if the process
- // has crashed, our death notification will clean things
- // up.
- Slog.w(TAG, "Exception thrown during pause", e);
- // Just in case, assume it to be stopped.
- r.stopped = true;
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + r);
- r.setState(STOPPED, "stopActivityLocked");
- if (r.deferRelaunchUntilPaused) {
- destroyActivityLocked(r, true, "stop-except");
- }
- }
- }
- }
-
/** Finish all activities that were started for result from the specified activity. */
final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -3815,8 +3639,7 @@
if (r.resultTo == self && r.requestCode == requestCode) {
if ((r.resultWho == null && resultWho == null) ||
(r.resultWho != null && r.resultWho.equals(resultWho))) {
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "request-sub", false /* oomAdj */);
+ r.finishIfPossible("request-sub", false /* oomAdj */);
}
}
}
@@ -3846,8 +3669,7 @@
int activityNdx = task.mActivities.indexOf(r);
getDisplay().mDisplayContent.prepareAppTransition(
TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, reason,
- false /* oomAdj */);
+ r.finishIfPossible(reason, false /* oomAdj */);
finishedTask = task;
// Also terminate any activities below it that aren't yet
// stopped, to avoid a situation where one will get
@@ -3868,8 +3690,7 @@
if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */, reason,
- false /* oomAdj */);
+ r.finishIfPossible(reason, false /* oomAdj */);
}
}
}
@@ -3885,8 +3706,7 @@
for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = tr.mActivities.get(activityNdx);
if (!r.finishing) {
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "finish-voice", false /* oomAdj */);
+ r.finishIfPossible("finish-voice", false /* oomAdj */);
didOne = true;
}
}
@@ -3924,8 +3744,7 @@
final ActivityRecord r = activities.get(activityNdx);
noActivitiesInStack = false;
Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r);
- r.finishCurrentActivityLocked(FINISH_IMMEDIATELY, false /* oomAdj */,
- "finishAllActivitiesImmediatelyLocked");
+ r.destroyIfPossible("finishAllActivitiesImmediatelyLocked");
}
}
if (noActivitiesInStack) {
@@ -4029,7 +3848,8 @@
final long origId = Binder.clearCallingIdentity();
for (int i = start; i > finishTo; i--) {
final ActivityRecord r = activities.get(i);
- r.finishActivityLocked(resultCode, resultData, "navigate-up", true /* oomAdj */);
+ r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */,
+ !PAUSE_IMMEDIATELY);
// Only return the supplied result for the first activity finished
resultCode = Activity.RESULT_CANCELED;
resultData = null;
@@ -4066,8 +3886,8 @@
} catch (RemoteException e) {
foundParentInTask = false;
}
- parent.finishActivityLocked(resultCode, resultData, "navigate-top",
- true /* oomAdj */);
+ parent.finishIfPossible(resultCode, resultData, "navigate-top",
+ true /* oomAdj */, !PAUSE_IMMEDIATELY);
}
}
Binder.restoreCallingIdentity(origId);
@@ -4079,7 +3899,7 @@
* an activity moves away from the stack.
*/
void onActivityRemovedFromStack(ActivityRecord r) {
- removeTimeoutsForActivityLocked(r);
+ removeTimeoutsForActivity(r);
if (mResumedActivity != null && mResumedActivity == r) {
setResumedActivity(null, "onActivityRemovedFromStack");
@@ -4095,132 +3915,64 @@
}
}
- /**
- * Perform the common clean-up of an activity record. This is called both
- * as part of destroyActivityLocked() (when destroying the client-side
- * representation) and cleaning things up as a result of its hosting
- * processing going away, in which case there is no remaining client-side
- * state to destroy so only the cleanup here is needed.
- *
- * Note: Call before #removeActivityFromHistoryLocked.
- */
- private void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) {
- onActivityRemovedFromStack(r);
-
- r.deferRelaunchUntilPaused = false;
- r.frozenBeforeDestroy = false;
-
- if (setState) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (cleaning up)");
- r.setState(DESTROYED, "cleanupActivityLocked");
- if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + r);
- r.app = null;
- }
-
- // Inform supervisor the activity has been removed.
- mStackSupervisor.cleanupActivity(r);
-
-
- // Remove any pending results.
- if (r.finishing && r.pendingResults != null) {
- for (WeakReference<PendingIntentRecord> apr : r.pendingResults) {
- PendingIntentRecord rec = apr.get();
- if (rec != null) {
- mService.mPendingIntentController.cancelIntentSender(rec, false);
- }
- }
- r.pendingResults = null;
- }
-
- if (cleanServices) {
- cleanUpActivityServicesLocked(r);
- }
-
- // Get rid of any pending idle timeouts.
- removeTimeoutsForActivityLocked(r);
- // Clean-up activities are no longer relaunching (e.g. app process died). Notify window
- // manager so it can update its bookkeeping.
- mWindowManager.notifyAppRelaunchesCleared(r.appToken);
- }
-
- private void removeTimeoutsForActivityLocked(ActivityRecord r) {
+ /// HANDLER INTERFACE BEGIN
+ void removeTimeoutsForActivity(ActivityRecord r) {
mStackSupervisor.removeTimeoutsForActivityLocked(r);
- mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
- mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
- mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
+ removePauseTimeoutForActivity(r);
+ removeStopTimeoutForActivity(r);
+ removeDestroyTimeoutForActivity(r);
r.finishLaunchTickingLocked();
}
- private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) {
- r.finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */);
- r.makeFinishingLocked();
- if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
- "Removing activity " + r + " from stack callers=" + Debug.getCallers(5));
-
- r.takeFromHistory();
- removeTimeoutsForActivityLocked(r);
- if (DEBUG_STATES) Slog.v(TAG_STATES,
- "Moving to DESTROYED: " + r + " (removed from history)");
- r.setState(DESTROYED, "removeActivityFromHistoryLocked");
- if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + r);
- r.app = null;
- r.removeWindowContainer();
- final TaskRecord task = r.getTaskRecord();
- final boolean lastActivity = task != null ? task.removeActivity(r) : false;
- // If we are removing the last activity in the task, not including task overlay activities,
- // then fall through into the block below to remove the entire task itself
- final boolean onlyHasTaskOverlays = task != null
- ? task.onlyHasTaskOverlayActivities(false /* excludingFinishing */) : false;
-
- if (lastActivity || onlyHasTaskOverlays) {
- if (DEBUG_STACK) {
- Slog.i(TAG_STACK,
- "removeActivityFromHistoryLocked: last activity removed from " + this
- + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
- }
-
- // The following block can be executed multiple times if there is more than one overlay.
- // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
- // of the task by id and exiting early if not found.
- if (onlyHasTaskOverlays) {
- // When destroying a task, tell the supervisor to remove it so that any activity it
- // has can be cleaned up correctly. This is currently the only place where we remove
- // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays
- // state into removeTask(), we just clear the task here before the other residual
- // work.
- // TODO: If the callers to removeTask() changes such that we have multiple places
- // where we are destroying the task, move this back into removeTask()
- mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
- !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
- }
-
- // We must keep the task around until all activities are destroyed. The following
- // statement will only execute once since overlays are also considered activities.
- if (lastActivity) {
- removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
- }
- }
- cleanUpActivityServicesLocked(r);
- r.removeUriPermissionsLocked();
- }
-
- /**
- * Perform clean-up of service connections in an activity record.
- */
- private void cleanUpActivityServicesLocked(ActivityRecord r) {
- if (r.mServiceConnectionsHolder == null) {
- return;
- }
- // Throw away any services that have been bound by this activity.
- r.mServiceConnectionsHolder.disconnectActivityFromServices();
- }
-
- final void scheduleDestroyActivities(WindowProcessController owner, String reason) {
- Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG);
+ void scheduleDestroyActivities(WindowProcessController owner, String reason) {
+ final Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG);
msg.obj = new ScheduleDestroyArgs(owner, reason);
mHandler.sendMessage(msg);
}
+ void scheduleDestroyTimeoutForActivity(ActivityRecord r) {
+ final Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
+ mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
+ }
+
+ void removeDestroyTimeoutForActivity(ActivityRecord r) {
+ mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
+ }
+
+ void scheduleStopTimeoutForActivity(ActivityRecord r) {
+ final Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r);
+ mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
+ }
+
+ void removeStopTimeoutForActivity(ActivityRecord r) {
+ mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+ }
+
+ /**
+ * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because
+ * this directly impacts the responsiveness seen by the user.
+ */
+ private void schedulePauseTimeoutForActivity(ActivityRecord r) {
+ final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG, r);
+ r.pauseTime = SystemClock.uptimeMillis();
+ mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
+ }
+
+ void removePauseTimeoutForActivity(ActivityRecord r) {
+ mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+ }
+
+ void scheduleLaunchTickForActivity(ActivityRecord r) {
+ final Message msg = mHandler.obtainMessage(LAUNCH_TICK_MSG, r);
+ mHandler.sendMessageDelayed(msg, LAUNCH_TICK);
+ }
+
+ void removeLaunchTickMessages() {
+ mHandler.removeMessages(LAUNCH_TICK_MSG);
+ }
+ /// HANDLER INTERFACE END
+
private void destroyActivitiesLocked(WindowProcessController owner, String reason) {
boolean lastIsOpaque = false;
boolean activityRemoved = false;
@@ -4245,7 +3997,7 @@
+ " in state " + r.getState()
+ " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
- if (destroyActivityLocked(r, true, reason)) {
+ if (r.destroyImmediately(true /* removeFromTask */, reason)) {
activityRemoved = true;
}
}
@@ -4256,16 +4008,6 @@
}
}
- final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) {
- if (r.isDestroyable()) {
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
- "Destroying " + r + " in state " + r.getState() + " resumed=" + mResumedActivity
- + " pausing=" + mPausingActivity + " for reason " + reason);
- return destroyActivityLocked(r, true, reason);
- }
- return false;
- }
-
final int releaseSomeActivitiesLocked(WindowProcessController app, ArraySet<TaskRecord> tasks,
String reason) {
// Iterate over tasks starting at the back (oldest) first.
@@ -4289,7 +4031,7 @@
if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity
+ " in state " + activity.getState() + " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
- destroyActivityLocked(activity, true, reason);
+ activity.destroyImmediately(true /* removeFromApp */, reason);
if (activities.get(actNdx) != activity) {
// Was removed from list, back up so we don't miss the next one.
actNdx--;
@@ -4311,142 +4053,6 @@
return numReleased;
}
- /**
- * Destroy the current CLIENT SIDE instance of an activity. This may be
- * called both when actually finishing an activity, or when performing
- * a configuration switch where we destroy the current client-side object
- * but then create a new client-side object for this same HistoryRecord.
- */
- final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
- if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH,
- "Removing activity from " + reason + ": token=" + r
- + ", app=" + (r.hasProcess() ? r.app.mName : "(null)"));
-
- if (r.isState(DESTROYING, DESTROYED)) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "activity " + r + " already destroying."
- + "skipping request with reason:" + reason);
- return false;
- }
-
- EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY,
- r.mUserId, System.identityHashCode(r),
- r.getTaskRecord().taskId, r.shortComponentName, reason);
-
- boolean removedFromHistory = false;
-
- cleanUpActivityLocked(r, false, false);
-
- final boolean hadApp = r.hasProcess();
-
- if (hadApp) {
- if (removeFromApp) {
- r.app.removeActivity(r);
- if (!r.app.hasActivities()) {
- mService.clearHeavyWeightProcessIfEquals(r.app);
- }
- if (!r.app.hasActivities()) {
- // Update any services we are bound to that might care about whether
- // their client may have activities.
- // No longer have activities, so update LRU list and oom adj.
- r.app.updateProcessInfo(true /* updateServiceConnectionActivities */,
- false /* activityChange */, true /* updateOomAdj */);
- }
- }
-
- boolean skipDestroy = false;
-
- try {
- if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
- mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken,
- DestroyActivityItem.obtain(r.finishing, r.configChangeFlags));
- } catch (Exception e) {
- // We can just ignore exceptions here... if the process
- // has crashed, our death notification will clean things
- // up.
- //Slog.w(TAG, "Exception thrown during finish", e);
- if (r.finishing) {
- removeActivityFromHistoryLocked(r, reason + " exceptionInScheduleDestroy");
- removedFromHistory = true;
- skipDestroy = true;
- }
- }
-
- r.nowVisible = false;
-
- // If the activity is finishing, we need to wait on removing it
- // from the list to give it a chance to do its cleanup. During
- // that time it may make calls back with its token so we need to
- // be able to find it on the list and so we don't want to remove
- // it from the list yet. Otherwise, we can just immediately put
- // it in the destroyed state since we are not removing it from the
- // list.
- if (r.finishing && !skipDestroy) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYING: " + r
- + " (destroy requested)");
- r.setState(DESTROYING,
- "destroyActivityLocked. finishing and not skipping destroy");
- Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
- mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
- } else {
- if (DEBUG_STATES) Slog.v(TAG_STATES,
- "Moving to DESTROYED: " + r + " (destroy skipped)");
- r.setState(DESTROYED,
- "destroyActivityLocked. not finishing or skipping destroy");
- if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r);
- r.app = null;
- }
- } else {
- // remove this record from the history.
- if (r.finishing) {
- removeActivityFromHistoryLocked(r, reason + " hadNoApp");
- removedFromHistory = true;
- } else {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (no app)");
- r.setState(DESTROYED, "destroyActivityLocked. not finishing and had no app");
- if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r);
- r.app = null;
- }
- }
-
- r.configChangeFlags = 0;
-
- if (!mLRUActivities.remove(r) && hadApp) {
- Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list");
- }
-
- return removedFromHistory;
- }
-
- final void activityDestroyedLocked(IBinder token, String reason) {
- final long origId = Binder.clearCallingIdentity();
- try {
- activityDestroyedLocked(ActivityRecord.forTokenLocked(token), reason);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- /**
- * This method is to only be called from the client via binder when the activity is destroyed
- * AND finished.
- */
- final void activityDestroyedLocked(ActivityRecord record, String reason) {
- if (record != null) {
- mHandler.removeMessages(DESTROY_TIMEOUT_MSG, record);
- }
-
- if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + record);
-
- if (isInStackLocked(record) != null) {
- if (record.isState(DESTROYING, DESTROYED)) {
- cleanUpActivityLocked(record, true, false);
- removeActivityFromHistoryLocked(record, reason);
- }
- }
-
- mRootActivityContainer.resumeFocusedStacksTopActivities();
- }
-
private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list,
WindowProcessController app, String listName) {
int i = list.size();
@@ -4459,7 +4065,7 @@
if (r.app == app) {
if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "---> REMOVING this entry!");
list.remove(i);
- removeTimeoutsForActivityLocked(r);
+ removeTimeoutsForActivity(r);
}
}
}
@@ -4554,9 +4160,9 @@
// other apps when user transfers focus to the restarted activity.
r.nowVisible = r.visible;
}
- cleanUpActivityLocked(r, true, true);
+ r.cleanUp(true /* cleanServices */, true /* setState */);
if (remove) {
- removeActivityFromHistoryLocked(r, "appDied");
+ r.removeFromHistory("appDied");
}
}
}
@@ -4747,16 +4353,6 @@
return true;
}
- static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
- final Uri data = r.intent.getData();
- final String strData = data != null ? data.toSafeString() : null;
-
- EventLog.writeEvent(tag,
- r.mUserId, System.identityHashCode(r), task.taskId,
- r.shortComponentName, r.intent.getAction(),
- r.intent.getType(), strData, r.intent.getFlags());
- }
-
/**
* Ensures all visible activities at or below the input activity have the right configuration.
*/
@@ -4890,7 +4486,7 @@
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
- r.finishActivityLocked(Activity.RESULT_CANCELED, null, "close-sys", true);
+ r.finishIfPossible("close-sys", true /* oomAdj */);
}
}
}
@@ -4934,7 +4530,7 @@
didSomething = true;
Slog.i(TAG, " Force finishing activity " + r);
lastTask = r.getTaskRecord();
- r.finishActivityLocked(Activity.RESULT_CANCELED, null, "force-stop", true);
+ r.finishIfPossible("force-stop", true);
}
}
}
@@ -4988,8 +4584,7 @@
final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
int activityTop = activities.size() - 1;
if (activityTop >= 0) {
- activities.get(activityTop).finishActivityLocked(Activity.RESULT_CANCELED, null,
- "unhandled-back", true);
+ activities.get(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
}
}
}
@@ -5025,8 +4620,7 @@
r.app = null;
getDisplay().mDisplayContent.prepareAppTransition(
TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
- r.finishCurrentActivityLocked(FINISH_IMMEDIATELY, false /* oomAdj */,
- "handleAppCrashedLocked");
+ r.destroyIfPossible("handleAppCrashedLocked");
}
}
}
@@ -5167,7 +4761,7 @@
EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId());
}
- removeActivitiesFromLRUListLocked(task);
+ removeActivitiesFromLRUList(task);
updateTaskMovement(task, true);
if (mode == REMOVE_TASK_MODE_DESTROYING) {
@@ -5350,7 +4944,7 @@
// If the activity was previously pausing, then ensure we transfer that as well
if (setPause) {
mPausingActivity = r;
- schedulePauseTimeout(r);
+ schedulePauseTimeoutForActivity(r);
}
// Move the stack in which we are placing the activity to the front.
moveToFront(reason);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1c56a10..a06b9ce 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -42,6 +42,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.graphics.Rect.copyOrNull;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -250,7 +251,7 @@
RecentTasks mRecentTasks;
/** Helper class to abstract out logic for fetching the set of currently running tasks */
- RunningTasks mRunningTasks;
+ private RunningTasks mRunningTasks;
final ActivityStackSupervisorHandler mHandler;
final Looper mLooper;
@@ -444,7 +445,7 @@
}
mInitialized = true;
- mRunningTasks = createRunningTasks();
+ setRunningTasks(new RunningTasks());
mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
mHandler.getLooper());
@@ -485,13 +486,20 @@
}
void setRecentTasks(RecentTasks recentTasks) {
+ if (mRecentTasks != null) {
+ mRecentTasks.unregisterCallback(this);
+ }
mRecentTasks = recentTasks;
mRecentTasks.registerCallback(this);
}
@VisibleForTesting
- RunningTasks createRunningTasks() {
- return new RunningTasks();
+ void setRunningTasks(RunningTasks runningTasks) {
+ mRunningTasks = runningTasks;
+ }
+
+ RunningTasks getRunningTasks() {
+ return mRunningTasks;
}
/**
@@ -890,8 +898,7 @@
Slog.e(TAG, "Second failure launching "
+ r.intent.getComponent().flattenToShortString() + ", giving up", e);
proc.appDied();
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "2nd-crash", false /* oomAdj */);
+ r.finishIfPossible("2nd-crash", false /* oomAdj */);
return false;
}
@@ -1022,9 +1029,8 @@
if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
|| actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
if (resultRecord != null) {
- resultStack.sendActivityResultLocked(-1,
- resultRecord, resultWho, requestCode,
- Activity.RESULT_CANCELED, null);
+ resultRecord.sendResult(INVALID_UID, resultWho, requestCode,
+ Activity.RESULT_CANCELED, null /* data */);
}
final String msg;
if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
@@ -1340,10 +1346,10 @@
final ActivityStack stack = r.getActivityStack();
if (stack != null) {
if (r.finishing) {
- r.finishCurrentActivityLocked(ActivityRecord.FINISH_IMMEDIATELY,
- false /* oomAdj */, "activityIdleInternalLocked");
+ // TODO(b/137329632): Wait for idle of the right activity, not just any.
+ r.destroyIfPossible("activityIdleInternalLocked");
} else {
- stack.stopActivityLocked(r);
+ r.stopIfPossible();
}
}
}
@@ -1354,7 +1360,7 @@
r = finishes.get(i);
final ActivityStack stack = r.getActivityStack();
if (stack != null) {
- activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
+ activityRemoved |= r.destroyImmediately(true /* removeFromApp */, "finish-idle");
}
}
@@ -2735,7 +2741,7 @@
mWindowManager.deferSurfaceLayout();
try {
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mWindowManager.setDockedStackCreateState(
+ mWindowManager.setDockedStackCreateStateLocked(
activityOptions.getSplitScreenCreateMode(), null /* initialBounds */);
// Defer updating the stack in which recents is until the app transition is done, to
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9ed93c4..48bc963 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -55,6 +55,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.INVALID_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -752,8 +753,8 @@
if (err != START_SUCCESS) {
if (resultRecord != null) {
- resultStack.sendActivityResultLocked(
- -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null);
+ resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED,
+ null /* data */);
}
SafeActivityOptions.abort(options);
return err;
@@ -817,8 +818,8 @@
if (abort) {
if (resultRecord != null) {
- resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
- RESULT_CANCELED, null);
+ resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED,
+ null /* data */);
}
// We pretend to the caller that it was really started, but
// they will just get a cancel result.
@@ -1426,8 +1427,7 @@
// performing operations without a window container.
final ActivityStack stack = mStartActivity.getActivityStack();
if (stack != null) {
- mStartActivity.finishActivityLocked(RESULT_CANCELED,
- null /* intentResultData */, "startActivity", true /* oomAdj */);
+ mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
}
// Stack should also be detached from display and be removed if it's empty.
@@ -1451,18 +1451,17 @@
* TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere.
*/
private boolean handleBackgroundActivityAbort(ActivityRecord r) {
- // TODO(b/131747138): Remove toast and refactor related code in Q release.
- boolean abort = !mService.isBackgroundActivityStartsEnabled();
+ // TODO(b/131747138): Remove toast and refactor related code in R release.
+ final boolean abort = !mService.isBackgroundActivityStartsEnabled();
if (!abort) {
return false;
}
- ActivityRecord resultRecord = r.resultTo;
- String resultWho = r.resultWho;
+ final ActivityRecord resultRecord = r.resultTo;
+ final String resultWho = r.resultWho;
int requestCode = r.requestCode;
if (resultRecord != null) {
- ActivityStack resultStack = resultRecord.getActivityStack();
- resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
- RESULT_CANCELED, null);
+ resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED,
+ null /* data */);
}
// We pretend to the caller that it was really started to make it backward compatible, but
// they will just get a cancel result.
@@ -1627,12 +1626,9 @@
}
if (mStartActivity.packageName == null) {
- final ActivityStack sourceStack = mStartActivity.resultTo != null
- ? mStartActivity.resultTo.getActivityStack() : null;
- if (sourceStack != null) {
- sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
- mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
- null /* data */);
+ if (mStartActivity.resultTo != null) {
+ mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+ mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
}
ActivityOptions.abort(mOptions);
return START_CLASS_NOT_FOUND;
@@ -1709,8 +1705,8 @@
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
mStartActivity.getTaskRecord().taskId);
}
- ActivityStack.logStartActivity(
- EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTaskRecord());
+ mStartActivity.logStartActivity(
+ EventLogTags.AM_CREATE_ACTIVITY, mStartActivity.getTaskRecord());
mTargetStack.mLastPausedActivity = null;
mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
@@ -1928,17 +1924,14 @@
}
private void sendNewTaskResultRequestIfNeeded() {
- final ActivityStack sourceStack = mStartActivity.resultTo != null
- ? mStartActivity.resultTo.getActivityStack() : null;
- if (sourceStack != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new task...
// yet the caller has requested a result back. Well, that is pretty messed up,
// so instead immediately send back a cancel and let the new task continue launched
// as normal without a dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
- sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
- mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
- null /* data */);
+ mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+ mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
mStartActivity.resultTo = null;
}
}
@@ -2363,7 +2356,7 @@
return;
}
- ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTaskRecord());
+ activity.logStartActivity(AM_NEW_INTENT, activity.getTaskRecord());
activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
mStartActivity.launchedFromPackage);
mIntentDelivered = true;
@@ -2430,7 +2423,7 @@
ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags);
mKeepCurTransition = true;
if (top != null) {
- ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTaskRecord());
+ mStartActivity.logStartActivity(AM_NEW_INTENT, top.getTaskRecord());
deliverNewIntent(top);
// For paranoia, make sure we have correctly resumed the top activity.
mTargetStack.mLastPausedActivity = null;
@@ -2449,7 +2442,7 @@
final TaskRecord task = top.getTaskRecord();
task.moveActivityToFrontLocked(top);
top.updateOptionsLocked(mOptions);
- ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
+ mStartActivity.logStartActivity(AM_NEW_INTENT, task);
deliverNewIntent(top);
mTargetStack.mLastPausedActivity = null;
if (mDoResume) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a3ab27e..3c5947a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -43,11 +43,9 @@
import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
-import static android.content.pm.PackageManager.FEATURE_PC;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
-import static android.os.Build.VERSION_CODES.N;
import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL;
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
import static android.os.FactoryTest.FACTORY_TEST_OFF;
@@ -87,6 +85,7 @@
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
@@ -211,7 +210,7 @@
import android.sysprop.DisplayProperties;
import android.telecom.TelecomManager;
import android.text.TextUtils;
-import android.text.format.Time;
+import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
@@ -361,7 +360,7 @@
/* Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
/**
- * It is the same instance as {@link mGlobalLock}, just declared as a type that the
+ * It is the same instance as {@link #mGlobalLock}, just declared as a type that the
* locked-region-code-injection does't recognize it. It is used to skip wrapping priority
* booster for places that are already in the scope of another booster (e.g. computing oom-adj).
*
@@ -730,7 +729,6 @@
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
final boolean forceResizable = Settings.Global.getInt(
resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
- final boolean isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
// Transfer any global setting for forcing RTL layout, into a System Property
DisplayProperties.debug_force_rtl(forceRtl);
@@ -761,10 +759,6 @@
mSupportsPictureInPicture = false;
mSupportsMultiDisplay = false;
}
- mWindowManager.setForceResizableTasks(mForceResizableActivities);
- mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture);
- mWindowManager.setSupportsFreeformWindowManagement(mSupportsFreeformWindowManagement);
- mWindowManager.setIsPc(isPc);
mWindowManager.mRoot.onSettingsRetrieved();
// This happens before any activities are started, so we can change global configuration
// in-place.
@@ -821,8 +815,7 @@
new TaskChangeNotificationController(mGlobalLock, mStackSupervisor, mH);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mH);
mActivityStartController = new ActivityStartController(this);
- mRecentTasks = createRecentTasks();
- mStackSupervisor.setRecentTasks(mRecentTasks);
+ setRecentTasks(new RecentTasks(this, mStackSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mStackSupervisor.getKeyguardController();
}
@@ -890,8 +883,10 @@
return mode == AppOpsManager.MODE_ALLOWED;
}
- protected RecentTasks createRecentTasks() {
- return new RecentTasks(this, mStackSupervisor);
+ @VisibleForTesting
+ protected void setRecentTasks(RecentTasks recentTasks) {
+ mRecentTasks = recentTasks;
+ mStackSupervisor.setRecentTasks(recentTasks);
}
RecentTasks getRecentTasks() {
@@ -1619,8 +1614,8 @@
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
} else {
- r.finishActivityLocked(resultCode, resultData, "app-request",
- true /* oomAdj */);
+ r.finishIfPossible(resultCode, resultData, "app-request",
+ true /* oomAdj */, !PAUSE_IMMEDIATELY);
res = r.finishing;
if (!res) {
Slog.i(TAG, "Failed to finish by app-request");
@@ -1758,9 +1753,14 @@
public final void activityDestroyed(IBinder token) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
synchronized (mGlobalLock) {
- ActivityStack stack = ActivityRecord.getStackLocked(token);
- if (stack != null) {
- stack.activityDestroyedLocked(token, "activityDestroyed");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
+ if (activity != null) {
+ activity.destroyed("activityDestroyed");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
}
@@ -1954,12 +1954,7 @@
if (r == null) {
return false;
}
- final boolean translucentChanged = r.changeWindowTranslucency(true);
- if (translucentChanged) {
- mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
- }
- mWindowManager.setAppFullscreen(token, true);
- return translucentChanged;
+ return r.setOccludesParent(true);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1982,13 +1977,7 @@
ActivityRecord under = task.mActivities.get(index - 1);
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
- final boolean translucentChanged = r.changeWindowTranslucency(false);
- if (translucentChanged) {
- r.getActivityStack().convertActivityToTranslucent(r);
- }
- mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
- mWindowManager.setAppFullscreen(token, false);
- return translucentChanged;
+ return r.setOccludesParent(false);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -2581,7 +2570,7 @@
+ taskId + " to stack " + stackId);
}
if (stack.inSplitScreenPrimaryWindowingMode()) {
- mWindowManager.setDockedStackCreateState(
+ mWindowManager.setDockedStackCreateStateLocked(
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
}
task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
@@ -2700,7 +2689,7 @@
+ " non-standard task " + taskId + " to split-screen windowing mode");
}
- mWindowManager.setDockedStackCreateState(createMode, initialBounds);
+ mWindowManager.setDockedStackCreateStateLocked(createMode, initialBounds);
final int windowingMode = task.getWindowingMode();
final ActivityStack stack = task.getStack();
if (toTop) {
@@ -2802,7 +2791,7 @@
try {
synchronized (mGlobalLock) {
// Cancel the recents animation synchronously (do not hold the WM lock)
- mWindowManager.cancelRecentsAnimationSynchronously(restoreHomeStackPosition
+ mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition
? REORDER_MOVE_TO_ORIGINAL_POSITION
: REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation/uid=" + callingUid);
}
@@ -3252,7 +3241,7 @@
if (r == null) {
return false;
}
- return r.getActivityStack().safelyDestroyActivityLocked(r, "app-req");
+ return r.safelyDestroy("app-req");
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -5878,9 +5867,9 @@
tracesFile = File.createTempFile("app_slow", null, tracesDir);
StringBuilder sb = new StringBuilder();
- Time tobj = new Time();
- tobj.set(System.currentTimeMillis());
- sb.append(tobj.format("%Y-%m-%d %H:%M:%S"));
+ String timeString =
+ TimeMigrationUtils.formatMillisWithFixedFormat(System.currentTimeMillis());
+ sb.append(timeString);
sb.append(": ");
TimeUtils.formatDuration(SystemClock.uptimeMillis()-startTime, sb);
sb.append(" since ");
@@ -6500,8 +6489,7 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
if (r != null && r.getActivityStack() != null) {
- r.getActivityStack().sendActivityResultLocked(callingUid, r, resultWho,
- requestCode, resultCode, data);
+ r.sendResult(callingUid, resultWho, requestCode, resultCode, data);
}
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 7fde6de..7e0d9a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -92,6 +92,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
@@ -153,8 +154,11 @@
final ComponentName mActivityComponent;
final boolean mVoiceInteraction;
- /** @see WindowContainer#fillsParent() */
- private boolean mFillsParent;
+ /**
+ * The activity is opaque and fills the entire space of this task.
+ * @see WindowContainer#fillsParent()
+ */
+ private boolean mOccludesParent;
boolean mShowForAllUsers;
int mTargetSdk;
@@ -373,7 +377,7 @@
appToken = token;
mActivityComponent = activityComponent;
mVoiceInteraction = voiceInteraction;
- mFillsParent = fillsParent;
+ mOccludesParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(appToken.asBinder());
}
@@ -2354,11 +2358,29 @@
@Override
boolean fillsParent() {
- return mFillsParent;
+ return occludesParent();
}
- void setFillsParent(boolean fillsParent) {
- mFillsParent = fillsParent;
+ /** Returns true if this activity is opaque and fills the entire space of this task. */
+ boolean occludesParent() {
+ return mOccludesParent;
+ }
+
+ boolean setOccludesParent(boolean occludesParent) {
+ final boolean changed = occludesParent != mOccludesParent;
+ mOccludesParent = occludesParent;
+ setMainWindowOpaque(occludesParent);
+ mWmService.mWindowPlacerLocked.requestTraversal();
+ return changed;
+ }
+
+ void setMainWindowOpaque(boolean isOpaque) {
+ final WindowState win = findMainWindow();
+ if (win == null) {
+ return;
+ }
+ isOpaque = isOpaque & !PixelFormat.formatHasAlpha(win.getAttrs().format);
+ win.mWinAnimator.setOpaqueLocked(isOpaque);
}
boolean containsDismissKeyguardWindow() {
@@ -3035,7 +3057,7 @@
}
pw.println(prefix + "component=" + mActivityComponent.flattenToShortString());
pw.print(prefix); pw.print("task="); pw.println(getTask());
- pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent);
+ pw.print(prefix); pw.print(" mOccludesParent="); pw.print(mOccludesParent);
pw.print(" mOrientation="); pw.println(mOrientation);
pw.println(prefix + "hiddenRequested=" + hiddenRequested + " mClientHidden=" + mClientHidden
+ ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
@@ -3152,7 +3174,7 @@
if (mThumbnail != null){
mThumbnail.writeToProto(proto, THUMBNAIL);
}
- proto.write(FILLS_PARENT, mFillsParent);
+ proto.write(FILLS_PARENT, mOccludesParent);
proto.write(APP_STOPPED, mAppStopped);
proto.write(HIDDEN_REQUESTED, hiddenRequested);
proto.write(CLIENT_HIDDEN, mClientHidden);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 282ed42..410cc94 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -295,7 +295,8 @@
false /* forceRelayout */);
}
- private void setUserRotation(int userRotationMode, int userRotation) {
+ @VisibleForTesting
+ void setUserRotation(int userRotationMode, int userRotation) {
if (isDefaultDisplay) {
// We'll be notified via settings listener, so we don't need to update internal values.
final ContentResolver res = mContext.getContentResolver();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 207e8ef..8507918 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -275,14 +275,14 @@
// This display used to be in freeform, but we don't support freeform anymore, so fall
// back to fullscreen.
if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
- && !mService.mSupportsFreeformWindowManagement) {
+ && !mService.mAtmService.mSupportsFreeformWindowManagement) {
return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
}
// No record is present so use default windowing mode policy.
if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays
&& displayId != Display.DEFAULT_DISPLAY;
- windowingMode = mService.mSupportsFreeformWindowManagement
+ windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
&& (mService.mIsPc || forceDesktopMode)
? WindowConfiguration.WINDOWING_MODE_FREEFORM
: WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index b1bc2197..120ce3e 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -355,7 +355,9 @@
void getTouchRegion(Rect outRegion) {
outRegion.set(mTouchRegion);
- outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
+ if (mWindow != null) {
+ outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
+ }
}
private void resetDragResizingChangeReported() {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 4f0332c..1a8944a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -149,8 +149,8 @@
// traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
// things (e.g. the measure can be done earlier). The actual stop will be performed when
// it reports idle.
- targetStack.addToStopping(targetActivity, true /* scheduleIdle */,
- true /* idleDelayed */, "preloadRecents");
+ targetActivity.addToStopping(true /* scheduleIdle */, true /* idleDelayed */,
+ "preloadRecents");
}
}
@@ -241,7 +241,7 @@
// Fetch all the surface controls and pass them to the client to get the animation
// started. Cancel any existing recents animation running synchronously (do not hold the
// WM lock)
- mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION,
+ mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
"startRecentsActivity");
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultDisplay.mDisplayId,
@@ -396,12 +396,8 @@
@Override
public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
- boolean runSychronously, boolean sendUserLeaveHint) {
- if (runSychronously) {
- finishAnimation(reorderMode, sendUserLeaveHint);
- } else {
- mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint));
- }
+ boolean sendUserLeaveHint) {
+ finishAnimation(reorderMode, sendUserLeaveHint);
}
@Override
@@ -435,8 +431,7 @@
} else {
// Just cancel directly to unleash from launcher when the next launching task is the
// current top task.
- mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE,
- "stackOrderChanged");
+ mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged");
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 724a72e..8752f37 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -168,8 +168,7 @@
public interface RecentsAnimationCallbacks {
/** Callback when recents animation is finished. */
- void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously,
- boolean sendUserLeaveHint);
+ void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
}
private final IRecentsAnimationController mController =
@@ -221,8 +220,7 @@
// prior to calling the callback
mCallbacks.onAnimationFinished(moveHomeToTop
? REORDER_MOVE_TO_TOP
- : REORDER_MOVE_TO_ORIGINAL_POSITION,
- true /* runSynchronously */, sendUserLeaveHint);
+ : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint);
mDisplayContent.mBoundsAnimationController.setAnimationType(FADE_IN);
} finally {
Binder.restoreCallingIdentity(token);
@@ -498,21 +496,15 @@
}
void cancelAnimation(@ReorderMode int reorderMode, String reason) {
- cancelAnimation(reorderMode, false /* runSynchronously */, false /*screenshot */, reason);
- }
-
- void cancelAnimationSynchronously(@ReorderMode int reorderMode, String reason) {
- cancelAnimation(reorderMode, true /* runSynchronously */, false /* screenshot */, reason);
+ cancelAnimation(reorderMode, false /*screenshot */, reason);
}
void cancelAnimationWithScreenshot(boolean screenshot) {
- cancelAnimation(REORDER_KEEP_IN_PLACE, true /* sync */, screenshot, "stackOrderChanged");
+ cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "stackOrderChanged");
}
- private void cancelAnimation(@ReorderMode int reorderMode, boolean runSynchronously,
- boolean screenshot, String reason) {
- if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason
- + " runSynchronously=" + runSynchronously);
+ private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) {
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason);
synchronized (mService.getWindowManagerLock()) {
if (mCanceled) {
// We've already canceled the animation
@@ -525,16 +517,14 @@
// Screen shot previous task when next task starts transition and notify the runner.
// We will actually finish the animation once the runner calls cleanUpScreenshot().
final Task task = mPendingAnimations.get(0).mTask;
- final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode,
- runSynchronously);
+ final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode);
try {
mRunner.onAnimationCanceled(taskSnapshot);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to cancel recents animation", e);
}
if (taskSnapshot == null) {
- mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
- false /* sendUserLeaveHint */);
+ mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
}
} else {
// Otherwise, notify the runner and clean up the animation immediately
@@ -545,8 +535,7 @@
} catch (RemoteException e) {
Slog.e(TAG, "Failed to cancel recents animation", e);
}
- mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
- false /* sendUserLeaveHint */);
+ mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
}
}
}
@@ -592,8 +581,7 @@
return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot;
}
- TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode,
- boolean runSynchronously) {
+ TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode) {
final TaskSnapshotController snapshotController = mService.mTaskSnapshotController;
final ArraySet<Task> tasks = Sets.newArraySet(task);
snapshotController.snapshotTasks(tasks);
@@ -613,8 +601,7 @@
if (DEBUG_RECENTS_ANIMATIONS) {
Slog.d(TAG, "mRecentScreenshotAnimator finish");
}
- mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
- false /* sendUserLeaveHint */);
+ mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
}, mService);
mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator);
return taskSnapshot;
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 66d42db..3401de6 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -227,15 +227,10 @@
mStackSupervisor.mRootActivityContainer = this;
}
- @VisibleForTesting
- void setWindowContainer(RootWindowContainer container) {
- mRootWindowContainer = container;
- mRootWindowContainer.setRootActivityContainer(this);
- }
-
void setWindowManager(WindowManagerService wm) {
mWindowManager = wm;
- setWindowContainer(mWindowManager.mRoot);
+ mRootWindowContainer = mWindowManager.mRoot;
+ mRootWindowContainer.setRootActivityContainer(this);
mDisplayManager = mService.mContext.getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(this, mService.mUiHandler);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -2266,7 +2261,7 @@
@WindowConfiguration.ActivityType int ignoreActivityType,
@WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid,
boolean allowed) {
- mStackSupervisor.mRunningTasks.getTasks(maxNum, list, ignoreActivityType,
+ mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType,
ignoreWindowingMode, mActivityDisplays, callingUid, allowed);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fd86faa..3a2eb57 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -380,7 +380,7 @@
boolean isResizeable() {
return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture
- || mWmService.mForceResizableTasks;
+ || mWmService.mAtmService.mForceResizableActivities;
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index f51d4c4..1c015d0 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -159,11 +159,23 @@
}
// STEP 2: Resolve launch windowing mode.
- // STEP 2.1: Determine if any parameter has specified initial bounds. That might be the
- // launch bounds from activity options, or size/gravity passed in layout. It also treats the
- // launch windowing mode in options as a suggestion for future resolution.
+ // STEP 2.1: Determine if any parameter can specify initial bounds/windowing mode. That
+ // might be the launch bounds from activity options, or size/gravity passed in layout. It
+ // also treats the launch windowing mode in options and source activity windowing mode in
+ // some cases as a suggestion for future resolution.
int launchMode = options != null ? options.getLaunchWindowingMode()
: WINDOWING_MODE_UNDEFINED;
+ // In some cases we want to use the source's windowing mode as the default value, e.g. when
+ // source is a freeform window in a fullscreen display launching an activity on the same
+ // display.
+ if (launchMode == WINDOWING_MODE_UNDEFINED
+ && canInheritWindowingModeFromSource(display, source)) {
+ launchMode = source.getWindowingMode();
+ if (DEBUG) {
+ appendLog("inherit-from-source="
+ + WindowConfiguration.windowingModeToString(launchMode));
+ }
+ }
// hasInitialBounds is set if either activity options or layout has specified bounds. If
// that's set we'll skip some adjustments later to avoid overriding the initial bounds.
boolean hasInitialBounds = false;
@@ -342,6 +354,31 @@
? displayId : DEFAULT_DISPLAY;
}
+ private boolean canInheritWindowingModeFromSource(@NonNull ActivityDisplay display,
+ @Nullable ActivityRecord source) {
+ if (source == null) {
+ return false;
+ }
+
+ // There is not really any strong reason to tie the launching windowing mode and the source
+ // on freeform displays. The launching windowing mode is more tied to the content of the new
+ // activities.
+ if (display.inFreeformWindowingMode()) {
+ return false;
+ }
+
+ final int sourceWindowingMode = source.getWindowingMode();
+ if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN
+ && sourceWindowingMode != WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+
+ // Only inherit windowing mode if both source and target activities are on the same display.
+ // Otherwise we may have unintended freeform windows showing up if an activity in freeform
+ // window launches an activity on a fullscreen display by specifying display ID.
+ return display.mDisplayId == source.getDisplayId();
+ }
+
private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display, int launchMode) {
return mSupervisor.mService.mSupportsFreeformWindowManagement
&& (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM);
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 882f411..ede2f56 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1419,8 +1419,9 @@
mActivities.remove(activityNdx);
--activityNdx;
--numActivities;
- } else if (r.finishActivityLocked(Activity.RESULT_CANCELED, null,
- reason, false /* oomAdj */, pauseImmediately) == FINISH_RESULT_REMOVED) {
+ } else if (r.finishIfPossible(Activity.RESULT_CANCELED,
+ null /* resultData */, reason, false /* oomAdj */, pauseImmediately)
+ == FINISH_RESULT_REMOVED) {
--activityNdx;
--numActivities;
}
@@ -1474,8 +1475,8 @@
if (opts != null) {
ret.updateOptionsLocked(opts);
}
- if (r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "clear-task-stack", false /* oomAdj */) == FINISH_RESULT_REMOVED) {
+ if (r.finishIfPossible("clear-task-stack", false /* oomAdj */)
+ == FINISH_RESULT_REMOVED) {
--activityNdx;
--numActivities;
}
@@ -1488,8 +1489,7 @@
&& (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0
&& !ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) {
if (!ret.finishing) {
- ret.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "clear-task-top", false /* oomAdj */);
+ ret.finishIfPossible("clear-task-top", false /* oomAdj */);
return null;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 79367a0..cc2112e 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1666,7 +1666,7 @@
* default bounds.
*/
Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
- if (!mWmService.mSupportsPictureInPicture) {
+ if (!mWmService.mAtmService.mSupportsPictureInPicture) {
return null;
}
@@ -1762,7 +1762,7 @@
* Sets the current picture-in-picture aspect ratio.
*/
void setPictureInPictureAspectRatio(float aspectRatio) {
- if (!mWmService.mSupportsPictureInPicture) {
+ if (!mWmService.mAtmService.mSupportsPictureInPicture) {
return;
}
@@ -1792,7 +1792,7 @@
* Sets the current picture-in-picture actions.
*/
void setPictureInPictureActions(List<RemoteAction> actions) {
- if (!mWmService.mSupportsPictureInPicture) {
+ if (!mWmService.mAtmService.mSupportsPictureInPicture) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bbef261..29d232f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -767,11 +767,11 @@
*/
void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,
@Nullable ConfigurationContainer requestingContainer) {
- final boolean changed = mOrientation != orientation;
- mOrientation = orientation;
- if (!changed) {
+ if (mOrientation == orientation) {
return;
}
+
+ mOrientation = orientation;
final WindowContainer parent = getParent();
if (parent != null) {
onDescendantOrientationChanged(freezeDisplayToken, requestingContainer);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 86faad0..3874fdc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -28,6 +28,7 @@
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.content.pm.PackageManager.FEATURE_PC;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.myPid;
@@ -591,9 +592,6 @@
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
- boolean mForceResizableTasks;
- boolean mSupportsPictureInPicture;
- boolean mSupportsFreeformWindowManagement;
boolean mIsPc;
/**
* Flag that indicates that desktop mode is forced for public secondary screens.
@@ -819,7 +817,7 @@
int mTransactionSequence;
final WindowAnimator mAnimator;
- final SurfaceAnimationRunner mSurfaceAnimationRunner;
+ SurfaceAnimationRunner mSurfaceAnimationRunner;
/**
* Keeps track of which animations got transferred to which animators. Entries will get cleaned
@@ -957,6 +955,9 @@
final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
+ @VisibleForTesting
+ final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener;
+
interface AppFreezeListener {
void onAppFreezeTimeout();
}
@@ -1010,6 +1011,7 @@
mGlobalLock = atm.getGlobalLock();
mAtmService = atm;
mContext = context;
+ mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mAllowBootMessages = showBootMsgs;
mOnlyCore = onlyCore;
mLimitedAlphaCompositing = context.getResources().getBoolean(
@@ -1159,26 +1161,28 @@
mSystemGestureExcludedByPreQStickyImmersive =
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- new HandlerExecutor(mH), properties -> {
- synchronized (mGlobalLock) {
- final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
- properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
- final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
- if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky
- || mSystemGestureExclusionLimitDp != exclusionLimitDp) {
- mSystemGestureExclusionLimitDp = exclusionLimitDp;
- mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky;
- mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit);
- }
- mSystemGestureExclusionLogDebounceTimeoutMillis =
- DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS, 0);
- }
- });
+ mPropertiesChangedListener = properties -> {
+ synchronized (mGlobalLock) {
+ final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
+ properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
+ final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
+ if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky
+ || mSystemGestureExclusionLimitDp != exclusionLimitDp) {
+ mSystemGestureExclusionLimitDp = exclusionLimitDp;
+ mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky;
+ mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit);
+ }
+
+ mSystemGestureExclusionLogDebounceTimeoutMillis =
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS, 0);
+ }
+ };
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ new HandlerExecutor(mH), mPropertiesChangedListener);
LocalServices.addService(WindowManagerInternal.class, new LocalService());
}
@@ -2595,7 +2599,7 @@
mRecentsAnimationController = controller;
}
- public RecentsAnimationController getRecentsAnimationController() {
+ RecentsAnimationController getRecentsAnimationController() {
return mRecentsAnimationController;
}
@@ -2603,74 +2607,37 @@
* @return Whether the next recents animation can continue to start. Called from
* {@link RecentsAnimation#startRecentsActivity}.
*/
- public boolean canStartRecentsAnimation() {
- synchronized (mGlobalLock) {
- // TODO(multi-display): currently only default display support recent activity
- if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) {
- return false;
- }
- return true;
+ boolean canStartRecentsAnimation() {
+ // TODO(multi-display): currently only default display support recent activity
+ if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) {
+ return false;
}
+ return true;
}
- /**
- * Cancels any running recents animation. The caller should NOT hold the WM lock while calling
- * this method, as it will call back into AM and may cause a deadlock. Any locking will be done
- * in the animation controller itself.
- */
- public void cancelRecentsAnimationSynchronously(
+ void cancelRecentsAnimation(
@RecentsAnimationController.ReorderMode int reorderMode, String reason) {
if (mRecentsAnimationController != null) {
// This call will call through to cleanupAnimation() below after the animation is
// canceled
- mRecentsAnimationController.cancelAnimationSynchronously(reorderMode, reason);
+ mRecentsAnimationController.cancelAnimation(reorderMode, reason);
}
}
- public void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
- synchronized (mGlobalLock) {
- if (mRecentsAnimationController != null) {
- final RecentsAnimationController controller = mRecentsAnimationController;
- mRecentsAnimationController = null;
- controller.cleanupAnimation(reorderMode);
- // TODO(mult-display): currently only default display support recents animation.
- getDefaultDisplayContentLocked().mAppTransition.updateBooster();
- }
+ void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
+ if (mRecentsAnimationController != null) {
+ final RecentsAnimationController controller = mRecentsAnimationController;
+ mRecentsAnimationController = null;
+ controller.cleanupAnimation(reorderMode);
+ // TODO(mult-display): currently only default display support recents animation.
+ getDefaultDisplayContentLocked().mAppTransition.updateBooster();
}
}
- public void setAppFullscreen(IBinder token, boolean toOpaque) {
- synchronized (mGlobalLock) {
- final AppWindowToken atoken = mRoot.getAppWindowToken(token);
- if (atoken != null) {
- atoken.setFillsParent(toOpaque);
- setWindowOpaqueLocked(token, toOpaque);
- mWindowPlacerLocked.requestTraversal();
- }
- }
- }
-
- public void setWindowOpaque(IBinder token, boolean isOpaque) {
- synchronized (mGlobalLock) {
- setWindowOpaqueLocked(token, isOpaque);
- }
- }
-
- private void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
+ void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken != null) {
- final WindowState win = wtoken.findMainWindow();
- if (win == null) {
- return;
- }
- isOpaque = isOpaque & !PixelFormat.formatHasAlpha(win.getAttrs().format);
- win.mWinAnimator.setOpaqueLocked(isOpaque);
- }
- }
-
- public void setDockedStackCreateState(int mode, Rect bounds) {
- synchronized (mGlobalLock) {
- setDockedStackCreateStateLocked(mode, bounds);
+ wtoken.setMainWindowOpaque(isOpaque);
}
}
@@ -2679,14 +2646,12 @@
mDockedStackCreateBounds = bounds;
}
- public void checkSplitScreenMinimizedChanged(boolean animate) {
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.getDockedDividerController().checkMinimizeChanged(animate);
- }
+ void checkSplitScreenMinimizedChanged(boolean animate) {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ displayContent.getDockedDividerController().checkMinimizeChanged(animate);
}
- public boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
+ boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
aspectRatio);
@@ -4847,8 +4812,10 @@
case UPDATE_DOCKED_STACK_DIVIDER: {
synchronized (mGlobalLock) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.getDockedDividerController().reevaluateVisibility(false);
- displayContent.adjustForImeIfNeeded();
+ if (displayContent != null) {
+ displayContent.getDockedDividerController().reevaluateVisibility(false);
+ displayContent.adjustForImeIfNeeded();
+ }
}
break;
}
@@ -6495,31 +6462,14 @@
}
}
- public void setForceResizableTasks(boolean forceResizableTasks) {
- synchronized (mGlobalLock) {
- mForceResizableTasks = forceResizableTasks;
- }
- }
-
- public void setSupportsPictureInPicture(boolean supportsPictureInPicture) {
- synchronized (mGlobalLock) {
- mSupportsPictureInPicture = supportsPictureInPicture;
- }
- }
-
- public void setSupportsFreeformWindowManagement(boolean supportsFreeformWindowManagement) {
- synchronized (mGlobalLock) {
- mSupportsFreeformWindowManagement = supportsFreeformWindowManagement;
- }
- }
-
void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
synchronized (mGlobalLock) {
mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
}
}
- public void setIsPc(boolean isPc) {
+ @VisibleForTesting
+ void setIsPc(boolean isPc) {
synchronized (mGlobalLock) {
mIsPc = isPc;
}
@@ -6546,7 +6496,7 @@
"registerPinnedStackListener()")) {
return;
}
- if (!mSupportsPictureInPicture) {
+ if (!mAtmService.mSupportsPictureInPicture) {
return;
}
synchronized (mGlobalLock) {
@@ -7767,8 +7717,7 @@
clientChannel.transferTo(outInputChannel);
clientChannel.dispose();
- IBinder token = new Binder();
- mInputManager.registerInputChannel(inputChannel, token);
+ mInputManager.registerInputChannel(inputChannel, null /* generate new token */);
// Prevent the java finalizer from breaking the input channel. But we won't
// do any further management so we just release the java ref and let the
@@ -7776,7 +7725,7 @@
inputChannel.release();
InputWindowHandle h = new InputWindowHandle(null, null, displayId);
- h.token = token;
+ h.token = inputChannel.getToken();
h.name = name;
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
h.layoutParamsType = 0;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index cf8e1e8..242b116 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -41,7 +41,6 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import android.annotation.NonNull;
-import android.app.Activity;
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.app.ProfilerInfo;
@@ -644,8 +643,7 @@
for (int i = 0; i < activities.size(); i++) {
final ActivityRecord r = activities.get(i);
if (!r.finishing && r.isInStackLocked()) {
- r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
- "finish-heavy", true /* oomAdj */);
+ r.finishIfPossible("finish-heavy", true /* oomAdj */);
}
}
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 466ca93..03f4755 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -165,6 +165,7 @@
outPointerIcon->bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(),
bitmapCopy->rowBytes(), 0, 0);
}
+ outSpriteIcon->style = outPointerIcon->style;
outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX;
outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY;
}
@@ -1252,7 +1253,8 @@
status_t status = android_view_PointerIcon_load(env, pointerIconObj.get(),
displayContext.get(), &pointerIcon);
if (!status && !pointerIcon.isNullIcon()) {
- *icon = SpriteIcon(pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY);
+ *icon = SpriteIcon(
+ pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY);
} else {
*icon = SpriteIcon();
}
@@ -1293,10 +1295,12 @@
milliseconds_to_nanoseconds(pointerIcon.durationPerFrame);
animationData.animationFrames.reserve(numFrames);
animationData.animationFrames.push_back(SpriteIcon(
- pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+ pointerIcon.bitmap, pointerIcon.style,
+ pointerIcon.hotSpotX, pointerIcon.hotSpotY));
for (size_t i = 0; i < numFrames - 1; ++i) {
animationData.animationFrames.push_back(SpriteIcon(
- pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+ pointerIcon.bitmapFrames[i], pointerIcon.style,
+ pointerIcon.hotSpotX, pointerIcon.hotSpotY));
}
}
}
@@ -1711,6 +1715,7 @@
pointerIcon.bitmap.readPixels(spriteInfo, spriteIcon.bitmap.getPixels(),
spriteIcon.bitmap.rowBytes(), 0, 0);
}
+ spriteIcon.style = pointerIcon.style;
spriteIcon.hotSpotX = pointerIcon.hotSpotX;
spriteIcon.hotSpotY = pointerIcon.hotSpotY;
im->setCustomPointerIcon(spriteIcon);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ed900b1..67ae407 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -635,6 +635,13 @@
SystemServerInitThreadPool.get().submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
t.traceEnd();
+ // Platform compat service is used by ActivityManagerService, PackageManagerService, and
+ // possibly others in the future. b/135010838.
+ t.traceBegin("PlatformCompat");
+ ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE,
+ new PlatformCompat(mSystemContext));
+ t.traceEnd();
+
// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions. We need this to complete before we initialize other services.
@@ -1102,10 +1109,6 @@
SignedConfigService.registerUpdateReceiver(mSystemContext);
t.traceEnd();
- t.traceBegin("PlatformCompat");
- ServiceManager.addService("platform_compat", new PlatformCompat(context));
- t.traceEnd();
-
} catch (RuntimeException e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service", e);
diff --git a/services/robotests/backup/src/com/android/server/backup/TrampolineRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
similarity index 86%
rename from services/robotests/backup/src/com/android/server/backup/TrampolineRoboTest.java
rename to services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index f0a5d37..a1bfcdf 100644
--- a/services/robotests/backup/src/com/android/server/backup/TrampolineRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -26,8 +26,12 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import static org.testng.Assert.expectThrows;
@@ -72,7 +76,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
-/** Tests for {@link com.android.server.backup.Trampoline}. */
+/** Tests for {@link BackupManagerService}. */
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
@@ -83,7 +87,7 @@
ShadowSystemServiceRegistry.class
})
@Presubmit
-public class TrampolineRoboTest {
+public class BackupManagerServiceRoboTest {
private static final String TEST_PACKAGE = "package";
private static final String TEST_TRANSPORT = "transport";
private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE};
@@ -121,7 +125,7 @@
/** Test that the service registers users. */
@Test
public void testStartServiceForUser_registersUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.setBackupServiceActive(mUserOneId, true);
backupManagerService.startServiceForUser(mUserOneId);
@@ -134,7 +138,7 @@
/** Test that the service registers users. */
@Test
public void testStartServiceForUser_withServiceInstance_registersUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.setBackupServiceActive(mUserOneId, true);
backupManagerService.startServiceForUser(mUserOneId, mUserOneService);
@@ -147,7 +151,7 @@
/** Test that the service unregisters users when stopped. */
@Test
public void testStopServiceForUser_forRegisteredUser_unregistersCorrectUser() throws Exception {
- Trampoline backupManagerService =
+ BackupManagerService backupManagerService =
createServiceAndRegisterUser(mUserOneId, mUserOneService);
backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService);
ShadowBinder.setCallingUid(Process.SYSTEM_UID);
@@ -163,7 +167,7 @@
/** Test that the service unregisters users when stopped. */
@Test
public void testStopServiceForUser_forRegisteredUser_tearsDownCorrectUser() throws Exception {
- Trampoline backupManagerService =
+ BackupManagerService backupManagerService =
createServiceAndRegisterUser(mUserOneId, mUserOneService);
backupManagerService.setBackupServiceActive(mUserTwoId, true);
backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService);
@@ -177,7 +181,7 @@
/** Test that the service unregisters users when stopped. */
@Test
public void testStopServiceForUser_forUnknownUser_doesNothing() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.setBackupServiceActive(mUserOneId, true);
ShadowBinder.setCallingUid(Process.SYSTEM_UID);
@@ -194,7 +198,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testDataChanged_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -206,7 +210,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testDataChanged_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -218,7 +222,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAgentConnected_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
IBinder agentBinder = mock(IBinder.class);
@@ -231,7 +235,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testAgentConnected_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
IBinder agentBinder = mock(IBinder.class);
@@ -244,7 +248,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testOpComplete_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -256,7 +260,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testOpComplete_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -272,7 +276,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testInitializeTransports_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
String[] transports = {TEST_TRANSPORT};
@@ -285,7 +289,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testInitializeTransports_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
String[] transports = {TEST_TRANSPORT};
@@ -298,7 +302,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -310,7 +314,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testClearBackupData_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -322,7 +326,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testGetCurrentTransport_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -334,7 +338,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetCurrentTransport_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -347,7 +351,7 @@
@Test
public void testGetCurrentTransportComponent_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -360,7 +364,7 @@
@Test
public void testGetCurrentTransportComponent_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -372,7 +376,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testListAllTransports_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -384,7 +388,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testListAllTransports_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -397,7 +401,7 @@
@Test
public void testListAllTransportComponents_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -410,7 +414,7 @@
@Test
public void testListAllTransportComponents_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -422,7 +426,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testSelectBackupTransport_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -434,7 +438,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testSelectBackupTransport_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -446,7 +450,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testSelectTransportAsync_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
TransportData transport = backupTransport();
@@ -463,7 +467,7 @@
@Test
public void testSelectBackupTransportAsync_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
TransportData transport = backupTransport();
@@ -479,7 +483,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testGetConfigurationIntent_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -491,7 +495,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetConfigurationIntent_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -503,7 +507,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testGetDestinationString_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -515,7 +519,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetDestinationString_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -527,7 +531,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testGetDataManagementIntent_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -539,7 +543,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetDataManagementIntent_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -551,7 +555,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testGetDataManagementLabel_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -563,7 +567,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetDataManagementLabel_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -576,7 +580,7 @@
@Test
public void testUpdateTransportAttributes_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
TransportData transport = backupTransport();
@@ -606,7 +610,7 @@
@Test
public void testUpdateTransportAttributes_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
TransportData transport = backupTransport();
@@ -642,7 +646,7 @@
*/
@Test
public void testSetBackupEnabled_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -657,7 +661,7 @@
*/
@Test
public void testSetBackupEnabled_withPermission_propagatesForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
@@ -671,7 +675,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testSetBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -683,7 +687,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testSetBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -695,7 +699,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testSetAutoRestore_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -707,7 +711,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testSetAutoRestore_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -719,7 +723,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testIsBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -731,7 +735,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testIsBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -747,7 +751,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testIsAppEligibleForBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -759,7 +763,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testIsAppEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -772,7 +776,7 @@
@Test
public void testFilterAppsEligibleForBackup_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
String[] packages = {TEST_PACKAGE};
@@ -786,7 +790,7 @@
@Test
public void testFilterAppsEligibleForBackup_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
String[] packages = {TEST_PACKAGE};
@@ -802,7 +806,7 @@
*/
@Test
public void testBackupNow_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -815,7 +819,7 @@
*/
@Test
public void testBackupNow_withPermission_propagatesForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
@@ -829,7 +833,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testBackupNow_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -841,7 +845,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testBackupNow_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -856,7 +860,7 @@
*/
@Test
public void testRequestBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
String[] packages = {TEST_PACKAGE};
@@ -876,7 +880,7 @@
*/
@Test
public void testRequestBackup_withPermission_propagatesForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
@@ -893,7 +897,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testRequestBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
String[] packages = {TEST_PACKAGE};
IBackupObserver observer = mock(IBackupObserver.class);
@@ -908,7 +912,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testRequestBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
String[] packages = {TEST_PACKAGE};
IBackupObserver observer = mock(IBackupObserver.class);
@@ -926,7 +930,7 @@
*/
@Test
public void testCancelBackups_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -939,7 +943,7 @@
*/
@Test
public void testCancelBackups_withPermission_propagatesForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true);
@@ -952,7 +956,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testCancelBackups_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -964,7 +968,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testCancelBackups_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -976,7 +980,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testBeginFullBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService);
FullBackupJob job = new FullBackupJob();
@@ -988,7 +992,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testBeginFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
FullBackupJob job = new FullBackupJob();
backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job);
@@ -999,7 +1003,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testEndFullBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService);
backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
@@ -1010,7 +1014,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
@@ -1020,7 +1024,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testFullTransportBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
String[] packages = {TEST_PACKAGE};
@@ -1033,7 +1037,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testFullTransportBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
String[] packages = {TEST_PACKAGE};
@@ -1050,7 +1054,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testRestoreAtInstall_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1062,7 +1066,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testRestoreAtInstall_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1074,7 +1078,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testBeginRestoreSession_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1086,7 +1090,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testBeginRestoreSession_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1099,7 +1103,7 @@
@Test
public void testGetAvailableRestoreToken_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1111,7 +1115,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testGetAvailableRestoreToken_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1127,7 +1131,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testSetBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService);
ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM));
@@ -1139,7 +1143,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testSetBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.setBackupPassword("currentPassword", "newPassword");
@@ -1149,7 +1153,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testHasBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService);
ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM));
@@ -1161,7 +1165,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testHasBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.hasBackupPassword();
@@ -1174,7 +1178,7 @@
*/
@Test
public void testAdbBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1202,7 +1206,7 @@
*/
@Test
public void testAdbBackup_withPermission_propagatesForNonCallingUser() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
@@ -1239,7 +1243,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbBackup_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1274,7 +1278,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testAdbBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1312,7 +1316,7 @@
*/
@Test
public void testAdbRestore_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1327,7 +1331,7 @@
*/
@Test
public void testAdbRestore_withPermission_propagatesForNonCallingUser() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
registerUser(backupManagerService, mUserTwoId, mUserTwoService);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
@@ -1341,7 +1345,7 @@
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbRestore_onRegisteredUser_callsMethodForUser() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1354,7 +1358,7 @@
/** Test that the backup service does not route methods for non-registered users. */
@Test
public void testAdbRestore_onUnknownUser_doesNotPropagateCall() throws Exception {
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1374,7 +1378,7 @@
@Test
public void testAcknowledgeAdbBackupOrRestore_onRegisteredUser_callsMethodForUser()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class);
@@ -1400,7 +1404,7 @@
@Test
public void testAcknowledgeAdbBackupOrRestore_onUnknownUser_doesNotPropagateCall()
throws Exception {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class);
@@ -1430,7 +1434,7 @@
@Test
public void testDump_onRegisteredUser_callsMethodForUser() throws Exception {
grantDumpPermissions();
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
File testFile = createTestFile();
FileDescriptor fileDescriptor = new FileDescriptor();
PrintWriter printWriter = new PrintWriter(testFile);
@@ -1446,7 +1450,7 @@
@Test
public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception {
grantDumpPermissions();
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
File testFile = createTestFile();
FileDescriptor fileDescriptor = new FileDescriptor();
PrintWriter printWriter = new PrintWriter(testFile);
@@ -1461,7 +1465,7 @@
@Test
public void testDump_users_dumpsListOfRegisteredUsers() {
grantDumpPermissions();
- Trampoline backupManagerService = createSystemRegisteredService();
+ BackupManagerService backupManagerService = createSystemRegisteredService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
@@ -1471,7 +1475,7 @@
writer.flush();
assertEquals(
- String.format("%s %d %d\n", Trampoline.DUMP_RUNNING_USERS_MESSAGE,
+ String.format("%s %d %d\n", BackupManagerService.DUMP_RUNNING_USERS_MESSAGE,
UserHandle.USER_SYSTEM, mUserOneId),
out.toString());
}
@@ -1493,7 +1497,7 @@
*/
@Test
public void testGetServiceForUser_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
@@ -1510,7 +1514,7 @@
*/
@Test
public void testGetServiceForUserIfCallerHasPermission_withPermission_worksForNonCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true);
@@ -1525,7 +1529,7 @@
*/
@Test
public void testGetServiceForUserIfCallerHasPermission_withoutPermission_worksForCallingUser() {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
registerUser(backupManagerService, mUserOneId, mUserOneService);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
@@ -1534,25 +1538,98 @@
backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test"));
}
- private Trampoline createService() {
- return new Trampoline(mContext);
+ /**
+ * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is
+ * specifically to prevent overloading the logs in production.
+ */
+ @Test
+ public void testMoreDebug_isFalse() throws Exception {
+ boolean moreDebug = BackupManagerService.MORE_DEBUG;
+
+ assertThat(moreDebug).isFalse();
}
- private Trampoline createSystemRegisteredService() {
- Trampoline trampoline = createService();
- registerUser(trampoline, UserHandle.USER_SYSTEM, mUserSystemService);
- return trampoline;
+ /** Test that the constructor handles {@code null} parameters. */
+ @Test
+ public void testConstructor_withNullContext_throws() throws Exception {
+ expectThrows(
+ NullPointerException.class,
+ () ->
+ new BackupManagerService(
+ /* context */ null,
+ new SparseArray<>()));
+ }
+
+ /** Test that the constructor does not create {@link UserBackupManagerService} instances. */
+ @Test
+ public void testConstructor_doesNotRegisterUsers() throws Exception {
+ BackupManagerService backupManagerService = createService();
+
+ assertThat(backupManagerService.getUserServices().size()).isEqualTo(0);
+ }
+
+ // ---------------------------------------------
+ // Lifecycle tests
+ // ---------------------------------------------
+
+ /** testOnStart_publishesService */
+ @Test
+ public void testOnStart_publishesService() {
+ BackupManagerService backupManagerService = mock(BackupManagerService.class);
+ BackupManagerService.Lifecycle lifecycle =
+ spy(new BackupManagerService.Lifecycle(mContext, backupManagerService));
+ doNothing().when(lifecycle).publishService(anyString(), any());
+
+ lifecycle.onStart();
+
+ verify(lifecycle).publishService(Context.BACKUP_SERVICE, backupManagerService);
+ }
+
+ /** testOnUnlockUser_forwards */
+ @Test
+ public void testOnUnlockUser_forwards() {
+ BackupManagerService backupManagerService = mock(BackupManagerService.class);
+ BackupManagerService.Lifecycle lifecycle =
+ new BackupManagerService.Lifecycle(mContext, backupManagerService);
+
+ lifecycle.onUnlockUser(UserHandle.USER_SYSTEM);
+
+ verify(backupManagerService).onUnlockUser(UserHandle.USER_SYSTEM);
+ }
+
+ /** testOnStopUser_forwards */
+ @Test
+ public void testOnStopUser_forwards() {
+ BackupManagerService backupManagerService = mock(BackupManagerService.class);
+ BackupManagerService.Lifecycle lifecycle =
+ new BackupManagerService.Lifecycle(mContext, backupManagerService);
+
+ lifecycle.onStopUser(UserHandle.USER_SYSTEM);
+
+ verify(backupManagerService).onStopUser(UserHandle.USER_SYSTEM);
+ }
+
+ private BackupManagerService createService() {
+ return new BackupManagerService(mContext);
+ }
+
+ private BackupManagerService createSystemRegisteredService() {
+ BackupManagerService backupManagerService = createService();
+ registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserSystemService);
+ return backupManagerService;
}
private void registerUser(
- Trampoline trampoline, int userId, UserBackupManagerService userBackupManagerService) {
- trampoline.setBackupServiceActive(userId, true);
- trampoline.startServiceForUser(userId, userBackupManagerService);
+ BackupManagerService backupManagerService,
+ int userId,
+ UserBackupManagerService userBackupManagerService) {
+ backupManagerService.setBackupServiceActive(userId, true);
+ backupManagerService.startServiceForUser(userId, userBackupManagerService);
}
- private Trampoline createServiceAndRegisterUser(
+ private BackupManagerService createServiceAndRegisterUser(
int userId, UserBackupManagerService userBackupManagerService) {
- Trampoline backupManagerService = createService();
+ BackupManagerService backupManagerService = createService();
backupManagerService.setBackupServiceActive(userBackupManagerService.getUserId(), true);
backupManagerService.startServiceForUser(userId, userBackupManagerService);
return backupManagerService;
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
deleted file mode 100644
index a034474..0000000
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup;
-
-import static android.Manifest.permission.BACKUP;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.app.Application;
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.util.SparseArray;
-
-import com.android.server.testing.shadows.ShadowApplicationPackageManager;
-import com.android.server.testing.shadows.ShadowBinder;
-import com.android.server.testing.shadows.ShadowEnvironment;
-import com.android.server.testing.shadows.ShadowSystemServiceRegistry;
-import com.android.server.testing.shadows.ShadowUserManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowContextWrapper;
-
-import java.io.File;
-
-/** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowApplicationPackageManager.class,
- ShadowBinder.class,
- ShadowEnvironment.class,
- ShadowSystemServiceRegistry.class,
- ShadowUserManager.class,
- })
-@Presubmit
-public class BackupManagerServiceTest {
- private static final String TEST_PACKAGE = "package";
- private static final String TEST_TRANSPORT = "transport";
- private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE};
-
- private ShadowContextWrapper mShadowContext;
- private ShadowUserManager mShadowUserManager;
- private Context mContext;
- private Trampoline mTrampoline;
- @UserIdInt private int mUserOneId;
- @UserIdInt private int mUserTwoId;
- @Mock private UserBackupManagerService mUserOneService;
- @Mock private UserBackupManagerService mUserTwoService;
-
- /** Initialize {@link BackupManagerService}. */
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- Application application = RuntimeEnvironment.application;
- mContext = application;
- mShadowContext = shadowOf(application);
- mShadowUserManager = Shadow.extract(UserManager.get(application));
-
- mUserOneId = UserHandle.USER_SYSTEM + 1;
- mUserTwoId = mUserOneId + 1;
- mShadowUserManager.addUser(mUserOneId, "mUserOneId", 0);
- mShadowUserManager.addUser(mUserTwoId, "mUserTwoId", 0);
-
- mShadowContext.grantPermissions(BACKUP);
- mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL);
-
- mTrampoline = new Trampoline(mContext);
- ShadowBinder.setCallingUid(Process.SYSTEM_UID);
- }
-
- /**
- * Clean up and reset state that was created for testing {@link BackupManagerService}
- * operations.
- */
- @After
- public void tearDown() throws Exception {
- ShadowBinder.reset();
- }
-
- /**
- * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is
- * specifically to prevent overloading the logs in production.
- */
- @Test
- public void testMoreDebug_isFalse() throws Exception {
- boolean moreDebug = BackupManagerService.MORE_DEBUG;
-
- assertThat(moreDebug).isFalse();
- }
-
- /** Test that the constructor does not create {@link UserBackupManagerService} instances. */
- @Test
- public void testConstructor_doesNotRegisterUsers() throws Exception {
- BackupManagerService backupManagerService = createService();
-
- assertThat(mTrampoline.getUserServices().size()).isEqualTo(0);
- }
-
- /** Test that the constructor handles {@code null} parameters. */
- @Test
- public void testConstructor_withNullContext_throws() throws Exception {
- expectThrows(
- NullPointerException.class,
- () ->
- new BackupManagerService(
- /* context */ null,
- new Trampoline(mContext),
- new SparseArray<>()));
- }
-
- /** Test that the constructor handles {@code null} parameters. */
- @Test
- public void testConstructor_withNullTrampoline_throws() throws Exception {
- expectThrows(
- NullPointerException.class,
- () ->
- new BackupManagerService(
- mContext, /* trampoline */ null, new SparseArray<>()));
- }
-
- // ---------------------------------------------
- // Lifecycle tests
- // ---------------------------------------------
-
- /** testOnStart_publishesService */
- @Test
- public void testOnStart_publishesService() {
- Trampoline trampoline = mock(Trampoline.class);
- BackupManagerService.Lifecycle lifecycle =
- spy(new BackupManagerService.Lifecycle(mContext, trampoline));
- doNothing().when(lifecycle).publishService(anyString(), any());
-
- lifecycle.onStart();
-
- verify(lifecycle).publishService(Context.BACKUP_SERVICE, trampoline);
- }
-
- /** testOnUnlockUser_forwards */
- @Test
- public void testOnUnlockUser_forwards() {
- Trampoline trampoline = mock(Trampoline.class);
- BackupManagerService.Lifecycle lifecycle =
- new BackupManagerService.Lifecycle(mContext, trampoline);
-
- lifecycle.onUnlockUser(UserHandle.USER_SYSTEM);
-
- verify(trampoline).onUnlockUser(UserHandle.USER_SYSTEM);
- }
-
- /** testOnStopUser_forwards */
- @Test
- public void testOnStopUser_forwards() {
- Trampoline trampoline = mock(Trampoline.class);
- BackupManagerService.Lifecycle lifecycle =
- new BackupManagerService.Lifecycle(mContext, trampoline);
-
- lifecycle.onStopUser(UserHandle.USER_SYSTEM);
-
- verify(trampoline).onStopUser(UserHandle.USER_SYSTEM);
- }
-
- private BackupManagerService createService() {
- mShadowContext.grantPermissions(BACKUP);
- return new BackupManagerService(mContext, mTrampoline, mTrampoline.getUserServices());
- }
-
- private void registerUser(int userId, UserBackupManagerService userBackupManagerService) {
- mTrampoline.setBackupServiceActive(userId, true);
- mTrampoline.startServiceForUser(userId, userBackupManagerService);
- }
-
- /**
- * Sets the calling user to {@code userId} and grants the permission INTERACT_ACROSS_USERS_FULL
- * to the caller if {@code shouldGrantPermission} is {@code true}, else it denies the
- * permission.
- */
- private void setCallerAndGrantInteractUserPermission(
- @UserIdInt int userId, boolean shouldGrantPermission) {
- ShadowBinder.setCallingUserHandle(UserHandle.of(userId));
- if (shouldGrantPermission) {
- mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL);
- } else {
- mShadowContext.denyPermissions(INTERACT_ACROSS_USERS_FULL);
- }
- }
-
- private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception {
- File testFile = new File(mContext.getFilesDir(), "test");
- testFile.createNewFile();
- return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
- }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 84e810d..8632ca4 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1005,7 +1005,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
mDataDir,
@@ -1026,7 +1026,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
mDataDir,
@@ -1045,7 +1045,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
/* context */ null,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
mDataDir,
@@ -1077,7 +1077,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
/* backupThread */ null,
mBaseStateDir,
mDataDir,
@@ -1093,7 +1093,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
/* baseStateDir */ null,
mDataDir,
@@ -1102,8 +1102,8 @@
/**
* Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread,
- * File, File, TransportManager)}.
+ * UserBackupManagerService#createAndInitializeService(int, Context, BackupManagerService,
+ * HandlerThread, File, File, TransportManager)}.
*/
@Test
public void testCreateAndInitializeService_withNullDataDir_throws() {
@@ -1113,7 +1113,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
/* dataDir */ null,
@@ -1122,8 +1122,8 @@
/**
* Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread,
- * File, File, TransportManager)}.
+ * UserBackupManagerService#createAndInitializeService(int, Context, BackupManagerService,
+ * HandlerThread, File, File, TransportManager)}.
*/
@Test
public void testCreateAndInitializeService_withNullTransportManager_throws() {
@@ -1133,7 +1133,7 @@
UserBackupManagerService.createAndInitializeService(
USER_ID,
mContext,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
mDataDir,
@@ -1151,7 +1151,7 @@
UserBackupManagerService service = UserBackupManagerService.createAndInitializeService(
USER_ID,
contextSpy,
- new Trampoline(mContext),
+ new BackupManagerService(mContext),
mBackupThread,
mBaseStateDir,
mDataDir,
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
index 392d182..84421ef 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
@@ -37,7 +37,7 @@
import android.util.Log;
import com.android.server.backup.BackupAgentTimeoutParameters;
-import com.android.server.backup.Trampoline;
+import com.android.server.backup.BackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
@@ -89,7 +89,7 @@
UserBackupManagerService.createAndInitializeService(
userId,
context,
- new Trampoline(context),
+ new BackupManagerService(context),
backupThread,
baseStateDir,
dataDir,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
index 3614763..5d041b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -71,6 +71,7 @@
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
+ private TimeController.TcConstants mConstants;
private TimeController mTimeController;
private MockitoSession mMockingSession;
@@ -110,6 +111,7 @@
// Initialize real objects.
mTimeController = new TimeController(mJobSchedulerService);
+ mConstants = mTimeController.getTcConstants();
spyOn(mTimeController);
}
@@ -528,6 +530,46 @@
}
@Test
+ public void testJobDelayWakeupAlarmToggling() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus job = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(job), anyInt());
+
+ // Starting off with using a wakeup alarm.
+ mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false;
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(job, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(),
+ anyLong(),
+ eq(TAG_DELAY), any(), any(), any());
+
+ // Use a non wakeup alarm.
+ mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = true;
+
+ mTimeController.maybeStartTrackingJobLocked(job, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(eq(AlarmManager.ELAPSED_REALTIME), eq(now + HOUR_IN_MILLIS), anyLong(),
+ anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+
+ // Back off, use a wakeup alarm.
+ mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false;
+
+ mTimeController.maybeStartTrackingJobLocked(job, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(),
+ anyLong(),
+ eq(TAG_DELAY), any(), any(), any());
+ }
+
+ @Test
public void testCheckExpiredDeadlinesAndResetAlarm_AllReady() {
doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 1084d62..939aafa 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -752,7 +752,7 @@
}
@Override
- protected boolean isCalledForCurrentUserLocked() {
+ protected boolean hasRightsToCurrentUserLocked() {
return mResolvedUserId == mSystemSupport.getCurrentUserIdLocked();
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
similarity index 63%
rename from services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
rename to services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 848ef45..68b413f 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.server.backup;
@@ -76,7 +76,7 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class TrampolineTest {
+public class BackupManagerServiceTest {
private static final String PACKAGE_NAME = "some.package.name";
private static final String TRANSPORT_NAME = "some.transport.name";
private static final String CURRENT_PASSWORD = "current_password";
@@ -100,8 +100,6 @@
@UserIdInt
private int mUserId;
@Mock
- private BackupManagerService mBackupManagerServiceMock;
- @Mock
private UserBackupManagerService mUserBackupManagerService;
@Mock
private Context mContextMock;
@@ -124,7 +122,7 @@
private FileDescriptor mFileDescriptorStub = new FileDescriptor();
- private TrampolineTestable mTrampoline;
+ private BackupManagerServiceTestable mService;
private File mTestDir;
private File mSuppressFile;
private SparseArray<UserBackupManagerService> mUserServices;
@@ -142,38 +140,37 @@
when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
when(mUserManagerMock.getUserInfo(UNSTARTED_NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
- TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
- TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
- TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
- TrampolineTestable.sBackupDisabled = false;
- TrampolineTestable.sUserManagerMock = mUserManagerMock;
+ BackupManagerServiceTestable.sCallingUserId = UserHandle.USER_SYSTEM;
+ BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID;
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerServiceTestable.sUserManagerMock = mUserManagerMock;
mTestDir = InstrumentationRegistry.getContext().getFilesDir();
mTestDir.mkdirs();
mSuppressFile = new File(mTestDir, "suppress");
- TrampolineTestable.sSuppressFile = mSuppressFile;
+ BackupManagerServiceTestable.sSuppressFile = mSuppressFile;
setUpStateFilesForNonSystemUser(NON_USER_SYSTEM);
setUpStateFilesForNonSystemUser(UNSTARTED_NON_USER_SYSTEM);
when(mContextMock.getSystemService(Context.JOB_SCHEDULER_SERVICE))
.thenReturn(mock(JobScheduler.class));
- mTrampoline = new TrampolineTestable(mContextMock, mUserServices);
+ mService = new BackupManagerServiceTestable(mContextMock, mUserServices);
}
private void setUpStateFilesForNonSystemUser(int userId) {
File activatedFile = new File(mTestDir, "activate-" + userId);
- TrampolineTestable.sActivatedFiles.append(userId, activatedFile);
+ BackupManagerServiceTestable.sActivatedFiles.append(userId, activatedFile);
File rememberActivatedFile = new File(mTestDir, "rem-activate-" + userId);
- TrampolineTestable.sRememberActivatedFiles.append(userId, rememberActivatedFile);
+ BackupManagerServiceTestable.sRememberActivatedFiles.append(userId, rememberActivatedFile);
}
@After
public void tearDown() throws Exception {
mSuppressFile.delete();
- deleteFiles(TrampolineTestable.sActivatedFiles);
- deleteFiles(TrampolineTestable.sRememberActivatedFiles);
+ deleteFiles(BackupManagerServiceTestable.sActivatedFiles);
+ deleteFiles(BackupManagerServiceTestable.sRememberActivatedFiles);
}
private void deleteFiles(SparseArray<File> files) {
@@ -185,144 +182,149 @@
@Test
public void testIsBackupServiceActive_whenBackupsNotDisabledAndSuppressFileDoesNotExist() {
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void testOnUnlockUser_forNonSystemUserWhenBackupsDisabled_doesNotStartUser() {
- TrampolineTestable.sBackupDisabled = true;
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>());
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerServiceTestable service =
+ new BackupManagerServiceTestable(mContextMock, new SparseArray<>());
ConditionVariable unlocked = new ConditionVariable(false);
- trampoline.onUnlockUser(NON_USER_SYSTEM);
+ service.onUnlockUser(NON_USER_SYSTEM);
- trampoline.getBackupHandler().post(unlocked::open);
+ service.getBackupHandler().post(unlocked::open);
unlocked.block();
- assertNull(trampoline.getUserService(NON_USER_SYSTEM));
+ assertNull(service.getUserService(NON_USER_SYSTEM));
}
@Test
public void testOnUnlockUser_forSystemUserWhenBackupsDisabled_doesNotStartUser() {
- TrampolineTestable.sBackupDisabled = true;
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>());
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerServiceTestable service =
+ new BackupManagerServiceTestable(mContextMock, new SparseArray<>());
ConditionVariable unlocked = new ConditionVariable(false);
- trampoline.onUnlockUser(UserHandle.USER_SYSTEM);
+ service.onUnlockUser(UserHandle.USER_SYSTEM);
- trampoline.getBackupHandler().post(unlocked::open);
+ service.getBackupHandler().post(unlocked::open);
unlocked.block();
- assertNull(trampoline.getUserService(UserHandle.USER_SYSTEM));
+ assertNull(service.getUserService(UserHandle.USER_SYSTEM));
}
@Test
public void testOnUnlockUser_whenBackupNotActivated_doesNotStartUser() {
- TrampolineTestable.sBackupDisabled = false;
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>());
- trampoline.setBackupServiceActive(NON_USER_SYSTEM, false);
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerServiceTestable service =
+ new BackupManagerServiceTestable(mContextMock, new SparseArray<>());
+ service.setBackupServiceActive(NON_USER_SYSTEM, false);
ConditionVariable unlocked = new ConditionVariable(false);
- trampoline.onUnlockUser(NON_USER_SYSTEM);
+ service.onUnlockUser(NON_USER_SYSTEM);
- trampoline.getBackupHandler().post(unlocked::open);
+ service.getBackupHandler().post(unlocked::open);
unlocked.block();
- assertNull(trampoline.getUserService(NON_USER_SYSTEM));
+ assertNull(service.getUserService(NON_USER_SYSTEM));
}
@Test
public void testIsBackupServiceActive_forSystemUserWhenBackupDisabled_returnsTrue()
throws Exception {
- TrampolineTestable.sBackupDisabled = true;
- Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices);
- trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
+ backupManagerService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(backupManagerService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void testIsBackupServiceActive_forNonSystemUserWhenBackupDisabled_returnsTrue()
throws Exception {
- TrampolineTestable.sBackupDisabled = true;
- Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices);
- trampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
+ backupManagerService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertFalse(trampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(backupManagerService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated()
throws Exception {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated()
throws Exception {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
// Don't activate non-system user.
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void
isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated()
- throws Exception {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ throws Exception {
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void
isBackupServiceActive_forUnstartedNonSystemUser_returnsTrueWhenSystemAndUserActivated()
throws Exception {
- mTrampoline.setBackupServiceActive(UNSTARTED_NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(UNSTARTED_NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UNSTARTED_NON_USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UNSTARTED_NON_USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() {
- TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID;
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() {
- TrampolineTestable.sCallingUid = Process.ROOT_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.ROOT_UID;
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() {
- TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
try {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
fail();
} catch (SecurityException expected) {
}
@@ -331,30 +333,30 @@
@Test
public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() {
when(mUserInfoMock.isManagedProfile()).thenReturn(true);
- TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID;
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() {
when(mUserInfoMock.isManagedProfile()).thenReturn(true);
- TrampolineTestable.sCallingUid = Process.ROOT_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.ROOT_UID;
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() {
when(mUserInfoMock.isManagedProfile()).thenReturn(true);
- TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
+ BackupManagerServiceTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
try {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
fail();
} catch (SecurityException expected) {
}
@@ -367,7 +369,7 @@
.enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString());
try {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
fail();
} catch (SecurityException expected) {
}
@@ -381,7 +383,7 @@
eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
try {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
fail();
} catch (SecurityException expected) {
}
@@ -389,95 +391,96 @@
@Test
public void setBackupServiceActive_backupDisabled_ignored() {
- TrampolineTestable.sBackupDisabled = true;
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock, mUserServices);
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerServiceTestable service =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
- trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ service.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(service.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_alreadyActive_ignored() {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupServiceActive_makeNonActive_serviceDeletedAndSuppressFileCreated()
throws IOException {
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
public void setBackupActive_nonSystemUser_disabledForSystemUser_ignored() {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() {
int otherUser = NON_USER_SYSTEM + 1;
File activateFile = new File(mTestDir, "activate-" + otherUser);
- TrampolineTestable.sActivatedFiles.append(otherUser, activateFile);
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ BackupManagerServiceTestable.sActivatedFiles.append(otherUser, activateFile);
+ mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
- assertFalse(mTrampoline.isBackupServiceActive(otherUser));
+ assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mService.isBackupServiceActive(otherUser));
activateFile.delete();
}
@Test
public void setBackupServiceActive_forNonSystemUser_remembersActivated() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
assertTrue(RandomAccessFileUtils.readBoolean(
- TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), false));
+ BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), false));
}
@Test
public void setBackupServiceActiveFalse_forNonSystemUser_remembersActivated() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, false);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, false);
assertFalse(RandomAccessFileUtils.readBoolean(
- TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true));
+ BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true));
}
@Test
public void setBackupServiceActiveTwice_forNonSystemUser_remembersLastActivated() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, false);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mService.setBackupServiceActive(NON_USER_SYSTEM, false);
assertFalse(RandomAccessFileUtils.readBoolean(
- TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true));
+ BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true));
}
@Test
@@ -497,7 +500,7 @@
}
};
- mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
+ mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, (int) future.get(5, TimeUnit.SECONDS));
}
@@ -505,14 +508,14 @@
@Test
public void selectBackupTransportAsyncForUser_beforeUserUnlockedWithNullListener_doesNotThrow()
throws Exception {
- mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null);
+ mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null);
// No crash.
}
@Test
public void
- selectBackupTransportAsyncForUser_beforeUserUnlockedWithThrowingListener_doesNotThrow()
+ selectBackupTransportAsyncForUser_beforeUserUnlockedListenerThrowing_doesNotThrow()
throws Exception {
ISelectBackupTransportCallback.Stub listener =
new ISelectBackupTransportCallback.Stub() {
@@ -524,7 +527,7 @@
}
};
- mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
+ mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
// No crash.
}
@@ -535,44 +538,45 @@
android.Manifest.permission.DUMP)).thenReturn(
PackageManager.PERMISSION_DENIED);
- mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+ mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
- verifyNoMoreInteractions(mBackupManagerServiceMock);
+ verifyNoMoreInteractions(mUserBackupManagerService);
}
public void testGetUserForAncestralSerialNumber() {
- TrampolineTestable.sBackupDisabled = false;
- Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices);
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
- UserHandle user = trampoline.getUserForAncestralSerialNumber(11L);
+ UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
assertThat(user).isEqualTo(UserHandle.of(1));
}
public void testGetUserForAncestralSerialNumber_whenDisabled() {
- TrampolineTestable.sBackupDisabled = true;
- Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices);
+ BackupManagerServiceTestable.sBackupDisabled = true;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
- UserHandle user = trampoline.getUserForAncestralSerialNumber(11L);
+ UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
assertThat(user).isNull();
}
- private static class TrampolineTestable extends Trampoline {
+ private static class BackupManagerServiceTestable extends BackupManagerService {
static boolean sBackupDisabled = false;
static int sCallingUserId = -1;
static int sCallingUid = -1;
- static BackupManagerService sBackupManagerServiceMock = null;
static File sSuppressFile = null;
static SparseArray<File> sActivatedFiles = new SparseArray<>();
static SparseArray<File> sRememberActivatedFiles = new SparseArray<>();
static UserManager sUserManagerMock = null;
- TrampolineTestable(Context context, SparseArray<UserBackupManagerService> userServices) {
+ BackupManagerServiceTestable(
+ Context context, SparseArray<UserBackupManagerService> userServices) {
super(context, userServices);
- mService = sBackupManagerServiceMock;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
new file mode 100644
index 0000000..ccf3a90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -0,0 +1,748 @@
+/*
+ * Copyright (C) 2019 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.biometrics;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.security.KeyStore;
+
+import com.android.internal.R;
+import com.android.internal.statusbar.IStatusBarService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+@SmallTest
+public class BiometricServiceTest {
+
+ private static final String TAG = "BiometricServiceTest";
+
+ private static final String TEST_PACKAGE_NAME = "test_package";
+
+ private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable";
+ private static final String ERROR_NOT_RECOGNIZED = "not_recognized";
+ private static final String ERROR_TIMEOUT = "error_timeout";
+ private static final String ERROR_CANCELED = "error_canceled";
+ private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
+ private static final String ERROR_USER_CANCELED = "error_user_canceled";
+
+ private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
+
+ private BiometricService mBiometricService;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ IBiometricServiceReceiver mReceiver1;
+ @Mock
+ IBiometricServiceReceiver mReceiver2;
+ @Mock
+ FingerprintManager mFingerprintManager;
+ @Mock
+ FaceManager mFaceManager;
+
+ private static class MockInjector extends BiometricService.Injector {
+ @Override
+ IActivityManager getActivityManagerService() {
+ return mock(IActivityManager.class);
+ }
+
+ @Override
+ IStatusBarService getStatusBarService() {
+ return mock(IStatusBarService.class);
+ }
+
+ @Override
+ IFingerprintService getFingerprintService() {
+ return mock(IFingerprintService.class);
+ }
+
+ @Override
+ IFaceService getFaceService() {
+ return mock(IFaceService.class);
+ }
+
+ @Override
+ BiometricService.SettingObserver getSettingObserver(Context context, Handler handler,
+ List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
+ return mock(BiometricService.SettingObserver.class);
+ }
+
+ @Override
+ KeyStore getKeyStore() {
+ return mock(KeyStore.class);
+ }
+
+ @Override
+ boolean isDebugEnabled(Context context, int userId) {
+ return false;
+ }
+
+ @Override
+ void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
+ // no-op for test
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+ when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
+ .thenReturn(mFingerprintManager);
+ when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getResources()).thenReturn(mResources);
+
+ when(mResources.getString(R.string.biometric_error_hw_unavailable))
+ .thenReturn(ERROR_HW_UNAVAILABLE);
+ when(mResources.getString(R.string.biometric_not_recognized))
+ .thenReturn(ERROR_NOT_RECOGNIZED);
+ when(mResources.getString(R.string.biometric_error_user_canceled))
+ .thenReturn(ERROR_USER_CANCELED);
+ }
+
+ @Test
+ public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
+
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE));
+ }
+
+ @Test
+ public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any());
+ }
+
+ @Test
+ public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+ }
+
+ @Test
+ public void testAuthenticateFace_respectsUserSetting()
+ throws Exception {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ // Disabled in user settings receives onError
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+
+ // Enrolled, not disabled in settings, user requires confirmation in settings
+ resetReceiver();
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
+ .thenReturn(true);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mReceiver1, never()).onError(anyInt(), any(String.class));
+ verify(mBiometricService.mFaceService).prepareForAuthentication(
+ eq(true) /* requireConfirmation */,
+ any(IBinder.class),
+ anyLong() /* sessionId */,
+ anyInt() /* userId */,
+ any(IBiometricServiceReceiverInternal.class),
+ anyString() /* opPackageName */,
+ anyInt() /* cookie */,
+ anyInt() /* callingUid */,
+ anyInt() /* callingPid */,
+ anyInt() /* callingUserId */);
+
+ // Enrolled, not disabled in settings, user doesn't require confirmation in settings
+ resetReceiver();
+ when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
+ .thenReturn(false);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+ verify(mBiometricService.mFaceService).prepareForAuthentication(
+ eq(false) /* requireConfirmation */,
+ any(IBinder.class),
+ anyLong() /* sessionId */,
+ anyInt() /* userId */,
+ any(IBiometricServiceReceiverInternal.class),
+ anyString() /* opPackageName */,
+ anyInt() /* cookie */,
+ anyInt() /* callingUid */,
+ anyInt() /* callingPid */,
+ anyInt() /* callingUserId */);
+ }
+
+ @Test
+ public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ // Start testing the happy path
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */);
+ waitForIdle();
+
+ // Creates a pending auth session with the correct initial states
+ assertEquals(mBiometricService.mPendingAuthSession.mState,
+ BiometricService.STATE_AUTH_CALLED);
+
+ // Invokes <Modality>Service#prepareForAuthentication
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mReceiver1, never()).onError(anyInt(), any(String.class));
+ verify(mBiometricService.mFingerprintService).prepareForAuthentication(
+ any(IBinder.class),
+ anyLong() /* sessionId */,
+ anyInt() /* userId */,
+ any(IBiometricServiceReceiverInternal.class),
+ anyString() /* opPackageName */,
+ cookieCaptor.capture() /* cookie */,
+ anyInt() /* callingUid */,
+ anyInt() /* callingPid */,
+ anyInt() /* callingUserId */);
+
+ // onReadyForAuthentication, mCurrentAuthSession state OK
+ mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue(),
+ anyBoolean() /* requireConfirmation */, anyInt() /* userId */);
+ waitForIdle();
+ assertNull(mBiometricService.mPendingAuthSession);
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+
+ // startPreparedClient invoked
+ verify(mBiometricService.mFingerprintService)
+ .startPreparedClient(cookieCaptor.getValue());
+
+ // StatusBar showBiometricDialog invoked
+ verify(mBiometricService.mStatusBarService).showBiometricDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+
+ // Hardware authenticated
+ mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
+ false /* requireConfirmation */,
+ new byte[69] /* HAT */);
+ waitForIdle();
+ // Waiting for SystemUI to send dismissed callback
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI);
+ // Notify SystemUI hardware authenticated
+ verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(
+ eq(true) /* authenticated */, eq(null) /* failureReason */);
+
+ // SystemUI sends callback with dismissed reason
+ mBiometricService.mInternalReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
+ waitForIdle();
+ // HAT sent to keystore
+ verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
+ // Send onAuthenticated to client
+ verify(mReceiver1).onAuthenticationSucceeded();
+ // Current session becomes null
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testAuthenticate_happyPathWithConfirmation() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */);
+
+ // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
+ // sent to KeyStore yet
+ mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
+ true /* requireConfirmation */,
+ new byte[69] /* HAT */);
+ waitForIdle();
+ // Waiting for SystemUI to send confirmation callback
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_PENDING_CONFIRM);
+ verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+
+ // SystemUI sends confirm, HAT is sent to keystore and client is notified.
+ mBiometricService.mInternalReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_CONFIRMED);
+ waitForIdle();
+ verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
+ verify(mReceiver1).onAuthenticationSucceeded();
+ }
+
+ @Test
+ public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onAuthenticationFailed();
+ waitForIdle();
+
+ verify(mBiometricService.mStatusBarService)
+ .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+ verify(mReceiver1).onAuthenticationFailed();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_PAUSED);
+ }
+
+ @Test
+ public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onAuthenticationFailed();
+ waitForIdle();
+
+ verify(mBiometricService.mStatusBarService)
+ .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+ verify(mReceiver1).onAuthenticationFailed();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ @Test
+ public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ // Create a new pending auth session but don't start it yet. HAL contract is that previous
+ // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session,
+ // sending ERROR_CANCELED to the current auth session, and then having the second one
+ // onReadyForAuthentication.
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */);
+ waitForIdle();
+
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED);
+ waitForIdle();
+
+ // Auth session doesn't become null until SystemUI responds that the animation is completed
+ assertNotNull(mBiometricService.mCurrentAuthSession);
+ // ERROR_CANCELED is not sent until SystemUI responded that animation is completed
+ verify(mReceiver1, never()).onError(
+ anyInt(), anyString());
+ verify(mReceiver2, never()).onError(anyInt(), any(String.class));
+
+ // SystemUI dialog closed
+ verify(mBiometricService.mStatusBarService).hideBiometricDialog();
+
+ // After SystemUI notifies that the animation has completed
+ mBiometricService.mInternalReceiver
+ .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+ eq(ERROR_CANCELED));
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ ERROR_TIMEOUT);
+ waitForIdle();
+
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_PAUSED);
+ verify(mBiometricService.mStatusBarService)
+ .onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT));
+ // Timeout does not count as fail as per BiometricPrompt documentation.
+ verify(mReceiver1, never()).onAuthenticationFailed();
+
+ // No pending auth session. Pressing try again will create one.
+ assertNull(mBiometricService.mPendingAuthSession);
+
+ // Pressing "Try again" on SystemUI starts a new auth session.
+ mBiometricService.mInternalReceiver.onTryAgainPressed();
+ waitForIdle();
+
+ // The last one is still paused, and a new one has been created.
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_PAUSED);
+ assertEquals(mBiometricService.mPendingAuthSession.mState,
+ BiometricService.STATE_AUTH_CALLED);
+
+ // Test resuming when hardware becomes ready. SystemUI should not be requested to
+ // show another dialog since it's already showing.
+ resetStatusBar();
+ startPendingAuthSession(mBiometricService);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService, never()).showBiometricDialog(
+ any(Bundle.class),
+ any(IBiometricServiceReceiverInternal.class),
+ anyInt(),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ anyString());
+ }
+
+ @Test
+ public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireCOnfirmation */);
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ ERROR_TIMEOUT);
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ ERROR_CANCELED);
+ waitForIdle();
+
+ // Client receives error immediately
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+ eq(ERROR_CANCELED));
+ // Dialog is hidden immediately
+ verify(mBiometricService.mStatusBarService).hideBiometricDialog();
+ // Auth session is over
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testErrorFromHal_whileAuthenticating_waitsForSysUIBeforeNotifyingClient()
+ throws Exception {
+ // For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
+ // until SystemUI notifies us that the dialog is dismissed at which point the current
+ // session is done.
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
+ ERROR_UNABLE_TO_PROCESS);
+ waitForIdle();
+
+ // Sends error to SystemUI and does not notify client yet
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_ERROR_PENDING_SYSUI);
+ verify(mBiometricService.mStatusBarService)
+ .onBiometricError(eq(ERROR_UNABLE_TO_PROCESS));
+ verify(mBiometricService.mStatusBarService, never()).hideBiometricDialog();
+ verify(mReceiver1, never()).onError(anyInt(), anyString());
+
+ // SystemUI animation completed, client is notified, auth session is over
+ mBiometricService.mInternalReceiver
+ .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
+ eq(ERROR_UNABLE_TO_PROCESS));
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver
+ .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
+ eq(ERROR_USER_CANCELED));
+ verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService(
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ eq(false) /* fromClient */);
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ ERROR_TIMEOUT);
+ mBiometricService.mInternalReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ waitForIdle();
+
+ verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyBoolean());
+ }
+
+ @Test
+ public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ ERROR_TIMEOUT);
+ mBiometricService.mInternalReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ waitForIdle();
+
+ verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyBoolean());
+ }
+
+ @Test
+ public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
+ true /* requireConfirmation */,
+ new byte[69] /* HAT */);
+ mBiometricService.mInternalReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+ waitForIdle();
+
+ // doesn't send cancel to HAL
+ verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyBoolean());
+ verify(mReceiver1).onError(
+ eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
+ eq(ERROR_USER_CANCELED));
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
+ public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */);
+
+ mBiometricService.mInternalReceiver.onAcquired(
+ FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ FINGERPRINT_ACQUIRED_SENSOR_DIRTY);
+ waitForIdle();
+
+ // Sends to SysUI and stays in authenticating state
+ verify(mBiometricService.mStatusBarService)
+ .onBiometricHelp(eq(FINGERPRINT_ACQUIRED_SENSOR_DIRTY));
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ // Helper methods
+
+ private void setupAuthForOnly(int modality) {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
+
+ if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ } else if (modality == BiometricAuthenticator.TYPE_FACE) {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+ } else {
+ fail("Unknown modality: " + modality);
+ }
+
+ mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService.onStart();
+
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ }
+
+ private void resetReceiver() {
+ mReceiver1 = mock(IBiometricServiceReceiver.class);
+ mReceiver2 = mock(IBiometricServiceReceiver.class);
+ }
+
+ private void resetStatusBar() {
+ mBiometricService.mStatusBarService = mock(IStatusBarService.class);
+ }
+
+ private void invokeAuthenticateAndStart(IBiometricService.Stub service,
+ IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception {
+ // Request auth, creates a pending session
+ invokeAuthenticate(service, receiver, requireConfirmation);
+ waitForIdle();
+
+ startPendingAuthSession(mBiometricService);
+ waitForIdle();
+ }
+
+ private static void startPendingAuthSession(BiometricService service) throws Exception {
+ // Get the cookie so we can pretend the hardware is ready to authenticate
+ // Currently we only support single modality per auth
+ assertEquals(service.mPendingAuthSession.mModalitiesWaiting.values().size(), 1);
+ final int cookie = service.mPendingAuthSession.mModalitiesWaiting.values()
+ .iterator().next();
+ assertNotEquals(cookie, 0);
+
+ service.mImpl.onReadyForAuthentication(cookie,
+ anyBoolean() /* requireConfirmation */, anyInt() /* userId */);
+ }
+
+ private static void invokeAuthenticate(IBiometricService.Stub service,
+ IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception {
+ service.authenticate(
+ new Binder() /* token */,
+ 0 /* sessionId */,
+ 0 /* userId */,
+ receiver,
+ TEST_PACKAGE_NAME /* packageName */,
+ createTestBiometricPromptBundle(requireConfirmation),
+ null /* IBiometricConfirmDeviceCredentialCallback */);
+ }
+
+ private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation) {
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation);
+ return bundle;
+ }
+
+ private static int getCookieForCurrentSession(BiometricService.AuthSession session) {
+ assertEquals(session.mModalitiesMatched.values().size(), 1);
+ return session.mModalitiesMatched.values().iterator().next();
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c1c0a30..3ac7a79 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -181,12 +181,6 @@
@RunWithLooper
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
- private static final String CLEAR_DEVICE_CONFIG_KEY_CMD =
- "device_config delete " + DeviceConfig.NAMESPACE_SYSTEMUI + " "
- + SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE;
- private static final String SET_DEFAULT_ASSISTANT_DEVICE_CONFIG_CMD =
- "device_config put " + DeviceConfig.NAMESPACE_SYSTEMUI + " "
- + SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE;
private final int mUid = Binder.getCallingUid();
private TestableNotificationManagerService mService;
@@ -2766,6 +2760,18 @@
}
@Test
+ public void testReadPolicyXml_readSnoozedNotificationsFromXml() throws Exception {
+ final String upgradeXml = "<notification-policy version=\"1\">"
+ + "<snoozed-notifications>></snoozed-notifications>"
+ + "</notification-policy>";
+ mService.readPolicyXml(
+ new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
+ false,
+ UserHandle.USER_ALL);
+ verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class));
+ }
+
+ @Test
public void testReadPolicyXml_readApprovedServicesFromSettings() throws Exception {
final String preupgradeXml = "<notification-policy version=\"1\">"
+ "<ranking></ranking>"
@@ -4028,6 +4034,41 @@
}
@Test
+ public void testPostFromAndroidForNonExistentPackage() throws Exception {
+ final String notReal = "NOT REAL";
+ when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())).thenThrow(
+ PackageManager.NameNotFoundException.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.uid = -1;
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai);
+
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ try {
+ mInternalService.enqueueNotification(notReal, "android", 0, 0, "tag",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ fail("can't post notifications for nonexistent packages, even if you exist");
+ } catch (SecurityException e) {
+ // yay
+ }
+ }
+
+ @Test
+ public void testCancelFromAndroidForNonExistentPackage() throws Exception {
+ final String notReal = "NOT REAL";
+ when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow(
+ PackageManager.NameNotFoundException.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.uid = -1;
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai);
+
+ // unlike the post case, ignore instead of throwing
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+
+ mInternalService.cancelNotification(notReal, "android", 0, 0, "tag",
+ sbn.getId(), sbn.getUserId());
+ }
+
+ @Test
public void testResolveNotificationUid_delegateNotAllowed() throws Exception {
when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(123);
// no delegate
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 2e7277f..36175a9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -37,9 +38,11 @@
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.IntArray;
+import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -48,6 +51,15 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -69,6 +81,117 @@
}
@Test
+ public void testWriteXMLformattedCorrectly_testReadingCorrectTime()
+ throws XmlPullParserException, IOException {
+ final String max_time_str = Long.toString(Long.MAX_VALUE);
+ final String xml_string = "<snoozed-notifications>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>"
+ + "</snoozed-notifications>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml_string.getBytes())), null);
+ mSnoozeHelper.readXml(parser);
+ assertTrue("Should read the notification time from xml and it should be more than zero",
+ 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ 0, "pkg", "key").doubleValue());
+ }
+
+ @Test
+ public void testWriteXMLformattedCorrectly_testCorrectContextURI()
+ throws XmlPullParserException, IOException {
+ final String max_time_str = Long.toString(Long.MAX_VALUE);
+ final String xml_string = "<snoozed-notifications>"
+ + "<context version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key\" id=\"uri\"/>"
+ + "<context version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key2\" id=\"uri\"/>"
+ + "</snoozed-notifications>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml_string.getBytes())), null);
+ mSnoozeHelper.readXml(parser);
+ assertEquals("Should read the notification context from xml and it should be `uri",
+ "uri", mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ 0, "pkg", "key"));
+ }
+
+ @Test
+ public void testReadValidSnoozedFromCorrectly_timeDeadline()
+ throws XmlPullParserException, IOException {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 999999999);
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser);
+ assertTrue("Should read the notification time from xml and it should be more than zero",
+ 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ 0, "pkg", r.getKey()).doubleValue());
+ }
+
+
+ @Test
+ public void testReadExpiredSnoozedNotification() throws
+ XmlPullParserException, IOException, InterruptedException {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 0);
+ // Thread.sleep(100);
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
+ Thread.sleep(10);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser);
+ int systemUser = UserHandle.SYSTEM.getIdentifier();
+ assertTrue("Should see a past time returned",
+ System.currentTimeMillis() > mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ systemUser, "pkg", r.getKey()).longValue());
+ }
+
+ @Test
+ public void testCleanupContextShouldRemovePersistedRecord() {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, "context");
+ mSnoozeHelper.cleanupPersistedContext(r.sbn.getKey());
+ assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ r.getUser().getIdentifier(),
+ r.sbn.getPackageName(),
+ r.sbn.getKey()
+ ));
+ }
+
+ @Test
+ public void testReadNoneSnoozedNotification() throws XmlPullParserException,
+ IOException, InterruptedException {
+ NotificationRecord r = getNotificationRecord(
+ "pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 0);
+
+ assertEquals("should see a zero value for unsnoozed notification",
+ 0L,
+ mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ UserHandle.SYSTEM.getIdentifier(),
+ "not_my_package", r.getKey()).longValue());
+ }
+
+ @Test
public void testSnoozeForTime() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
@@ -84,7 +207,7 @@
@Test
public void testSnooze() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
- mSnoozeHelper.snooze(r);
+ mSnoozeHelper.snooze(r, (String) null);
verify(mAm, never()).setExactAndAllowWhileIdle(
anyInt(), anyLong(), any(PendingIntent.class));
assertTrue(mSnoozeHelper.isSnoozed(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 63b9198..6c78f6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -28,6 +28,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
@@ -140,7 +141,8 @@
public void testNotResumeHomeStackOnRemovingDisplay() {
// Create a display which supports system decoration and allows reparenting stacks to
// another display when the display is removed.
- final ActivityDisplay display = spy(createNewActivityDisplay());
+ final ActivityDisplay display = createNewActivityDisplay();
+ spyOn(display);
doReturn(false).when(display).shouldDestroyContentOnRemove();
doReturn(true).when(display).supportsSystemDecorations();
mRootActivityContainer.addChild(display, ActivityDisplay.POSITION_TOP);
@@ -304,14 +306,18 @@
ACTIVITY_TYPE_STANDARD, ON_TOP);
final ActivityStack stack4 = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
- final TaskRecord task1 = new TaskBuilder(mService.mStackSupervisor).setStack(
- stack1).setTaskId(1).build();
- final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack(
- stack2).setTaskId(2).build();
- final TaskRecord task3 = new TaskBuilder(mService.mStackSupervisor).setStack(
- stack3).setTaskId(3).build();
- final TaskRecord task4 = new TaskBuilder(mService.mStackSupervisor).setStack(
- stack4).setTaskId(4).build();
+ final TaskRecord task1 = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(stack1)
+ .build();
+ final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(stack2)
+ .build();
+ final TaskRecord task3 = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(stack3)
+ .build();
+ final TaskRecord task4 = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(stack4)
+ .build();
// Reordering stacks while removing stacks.
doAnswer(invocation -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 23bae88..977dd8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -61,13 +61,13 @@
private ActivityMetricsLaunchObserver mLaunchObserver;
private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry;
- private TestActivityStack mStack;
+ private ActivityStack mStack;
private TaskRecord mTask;
private ActivityRecord mActivityRecord;
private ActivityRecord mActivityRecordTrampoline;
@Before
- public void setUpAMLO() throws Exception {
+ public void setUpAMLO() {
mLaunchObserver = mock(ActivityMetricsLaunchObserver.class);
// ActivityStackSupervisor always creates its own instance of ActivityMetricsLogger.
@@ -78,15 +78,19 @@
// Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
// This seems to be the easiest way to create an ActivityRecord.
- mStack = mRootActivityContainer.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
- mActivityRecord = new ActivityBuilder(mService).setTask(mTask).build();
+ mStack = new StackBuilder(mRootActivityContainer)
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setOnTop(true)
+ .setCreateActivity(true)
+ .build();
+ mTask = mStack.topTask();
+ mActivityRecord = mTask.getTopActivity();
mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build();
}
@After
- public void tearDownAMLO() throws Exception {
+ public void tearDownAMLO() {
if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed.
mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7b252cb..13748cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,10 +16,17 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
@@ -28,18 +35,25 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
+import static com.android.server.wm.ActivityStack.ActivityState.FINISHING;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STARTED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
+import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE;
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
@@ -54,6 +68,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -71,6 +86,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
import android.util.MutableBoolean;
+import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner.Stub;
import android.view.RemoteAnimationAdapter;
@@ -79,7 +95,7 @@
import androidx.test.filters.MediumTest;
import com.android.internal.R;
-import com.android.server.wm.utils.WmDisplayCutout;
+import com.android.server.wm.ActivityStack.ActivityState;
import org.junit.Before;
import org.junit.Test;
@@ -96,13 +112,13 @@
@MediumTest
@Presubmit
public class ActivityRecordTests extends ActivityTestsBase {
- private TestActivityStack mStack;
+ private ActivityStack mStack;
private TaskRecord mTask;
private ActivityRecord mActivity;
@Before
public void setUp() throws Exception {
- mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
+ mStack = new StackBuilder(mRootActivityContainer).build();
mTask = mStack.getChildAt(0);
mActivity = mTask.getTopActivity();
@@ -113,13 +129,13 @@
@Test
public void testStackCleanupOnClearingTask() {
mActivity.setTask(null);
- assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
+ verify(mStack, times(1)).onActivityRemovedFromStack(any());
}
@Test
public void testStackCleanupOnActivityRemoval() {
mTask.removeActivity(mActivity);
- assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
+ verify(mStack, times(1)).onActivityRemovedFromStack(any());
}
@Test
@@ -134,7 +150,7 @@
final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
.build();
mActivity.reparent(newTask, 0, null /*reason*/);
- assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
+ verify(mStack, times(0)).onActivityRemovedFromStack(any());
}
@Test
@@ -181,7 +197,7 @@
assertTrue(mActivity.isState(STARTED));
mStack.mTranslucentActivityWaiting = null;
- topActivity.changeWindowTranslucency(false);
+ topActivity.setOccludesParent(false);
mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque");
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
assertTrue(mActivity.isState(STARTED));
@@ -261,8 +277,8 @@
public void testNewParentConfigurationIncrementsSeq() {
final Configuration newConfig = new Configuration(
mTask.getRequestedOverrideConfiguration());
- newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+ newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
mTask.onRequestedOverrideConfigurationChanged(newConfig);
@@ -277,7 +293,7 @@
.getRequestedOverrideConfiguration();
final Configuration newConfig = new Configuration();
- newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+ newConfig.orientation = ORIENTATION_PORTRAIT;
final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
mActivity.onRequestedOverrideConfigurationChanged(newConfig);
@@ -293,10 +309,11 @@
mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
mActivity.getConfiguration()));
- mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+ mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(mTask.getConfiguration());
- newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+ newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
+ ? ORIENTATION_LANDSCAPE
+ : ORIENTATION_PORTRAIT;
mTask.onRequestedOverrideConfigurationChanged(newConfig);
mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
@@ -315,13 +332,14 @@
mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
mActivity.getConfiguration()));
- mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+ mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(mTask.getConfiguration());
- newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+ newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
+ ? ORIENTATION_LANDSCAPE
+ : ORIENTATION_PORTRAIT;
mTask.onRequestedOverrideConfigurationChanged(newConfig);
- doReturn(true).when(mTask.getTask()).isDragResizing();
+ doReturn(true).when(mTask.mTask).isDragResizing();
mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
@@ -355,30 +373,39 @@
@Test
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
+ mActivity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .build();
mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
- mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
mActivity.getConfiguration()));
- mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(mActivity.getConfiguration());
- newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Configuration.ORIENTATION_LANDSCAPE
- : Configuration.ORIENTATION_PORTRAIT;
+ final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
+ final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
+ if (newConfig.orientation == ORIENTATION_PORTRAIT) {
+ newConfig.orientation = ORIENTATION_LANDSCAPE;
+ newConfig.screenWidthDp = longSide;
+ newConfig.screenHeightDp = shortSide;
+ } else {
+ newConfig.orientation = ORIENTATION_PORTRAIT;
+ newConfig.screenWidthDp = shortSide;
+ newConfig.screenHeightDp = longSide;
+ }
// Mimic the behavior that display doesn't handle app's requested orientation.
- doAnswer(invocation -> {
- mTask.onConfigurationChanged(newConfig);
- return null;
- }).when(mActivity.mAppWindowToken).setOrientation(anyInt(), any(), any());
+ final DisplayContent dc = mTask.mTask.getDisplayContent();
+ doReturn(false).when(dc).onDescendantOrientationChanged(any(), any());
+ doReturn(false).when(dc).handlesOrientationChangeFromDescendant();
final int requestedOrientation;
switch (newConfig.orientation) {
- case Configuration.ORIENTATION_LANDSCAPE:
- requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ case ORIENTATION_LANDSCAPE:
+ requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE;
break;
- case Configuration.ORIENTATION_PORTRAIT:
+ case ORIENTATION_PORTRAIT:
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
break;
default:
@@ -421,24 +448,33 @@
@Test
public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
+ mActivity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setLaunchTaskBehind(true)
+ .setConfigChanges(CONFIG_ORIENTATION)
+ .build();
mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
- final TestActivityStack stack = (TestActivityStack) new StackBuilder(mRootActivityContainer)
- .build();
+ final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
try {
- stack.setIsTranslucent(false);
+ doReturn(false).when(stack).isStackTranslucent(any());
assertFalse(mStack.shouldBeVisible(null /* starting */));
- mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
mActivity.getConfiguration()));
- mActivity.mLaunchTaskBehind = true;
- mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(mActivity.getConfiguration());
- newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Configuration.ORIENTATION_LANDSCAPE
- : Configuration.ORIENTATION_PORTRAIT;
+ final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
+ final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
+ if (newConfig.orientation == ORIENTATION_PORTRAIT) {
+ newConfig.orientation = ORIENTATION_LANDSCAPE;
+ newConfig.screenWidthDp = longSide;
+ newConfig.screenHeightDp = shortSide;
+ } else {
+ newConfig.orientation = ORIENTATION_PORTRAIT;
+ newConfig.screenWidthDp = shortSide;
+ newConfig.screenHeightDp = longSide;
+ }
mTask.onConfigurationChanged(newConfig);
@@ -457,7 +493,7 @@
@Test
public void testShouldPauseWhenMakeClientVisible() {
ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
- topActivity.changeWindowTranslucency(false);
+ topActivity.setOccludesParent(false);
mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
mActivity.makeClientVisible();
assertEquals(STARTED, mActivity.getState());
@@ -468,6 +504,7 @@
setupDisplayContentForCompatDisplayInsets();
final int decorHeight = 200; // e.g. The device has cutout.
final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy();
+ spyOn(policy);
doAnswer(invocationOnMock -> {
final int rotation = invocationOnMock.<Integer>getArgument(0);
final Rect insets = invocationOnMock.<Rect>getArgument(4);
@@ -482,7 +519,7 @@
doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
.when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility();
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
ensureActivityConfiguration();
// The parent configuration doesn't change since the first resolved configuration, so the
@@ -506,19 +543,32 @@
@Test
public void testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay() {
// Initialize different bounds on a new display.
- final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
- newDisplay.getWindowConfiguration().setAppBounds(new Rect(0, 0, 1000, 2000));
- newDisplay.getConfiguration().densityDpi = 300;
+ final Rect newDisplayBounds = new Rect(0, 0, 1000, 2000);
+ DisplayInfo info = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(info);
+ info.logicalWidth = newDisplayBounds.width();
+ info.logicalHeight = newDisplayBounds.height();
+ info.logicalDensityDpi = 300;
- mTask.getConfiguration().densityDpi = 200;
- prepareFixedAspectRatioUnresizableActivity();
+ final ActivityDisplay newDisplay =
+ addNewActivityDisplayAt(info, ActivityDisplay.POSITION_TOP);
+
+ final Configuration c =
+ new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
+ c.densityDpi = 200;
+ mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
+ mActivity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setMaxAspectRatio(1.5f)
+ .build();
+ mActivity.visible = true;
final Rect originalBounds = new Rect(mActivity.getBounds());
final int originalDpi = mActivity.getConfiguration().densityDpi;
// Move the non-resizable activity to the new display.
mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */);
- ensureActivityConfiguration();
assertEquals(originalBounds, mActivity.getBounds());
assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
@@ -531,9 +581,9 @@
when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
- mTask.getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
+ mTask.getConfiguration().orientation = ORIENTATION_PORTRAIT;
mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
ensureActivityConfiguration();
final Rect originalBounds = new Rect(mActivity.getBounds());
@@ -549,7 +599,7 @@
public void testSizeCompatMode_FixedScreenLayoutSizeBits() {
final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
| Configuration.SCREENLAYOUT_SIZE_NORMAL;
- mTask.getConfiguration().screenLayout = fixedScreenLayout
+ mTask.getRequestedOverrideConfiguration().screenLayout = fixedScreenLayout
| Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
prepareFixedAspectRatioUnresizableActivity();
@@ -579,7 +629,7 @@
mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));
// Simulate the display changes orientation.
- doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION
+ doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
| ActivityInfo.CONFIG_WINDOW_CONFIGURATION)
.when(display).getLastOverrideConfigurationChanges();
mActivity.onConfigurationChanged(mTask.getConfiguration());
@@ -689,12 +739,11 @@
* incorrect state.
*/
@Test
- public void testFinishActivityLocked_cancelled() {
+ public void testFinishActivityIfPossible_cancelled() {
// Mark activity as finishing
mActivity.finishing = true;
assertEquals("Duplicate finish request must be ignored", FINISH_RESULT_CANCELLED,
- mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test",
- false /* oomAdj */));
+ mActivity.finishIfPossible("test", false /* oomAdj */));
assertTrue(mActivity.finishing);
assertTrue(mActivity.isInStackLocked());
@@ -702,21 +751,19 @@
mActivity.finishing = false;
mActivity.setTask(null);
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_CANCELLED,
- mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test",
- false /* oomAdj */));
+ mActivity.finishIfPossible("test", false /* oomAdj */));
assertFalse(mActivity.finishing);
}
/**
- * Verify that activity finish request is requested, but not executed immediately if activity is
+ * Verify that activity finish request is placed, but not executed immediately if activity is
* not ready yet.
*/
@Test
- public void testFinishActivityLocked_requested() {
+ public void testFinishActivityIfPossible_requested() {
mActivity.finishing = false;
- assertEquals("Currently resumed activity be paused removal", FINISH_RESULT_REQUESTED,
- mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test",
- false /* oomAdj */));
+ assertEquals("Currently resumed activity must be prepared removal", FINISH_RESULT_REQUESTED,
+ mActivity.finishIfPossible("test", false /* oomAdj */));
assertTrue(mActivity.finishing);
assertTrue(mActivity.isInStackLocked());
@@ -725,8 +772,7 @@
mActivity.finishing = false;
mActivity.setState(STOPPED, "test");
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REQUESTED,
- mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test",
- false /* oomAdj */));
+ mActivity.finishIfPossible("test", false /* oomAdj */));
assertTrue(mActivity.finishing);
assertTrue(mActivity.isInStackLocked());
}
@@ -735,7 +781,7 @@
* Verify that activity finish request removes activity immediately if it's ready.
*/
@Test
- public void testFinishActivityLocked_removed() {
+ public void testFinishActivityIfPossible_removed() {
// Prepare the activity record to be ready for immediate removal. It should be invisible and
// have no process. Otherwise, request to finish it will send a message to client first.
mActivity.setState(STOPPED, "test");
@@ -745,38 +791,445 @@
// this will cause NPE when updating task's process.
mActivity.app = null;
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REMOVED,
- mActivity.finishActivityLocked(0 /* resultCode */, null /* resultData */, "test",
- false /* oomAdj */));
+ mActivity.finishIfPossible("test", false /* oomAdj */));
assertTrue(mActivity.finishing);
assertFalse(mActivity.isInStackLocked());
}
+ /**
+ * Verify that resumed activity is paused due to finish request.
+ */
+ @Test
+ public void testFinishActivityIfPossible_resumedStartsPausing() {
+ mActivity.finishing = false;
+ mActivity.setState(RESUMED, "test");
+ assertEquals("Currently resumed activity must be paused before removal",
+ FINISH_RESULT_REQUESTED, mActivity.finishIfPossible("test", false /* oomAdj */));
+ assertEquals(PAUSING, mActivity.getState());
+ verify(mActivity).setVisibility(eq(false));
+ verify(mActivity.getDisplay().mDisplayContent)
+ .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */);
+ }
+
+ /**
+ * Verify that finish request will be completed immediately for non-resumed activity.
+ */
+ @Test
+ public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() {
+ final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
+ for (ActivityState state : states) {
+ mActivity.finishing = false;
+ mActivity.setState(state, "test");
+ reset(mActivity);
+ assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
+ mActivity.finishIfPossible("test", false /* oomAdj */));
+ verify(mActivity).completeFinishing(anyString());
+ }
+ }
+
+ /**
+ * Verify that finishing will not be completed in PAUSING state.
+ */
+ @Test
+ public void testFinishActivityIfPossible_pausing() {
+ mActivity.finishing = false;
+ mActivity.setState(PAUSING, "test");
+ assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
+ mActivity.finishIfPossible("test", false /* oomAdj */));
+ verify(mActivity, never()).completeFinishing(anyString());
+ }
+
+ /**
+ * Verify that finish request for resumed activity will prepare an app transition but not
+ * execute it immediately.
+ */
+ @Test
+ public void testFinishActivityIfPossible_visibleResumedPreparesAppTransition() {
+ mActivity.finishing = false;
+ mActivity.visible = true;
+ mActivity.setState(RESUMED, "test");
+ mActivity.finishIfPossible("test", false /* oomAdj */);
+
+ verify(mActivity).setVisibility(eq(false));
+ verify(mActivity.getDisplay().mDisplayContent)
+ .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */);
+ verify(mActivity.getDisplay().mDisplayContent, never()).executeAppTransition();
+ }
+
+ /**
+ * Verify that finish request for paused activity will prepare and execute an app transition.
+ */
+ @Test
+ public void testFinishActivityIfPossible_visibleNotResumedExecutesAppTransition() {
+ mActivity.finishing = false;
+ mActivity.visible = true;
+ mActivity.setState(PAUSED, "test");
+ mActivity.finishIfPossible("test", false /* oomAdj */);
+
+ verify(mActivity).setVisibility(eq(false));
+ verify(mActivity.getDisplay().mDisplayContent)
+ .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */);
+ verify(mActivity.getDisplay().mDisplayContent).executeAppTransition();
+ }
+
+ /**
+ * Verify that finish request for non-visible activity will not prepare any transitions.
+ */
+ @Test
+ public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
+ // Put an activity on top of test activity to make it invisible and prevent us from
+ // accidentally resuming the topmost one again.
+ new ActivityBuilder(mService).build();
+ mActivity.visible = false;
+ mActivity.setState(STOPPED, "test");
+
+ mActivity.finishIfPossible("test", false /* oomAdj */);
+
+ verify(mActivity.getDisplay().mDisplayContent, never())
+ .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */);
+ }
+
+ /**
+ * Verify that complete finish request for non-finishing activity is invalid.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCompleteFinishing_failNotFinishing() {
+ mActivity.finishing = false;
+ mActivity.completeFinishing("test");
+ }
+
+ /**
+ * Verify that complete finish request for resumed activity is invalid.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCompleteFinishing_failResumed() {
+ mActivity.setState(RESUMED, "test");
+ mActivity.completeFinishing("test");
+ }
+
+ /**
+ * Verify that finish request for pausing activity must be a no-op - activity will finish
+ * once it completes pausing.
+ */
+ @Test
+ public void testCompleteFinishing_pausing() {
+ mActivity.setState(PAUSING, "test");
+ mActivity.finishing = true;
+
+ assertEquals("Activity must not be removed immediately - waiting for paused",
+ mActivity, mActivity.completeFinishing("test"));
+ assertEquals(PAUSING, mActivity.getState());
+ verify(mActivity, never()).destroyIfPossible(anyString());
+ }
+
+ /**
+ * Verify that complete finish request for visible activity must be delayed before the next one
+ * becomes visible.
+ */
+ @Test
+ public void testCompleteFinishing_waitForNextVisible() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.visible = true;
+ topActivity.nowVisible = true;
+ topActivity.finishing = true;
+ topActivity.setState(PAUSED, "true");
+ // Mark the bottom activity as not visible, so that we will wait for it before removing
+ // the top one.
+ mActivity.visible = false;
+ mActivity.nowVisible = false;
+ mActivity.setState(STOPPED, "test");
+
+ assertEquals("Activity must not be removed immediately - waiting for next visible",
+ topActivity, topActivity.completeFinishing("test"));
+ assertEquals("Activity must be stopped to make next one visible", STOPPING,
+ topActivity.getState());
+ assertTrue("Activity must be stopped to make next one visible",
+ topActivity.mStackSupervisor.mStoppingActivities.contains(topActivity));
+ verify(topActivity, never()).destroyIfPossible(anyString());
+ }
+
+ /**
+ * Verify that complete finish request for invisible activity must not be delayed.
+ */
+ @Test
+ public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.visible = false;
+ topActivity.nowVisible = false;
+ topActivity.finishing = true;
+ topActivity.setState(PAUSED, "true");
+ // Mark the bottom activity as not visible, so that we would wait for it before removing
+ // the top one.
+ mActivity.visible = false;
+ mActivity.nowVisible = false;
+ mActivity.setState(STOPPED, "test");
+
+ topActivity.completeFinishing("test");
+
+ verify(topActivity).destroyIfPossible(anyString());
+ }
+
+ /**
+ * Verify that paused finishing activity will be added to finishing list and wait for next one
+ * to idle.
+ */
+ @Test
+ public void testCompleteFinishing_waitForIdle() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.visible = true;
+ topActivity.nowVisible = true;
+ topActivity.finishing = true;
+ topActivity.setState(PAUSED, "true");
+ // Mark the bottom activity as already visible, so that there is no need to wait for it.
+ mActivity.visible = true;
+ mActivity.nowVisible = true;
+ mActivity.setState(RESUMED, "test");
+
+ topActivity.completeFinishing("test");
+
+ verify(topActivity).addToFinishingAndWaitForIdle();
+ }
+
+ /**
+ * Verify that complete finish request for visible activity must not be delayed if the next one
+ * is already visible and it's not the focused stack.
+ */
+ @Test
+ public void testCompleteFinishing_noWaitForNextVisible_stopped() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.visible = false;
+ topActivity.nowVisible = false;
+ topActivity.finishing = true;
+ topActivity.setState(STOPPED, "true");
+ // Mark the bottom activity as already visible, so that there is no need to wait for it.
+ mActivity.visible = true;
+ mActivity.nowVisible = true;
+ mActivity.setState(RESUMED, "test");
+
+ topActivity.completeFinishing("test");
+
+ verify(topActivity).destroyIfPossible(anyString());
+ }
+
+ /**
+ * Verify that complete finish request for visible activity must not be delayed if the next one
+ * is already visible and it's not the focused stack.
+ */
+ @Test
+ public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ topActivity.visible = true;
+ topActivity.nowVisible = true;
+ topActivity.finishing = true;
+ topActivity.setState(PAUSED, "true");
+ // Mark the bottom activity as already visible, so that there is no need to wait for it.
+ mActivity.visible = true;
+ mActivity.nowVisible = true;
+ mActivity.setState(RESUMED, "test");
+
+ // Add another stack to become focused and make the activity there visible. This way it
+ // simulates finishing in non-focused stack in split-screen.
+ final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
+ stack.getChildAt(0).getChildAt(0).nowVisible = true;
+
+ topActivity.completeFinishing("test");
+
+ verify(topActivity).destroyIfPossible(anyString());
+ }
+
+ /**
+ * Verify destroy activity request completes successfully.
+ */
+ @Test
+ public void testDestroyIfPossible() {
+ doReturn(false).when(mRootActivityContainer).resumeFocusedStacksTopActivities();
+ spyOn(mStack);
+ mActivity.destroyIfPossible("test");
+
+ assertEquals(DESTROYING, mActivity.getState());
+ assertTrue(mActivity.finishing);
+ verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+ }
+
+ /**
+ * Verify that complete finish request for visible activity must not destroy it immediately if
+ * it is the last running activity on a display with a home stack. We must wait for home
+ * activity to come up to avoid a black flash in this case.
+ */
+ @Test
+ public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
+ // Empty the home stack.
+ final ActivityStack homeStack = mActivity.getDisplay().getHomeStack();
+ for (TaskRecord t : homeStack.getAllTasks()) {
+ homeStack.removeTask(t, "test", REMOVE_TASK_MODE_DESTROYING);
+ }
+ mActivity.finishing = true;
+ doReturn(false).when(mRootActivityContainer).resumeFocusedStacksTopActivities();
+ spyOn(mStack);
+
+ // Try to destroy the last activity above the home stack.
+ mActivity.destroyIfPossible("test");
+
+ // Verify that the activity was not actually destroyed, but waits for next one to come up
+ // instead.
+ verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+ assertEquals(FINISHING, mActivity.getState());
+ assertTrue(mActivity.mStackSupervisor.mFinishingActivities.contains(mActivity));
+ }
+
+ /**
+ * Test that the activity will be moved to destroying state and the message to destroy will be
+ * sent to the client.
+ */
+ @Test
+ public void testDestroyImmediately_hadApp_finishing() {
+ mActivity.finishing = true;
+ mActivity.destroyImmediately(false /* removeFromApp */, "test");
+
+ assertEquals(DESTROYING, mActivity.getState());
+ }
+
+ /**
+ * Test that the activity will be moved to destroyed state immediately if it was not marked as
+ * finishing before {@link ActivityRecord#destroyImmediately(boolean, String)}.
+ */
+ @Test
+ public void testDestroyImmediately_hadApp_notFinishing() {
+ mActivity.finishing = false;
+ mActivity.destroyImmediately(false /* removeFromApp */, "test");
+
+ assertEquals(DESTROYED, mActivity.getState());
+ }
+
+ /**
+ * Test that an activity with no process attached and that is marked as finishing will be
+ * removed from task when {@link ActivityRecord#destroyImmediately(boolean, String)} is called.
+ */
+ @Test
+ public void testDestroyImmediately_noApp_finishing() {
+ mActivity.app = null;
+ mActivity.finishing = true;
+ final TaskRecord task = mActivity.getTaskRecord();
+
+ mActivity.destroyImmediately(false /* removeFromApp */, "test");
+
+ assertEquals(DESTROYED, mActivity.getState());
+ assertNull(mActivity.getTaskRecord());
+ assertEquals(0, task.getChildCount());
+ }
+
+ /**
+ * Test that an activity with no process attached and that is not marked as finishing will be
+ * marked as DESTROYED but not removed from task.
+ */
+ @Test
+ public void testDestroyImmediately_noApp_notFinishing() {
+ mActivity.app = null;
+ mActivity.finishing = false;
+ final TaskRecord task = mActivity.getTaskRecord();
+
+ mActivity.destroyImmediately(false /* removeFromApp */, "test");
+
+ assertEquals(DESTROYED, mActivity.getState());
+ assertEquals(task, mActivity.getTaskRecord());
+ assertEquals(1, task.getChildCount());
+ }
+
+ /**
+ * Test that an activity will not be destroyed if it is marked as non-destroyable.
+ */
+ @Test
+ public void testSafelyDestroy_nonDestroyable() {
+ doReturn(false).when(mActivity).isDestroyable();
+
+ mActivity.safelyDestroy("test");
+
+ verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+ }
+
+ /**
+ * Test that an activity will not be destroyed if it is marked as non-destroyable.
+ */
+ @Test
+ public void testSafelyDestroy_destroyable() {
+ doReturn(true).when(mActivity).isDestroyable();
+
+ mActivity.safelyDestroy("test");
+
+ verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+ }
+
+ @Test
+ public void testRemoveFromHistory() {
+ final ActivityStack stack = mActivity.getActivityStack();
+ final TaskRecord task = mActivity.getTaskRecord();
+
+ mActivity.removeFromHistory("test");
+
+ assertEquals(DESTROYED, mActivity.getState());
+ assertNull(mActivity.app);
+ assertNull(mActivity.getTaskRecord());
+ assertEquals(0, task.getChildCount());
+ assertNull(task.getStack());
+ assertEquals(0, stack.getChildCount());
+ }
+
+ /**
+ * Test that it's not allowed to call {@link ActivityRecord#destroyed(String)} if activity is
+ * not in destroying or destroyed state.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testDestroyed_notDestroying() {
+ mActivity.setState(STOPPED, "test");
+ mActivity.destroyed("test");
+ }
+
+ /**
+ * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroying
+ */
+ @Test
+ public void testDestroyed_destroying() {
+ mActivity.setState(DESTROYING, "test");
+ mActivity.destroyed("test");
+
+ verify(mActivity).removeFromHistory(anyString());
+ }
+
+ /**
+ * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroyed.
+ */
+ @Test
+ public void testDestroyed_destroyed() {
+ mActivity.setState(DESTROYED, "test");
+ mActivity.destroyed("test");
+
+ verify(mActivity).removeFromHistory(anyString());
+ }
+
/** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
private void prepareFixedAspectRatioUnresizableActivity() {
setupDisplayContentForCompatDisplayInsets();
- when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
mActivity.info.maxAspectRatio = 1.5f;
+ mActivity.visible = true;
ensureActivityConfiguration();
}
private void setupDisplayContentForCompatDisplayInsets() {
final Rect displayBounds = mStack.getDisplay().getBounds();
- final DisplayContent displayContent = setupDisplayAndParentSize(
- displayBounds.width(), displayBounds.height());
- doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy();
- doReturn(mock(WmDisplayCutout.class)).when(displayContent)
- .calculateDisplayCutoutForRotation(anyInt());
+ setupDisplayAndParentSize(displayBounds.width(), displayBounds.height());
}
private DisplayContent setupDisplayAndParentSize(int width, int height) {
- // The DisplayContent is already a mocked object.
final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
displayContent.mBaseDisplayWidth = width;
displayContent.mBaseDisplayHeight = height;
- mTask.getWindowConfiguration().setAppBounds(0, 0, width, height);
- mTask.getWindowConfiguration().setRotation(ROTATION_0);
+ final Configuration c =
+ new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
+ c.windowConfiguration.setBounds(new Rect(0, 0, width, height));
+ c.windowConfiguration.setAppBounds(0, 0, width, height);
+ c.windowConfiguration.setRotation(ROTATION_0);
+ mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
return displayContent;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index e5278d8..60c5f0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.Activity.RESULT_CANCELED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -29,11 +28,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
import static com.android.server.wm.ActivityStack.ActivityState.FINISHING;
-import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
@@ -44,6 +42,7 @@
import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
import static com.google.common.truth.Truth.assertThat;
@@ -83,8 +82,9 @@
@Before
public void setUp() throws Exception {
mDefaultDisplay = mRootActivityContainer.getDefaultDisplay();
- mStack = spy(mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
- true /* onTop */));
+ mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ spyOn(mStack);
mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
}
@@ -140,10 +140,8 @@
final ActivityStack destStack = mRootActivityContainer.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build();
-
- mTask.removeActivity(r);
- destTask.addActivityToTop(r);
+ mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false,
+ "testResumedActivityFromActivityReparenting");
assertNull(mStack.getResumedActivity());
assertEquals(r, destStack.getResumedActivity());
@@ -235,7 +233,7 @@
final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
mStack.moveToFront("testStopActivityWithDestroy");
- mStack.stopActivityLocked(r);
+ r.stopIfPossible();
// Mostly testing to make sure there is a crash in the call part, so if we get here we are
// good-to-go!
}
@@ -313,45 +311,50 @@
@Test
public void testShouldBeVisible_Fullscreen() {
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // Add an activity to the pinned stack so it isn't considered empty for visibility check.
+ final ActivityRecord pinnedActivity = new ActivityBuilder(mService)
+ .setCreateTask(true)
+ .setStack(pinnedStack)
+ .build();
assertTrue(homeStack.shouldBeVisible(null /* starting */));
assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
- final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
// should be visible since it is always on-top.
- fullscreenStack.setIsTranslucent(false);
+ doReturn(false).when(fullscreenStack).isStackTranslucent(any());
assertFalse(homeStack.shouldBeVisible(null /* starting */));
assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
// Home stack should be visible behind a translucent fullscreen stack.
- fullscreenStack.setIsTranslucent(true);
+ doReturn(true).when(fullscreenStack).isStackTranslucent(any());
assertTrue(homeStack.shouldBeVisible(null /* starting */));
assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
}
@Test
public void testShouldBeVisible_SplitScreen() {
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
// Home stack should always be fullscreen for this test.
- homeStack.setSupportsSplitScreen(false);
- final TestActivityStack splitScreenPrimary =
+ doReturn(false).when(homeStack).supportsSplitScreenWindowingMode();
+ final ActivityStack splitScreenPrimary =
createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final TestActivityStack splitScreenSecondary =
+ final ActivityStack splitScreenSecondary =
createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Home stack shouldn't be visible if both halves of split-screen are opaque.
- splitScreenPrimary.setIsTranslucent(false);
- splitScreenSecondary.setIsTranslucent(false);
+ doReturn(false).when(splitScreenPrimary).isStackTranslucent(any());
+ doReturn(false).when(splitScreenSecondary).isStackTranslucent(any());
assertFalse(homeStack.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -362,7 +365,7 @@
splitScreenSecondary.getVisibility(null /* starting */));
// Home stack should be visible if one of the halves of split-screen is translucent.
- splitScreenPrimary.setIsTranslucent(true);
+ doReturn(true).when(splitScreenPrimary).isStackTranslucent(any());
assertTrue(homeStack.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -373,12 +376,12 @@
assertEquals(STACK_VISIBILITY_VISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- final TestActivityStack splitScreenSecondary2 =
+ final ActivityStack splitScreenSecondary2 =
createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// First split-screen secondary shouldn't be visible behind another opaque split-split
// secondary.
- splitScreenSecondary2.setIsTranslucent(false);
+ doReturn(false).when(splitScreenSecondary2).isStackTranslucent(any());
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(STACK_VISIBILITY_INVISIBLE,
@@ -388,7 +391,7 @@
// First split-screen secondary should be visible behind another translucent split-screen
// secondary.
- splitScreenSecondary2.setIsTranslucent(true);
+ doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any());
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -396,11 +399,11 @@
assertEquals(STACK_VISIBILITY_VISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
- final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
// Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
- assistantStack.setIsTranslucent(false);
+ doReturn(false).when(assistantStack).isStackTranslucent(any());
assertTrue(assistantStack.shouldBeVisible(null /* starting */));
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -415,7 +418,7 @@
splitScreenSecondary2.getVisibility(null /* starting */));
// Split-screen stacks should be visible behind a translucent fullscreen stack.
- assistantStack.setIsTranslucent(true);
+ doReturn(true).when(assistantStack).isStackTranslucent(any());
assertTrue(assistantStack.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -430,9 +433,9 @@
splitScreenSecondary2.getVisibility(null /* starting */));
// Assistant stack shouldn't be visible behind translucent split-screen stack
- assistantStack.setIsTranslucent(false);
- splitScreenPrimary.setIsTranslucent(true);
- splitScreenSecondary2.setIsTranslucent(true);
+ doReturn(false).when(assistantStack).isStackTranslucent(any());
+ doReturn(true).when(splitScreenPrimary).isStackTranslucent(any());
+ doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any());
splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen");
splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen");
assertFalse(assistantStack.shouldBeVisible(null /* starting */));
@@ -450,10 +453,10 @@
@Test
public void testGetVisibility_FullscreenBehindTranslucent() {
- final TestActivityStack bottomStack =
+ final ActivityStack bottomStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final TestActivityStack translucentStack =
+ final ActivityStack translucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
@@ -465,13 +468,13 @@
@Test
public void testGetVisibility_FullscreenBehindTranslucentAndOpaque() {
- final TestActivityStack bottomStack =
+ final ActivityStack bottomStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final TestActivityStack translucentStack =
+ final ActivityStack translucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final TestActivityStack opaqueStack =
+ final ActivityStack opaqueStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
@@ -483,13 +486,13 @@
@Test
public void testGetVisibility_FullscreenBehindOpaqueAndTranslucent() {
- final TestActivityStack bottomStack =
+ final ActivityStack bottomStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final TestActivityStack opaqueStack =
+ final ActivityStack opaqueStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final TestActivityStack translucentStack =
+ final ActivityStack translucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
@@ -502,10 +505,10 @@
@Test
public void testGetVisibility_FullscreenTranslucentBehindTranslucent() {
- final TestActivityStack bottomTranslucentStack =
+ final ActivityStack bottomTranslucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final TestActivityStack translucentStack =
+ final ActivityStack translucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
@@ -517,10 +520,10 @@
@Test
public void testGetVisibility_FullscreenTranslucentBehindOpaque() {
- final TestActivityStack bottomTranslucentStack =
+ final ActivityStack bottomTranslucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final TestActivityStack opaqueStack =
+ final ActivityStack opaqueStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
@@ -531,10 +534,10 @@
@Test
public void testGetVisibility_FullscreenBehindTranslucentAndPip() {
- final TestActivityStack bottomStack =
+ final ActivityStack bottomStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final TestActivityStack translucentStack =
+ final ActivityStack translucentStack =
createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
@@ -544,22 +547,34 @@
bottomStack.getVisibility(null /* starting */));
assertEquals(STACK_VISIBILITY_VISIBLE,
translucentStack.getVisibility(null /* starting */));
+ // Add an activity to the pinned stack so it isn't considered empty for visibility check.
+ final ActivityRecord pinnedActivity = new ActivityBuilder(mService)
+ .setCreateTask(true)
+ .setStack(pinnedStack)
+ .build();
assertEquals(STACK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */));
}
@Test
public void testShouldBeVisible_Finishing() {
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(
+ ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ if (topRunningHomeActivity == null) {
+ topRunningHomeActivity = new ActivityBuilder(mService)
+ .setStack(homeStack)
+ .setCreateTask(true)
+ .build();
+ }
+
+ final ActivityStack translucentStack = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- translucentStack.setIsTranslucent(true);
+ doReturn(true).when(translucentStack).isStackTranslucent(any());
assertTrue(homeStack.shouldBeVisible(null /* starting */));
assertTrue(translucentStack.shouldBeVisible(null /* starting */));
- final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
topRunningHomeActivity.finishing = true;
final ActivityRecord topRunningTranslucentActivity =
translucentStack.topRunningActivityLocked();
@@ -577,13 +592,13 @@
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack.setIsTranslucent(false);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack).isStackTranslucent(any());
// Ensure that we don't move the home stack if it is already behind the top fullscreen stack
int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -596,13 +611,13 @@
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack.setIsTranslucent(true);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(true).when(fullscreenStack).isStackTranslucent(any());
// Ensure that we don't move the home stack if it is already behind the top fullscreen stack
int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -615,13 +630,13 @@
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack.setIsTranslucent(false);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack).isStackTranslucent(any());
// Ensure we don't move the home stack if it is already on top
int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -634,20 +649,20 @@
public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack1.setIsTranslucent(false);
- fullscreenStack2.setIsTranslucent(false);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack2).isStackTranslucent(any());
// Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
// pinned stack
@@ -661,18 +676,18 @@
testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack1.setIsTranslucent(false);
- fullscreenStack2.setIsTranslucent(true);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
+ doReturn(true).when(fullscreenStack2).isStackTranslucent(any());
// Ensure that we move the home stack behind the bottom most non-translucent fullscreen
// stack
@@ -685,18 +700,18 @@
public void testMoveHomeStackBehindStack_BehindHomeStack() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- homeStack.setIsTranslucent(false);
- fullscreenStack1.setIsTranslucent(false);
- fullscreenStack2.setIsTranslucent(false);
+ doReturn(false).when(homeStack).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
+ doReturn(false).when(fullscreenStack2).isStackTranslucent(any());
// Ensure we don't move the home stack behind itself
int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -708,19 +723,19 @@
public void testMoveHomeStackBehindStack() {
mDefaultDisplay.removeChild(mStack);
- final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1);
@@ -735,13 +750,13 @@
@Test
public void testSetAlwaysOnTop() {
- final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
assertEquals(pinnedStack, mDefaultDisplay.getStackAbove(homeStack));
- final TestActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest(
+ final ActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
alwaysOnTopStack.setAlwaysOnTop(true);
@@ -749,13 +764,13 @@
// Ensure (non-pinned) always on top stack is put below pinned stack.
assertEquals(pinnedStack, mDefaultDisplay.getStackAbove(alwaysOnTopStack));
- final TestActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest(
+ final ActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
// Ensure non always on top stack is put below always on top stacks.
assertEquals(alwaysOnTopStack, mDefaultDisplay.getStackAbove(nonAlwaysOnTopStack));
- final TestActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest(
+ final ActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
alwaysOnTopStack2.setAlwaysOnTop(true);
@@ -780,18 +795,18 @@
@Test
public void testSplitScreenMoveToFront() {
- final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(
+ final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(
+ final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
- splitScreenPrimary.setIsTranslucent(false);
- splitScreenSecondary.setIsTranslucent(false);
- assistantStack.setIsTranslucent(false);
+ doReturn(false).when(splitScreenPrimary).isStackTranslucent(any());
+ doReturn(false).when(splitScreenSecondary).isStackTranslucent(any());
+ doReturn(false).when(assistantStack).isStackTranslucent(any());
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -804,18 +819,18 @@
assertFalse(assistantStack.shouldBeVisible(null /* starting */));
}
- private TestActivityStack createStandardStackForVisibilityTest(int windowingMode,
+ private ActivityStack createStandardStackForVisibilityTest(int windowingMode,
boolean translucent) {
- final TestActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay,
windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- stack.setIsTranslucent(translucent);
+ doReturn(translucent).when(stack).isStackTranslucent(any());
return stack;
}
@SuppressWarnings("TypeParameterUnusedInFormals")
private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
- final T stack;
+ final ActivityStack stack;
if (activityType == ACTIVITY_TYPE_HOME) {
// Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
@@ -825,11 +840,15 @@
mDefaultDisplay.positionChildAtBottom(stack);
}
} else {
- stack = display.createStack(windowingMode, activityType, onTop);
- final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack)
- .setCreateTask(true).build();
+ stack = new StackBuilder(mRootActivityContainer)
+ .setDisplay(display)
+ .setWindowingMode(windowingMode)
+ .setActivityType(activityType)
+ .setOnTop(onTop)
+ .setCreateActivity(true)
+ .build();
}
- return stack;
+ return (T) stack;
}
@Test
@@ -860,7 +879,7 @@
final ActivityRecord overlayActivity = new ActivityBuilder(mService).setTask(mTask)
.setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build();
// If the task only remains overlay activity, the task should also be removed.
- // See {@link ActivityStack#removeActivityFromHistoryLocked}.
+ // See {@link ActivityStack#removeFromHistory}.
overlayActivity.mTaskOverlay = true;
// The activity without an app means it will be removed immediately.
@@ -961,17 +980,21 @@
@Test
public void testAdjustFocusedStackToHomeWhenNoActivity() {
+ final ActivityStack homeStask = mDefaultDisplay.getHomeStack();
+ TaskRecord homeTask = homeStask.topTask();
+ if (homeTask == null) {
+ // Create home task if there isn't one.
+ homeTask = new TaskBuilder(mSupervisor).setStack(homeStask).build();
+ }
+
final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
mStack.moveToFront("testAdjustFocusedStack");
- final ActivityStack homeStask = mDefaultDisplay.getHomeStack();
- final TaskRecord homeTask = homeStask.topTask();
// Simulate that home activity has not been started or is force-stopped.
homeStask.removeTask(homeTask, "testAdjustFocusedStack", REMOVE_TASK_MODE_DESTROYING);
// Finish the only activity.
- topActivity.finishActivityLocked(RESULT_CANCELED /* resultCode */, null /* resultData */,
- "testAdjustFocusedStack", false /* oomAdj */);
+ topActivity.finishIfPossible("testAdjustFocusedStack", false /* oomAdj */);
// Although home stack is empty, it should still be the focused stack.
assertEquals(homeStask, mDefaultDisplay.getFocusedStack());
}
@@ -981,8 +1004,16 @@
final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ ActivityRecord activity = homeStack.topRunningActivityLocked();
+ if (activity == null) {
+ activity = new ActivityBuilder(mService)
+ .setStack(homeStack)
+ .setCreateTask(true)
+ .build();
+ }
+
// Home stack should not be destroyed immediately.
- final ActivityRecord activity1 = finishCurrentActivity(homeStack);
+ final ActivityRecord activity1 = finishTopActivity(homeStack);
assertEquals(FINISHING, activity1.getState());
}
@@ -998,25 +1029,24 @@
// There is still an activity1 in stack1 so the activity2 should be added to finishing list
// that will be destroyed until idle.
stack2.getTopActivity().visible = true;
- final ActivityRecord activity2 = finishCurrentActivity(stack2);
+ final ActivityRecord activity2 = finishTopActivity(stack2);
assertEquals(STOPPING, activity2.getState());
assertThat(mSupervisor.mStoppingActivities).contains(activity2);
// The display becomes empty. Since there is no next activity to be idle, the activity
// should be destroyed immediately with updating configuration to restore original state.
- final ActivityRecord activity1 = finishCurrentActivity(stack1);
+ final ActivityRecord activity1 = finishTopActivity(stack1);
assertEquals(DESTROYING, activity1.getState());
verify(mRootActivityContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
eq(display.mDisplayId), anyBoolean(), anyBoolean());
}
- private ActivityRecord finishCurrentActivity(ActivityStack stack) {
+ private ActivityRecord finishTopActivity(ActivityStack stack) {
final ActivityRecord activity = stack.topRunningActivityLocked();
assertNotNull(activity);
- activity.setState(PAUSED, "finishCurrentActivity");
+ activity.setState(STOPPED, "finishTopActivity");
activity.makeFinishingLocked();
- activity.finishCurrentActivityLocked(ActivityRecord.FINISH_AFTER_VISIBLE,
- false /* oomAdj */, "finishCurrentActivity");
+ activity.completeFinishing("finishTopActivity");
return activity;
}
@@ -1068,7 +1098,7 @@
public void testStackOrderChangedOnPositionStack() {
StackOrderChangedListener listener = new StackOrderChangedListener();
try {
- final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
mDefaultDisplay.registerStackOrderChangedListener(listener);
@@ -1079,6 +1109,19 @@
assertTrue(listener.mChanged);
}
+ @Test
+ public void testResetTaskWithFinishingActivities() {
+ final ActivityRecord taskTop =
+ new ActivityBuilder(mService).setStack(mStack).setCreateTask(true).build();
+ // Make all activities in the task are finishing to simulate TaskRecord#getTopActivity
+ // returns null.
+ taskTop.finishing = true;
+
+ final ActivityRecord newR = new ActivityBuilder(mService).build();
+ final ActivityRecord result = mStack.resetTaskIfNeededLocked(taskTop, newR);
+ assertThat(result).isEqualTo(taskTop);
+ }
+
private void verifyShouldSleepActivities(boolean focusedStack,
boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
final ActivityDisplay display = mock(ActivityDisplay.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 3d94467..81fbfe4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -79,6 +79,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
+import com.android.server.wm.utils.MockTracker;
import org.junit.Before;
import org.junit.Test;
@@ -186,6 +187,19 @@
verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult);
}
+ private void verifyStartActivityPreconditions(int preconditions, int launchFlags,
+ int expectedResult) {
+ // We track mocks created here because this is used in a single test
+ // (testStartActivityPreconditions) as a specific case, and mocks created inside it won't be
+ // used for other cases. To avoid extensive memory usage, we clean up all used mocks after
+ // each case. This is necessary because usually we only clean up mocks after a test
+ // finishes, but this test creates too many mocks that the intermediate memory usage can be
+ // ~0.8 GiB and thus very susceptible to OutOfMemoryException.
+ try (MockTracker tracker = new MockTracker()) {
+ verifyStartActivityPreconditionsUntracked(preconditions, launchFlags, expectedResult);
+ }
+ }
+
/**
* Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller
* provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP})
@@ -197,7 +211,7 @@
* @param launchFlags The launch flags to be provided by the launch {@link Intent}.
* @param expectedResult The expected result from the launch.
*/
- private void verifyStartActivityPreconditions(int preconditions, int launchFlags,
+ private void verifyStartActivityPreconditionsUntracked(int preconditions, int launchFlags,
int expectedResult) {
final ActivityTaskManagerService service = mService;
final IPackageManager packageManager = mock(IPackageManager.class);
@@ -329,9 +343,6 @@
any(), any(), any(), anyInt(), anyInt(), anyInt(), any(),
anyBoolean(), anyBoolean(), any(), any(), any());
- // Use factory that only returns spy task.
- mockTaskRecordFactory();
-
if (mockGetLaunchStack) {
// Instrument the stack and task used.
final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack(
@@ -482,7 +493,7 @@
@Test
public void testTaskModeViolation() {
final ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
- ((TestActivityDisplay) display).removeAllTasks();
+ display.removeAllTasks();
assertNoTasks(display);
final ActivityStarter starter = prepareStarter(0);
@@ -676,18 +687,27 @@
doReturn(isCallingUidDeviceOwner).when(mService).isDeviceOwner(callingUid);
final ActivityOptions options = spy(ActivityOptions.makeBasic());
+ ActivityRecord[] outActivity = new ActivityRecord[1];
ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK)
.setCallingPackage("com.whatever.dude")
.setCaller(caller)
.setCallingUid(callingUid)
.setRealCallingUid(realCallingUid)
- .setActivityOptions(new SafeActivityOptions(options));
+ .setActivityOptions(new SafeActivityOptions(options))
+ .setOutActivity(outActivity);
final int result = starter.setReason("testBackgroundActivityStarts_" + name).execute();
assertEquals(ActivityStarter.getExternalResult(
shouldHaveAborted ? START_ABORTED : START_SUCCESS), result);
verify(options, times(shouldHaveAborted ? 1 : 0)).abort();
+
+ final ActivityRecord startedActivity = outActivity[0];
+ if (startedActivity != null && startedActivity.getTaskRecord() != null) {
+ // Remove the activity so it doesn't interfere with with subsequent activity launch
+ // tests from this method.
+ startedActivity.getTaskRecord().removeActivity(startedActivity);
+ }
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index d8a9bb0..297aa7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -45,8 +45,7 @@
/** Verify that activity is finished correctly upon request. */
@Test
public void testActivityFinish() {
- final TestActivityStack stack =
- (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
+ final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
final ActivityRecord activity = stack.getChildAt(0).getTopActivity();
assertTrue("Activity must be finished", mService.finishActivity(activity.appToken,
0 /* resultCode */, null /* resultData */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index ab2da2b..a5dc241 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -16,96 +16,58 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
-import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
-import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.testing.DexmakerShareClassLoaderRule;
-import android.view.Display;
import android.view.DisplayInfo;
-import com.android.internal.app.IVoiceInteractor;
import com.android.server.AttributeCache;
-import com.android.server.ServiceThread;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.am.PendingIntentController;
-import com.android.server.appop.AppOpsService;
-import com.android.server.firewall.IntentFirewall;
-import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.server.wm.TaskRecord.TaskRecordFactory;
-import com.android.server.wm.utils.MockTracker;
-import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
-import org.mockito.invocation.InvocationOnMock;
-
-import java.io.File;
-import java.util.List;
-import java.util.function.Consumer;
/**
* A base class to handle common operations in activity related unit tests.
*/
class ActivityTestsBase {
- private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule
+ public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
+
final Context mContext = getInstrumentation().getTargetContext();
- final TestInjector mTestInjector = new TestInjector(mContext);
ActivityTaskManagerService mService;
RootActivityContainer mRootActivityContainer;
ActivityStackSupervisor mSupervisor;
- private MockTracker mMockTracker;
-
// Default package name
static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
@@ -119,37 +81,18 @@
@Before
public void setUpBase() {
- mMockTracker = new MockTracker();
-
- mTestInjector.setUp();
-
- mService = new TestActivityTaskManagerService(mContext);
+ mService = mSystemServicesTestRule.getActivityTaskManagerService();
mSupervisor = mService.mStackSupervisor;
mRootActivityContainer = mService.mRootActivityContainer;
}
- @After
- public void tearDownBase() {
- mTestInjector.tearDown();
- if (mService != null) {
- mService.setWindowManager(null);
- mService = null;
- }
- if (sMockWindowManagerService != null) {
- reset(sMockWindowManagerService);
- }
-
- mMockTracker.close();
- mMockTracker = null;
- }
-
/** Creates a {@link TestActivityDisplay}. */
TestActivityDisplay createNewActivityDisplay() {
- return TestActivityDisplay.create(mSupervisor, sNextDisplayId++);
+ return TestActivityDisplay.create(mSupervisor);
}
TestActivityDisplay createNewActivityDisplay(DisplayInfo info) {
- return TestActivityDisplay.create(mSupervisor, sNextDisplayId++, info);
+ return TestActivityDisplay.create(mSupervisor, info);
}
/** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
@@ -166,25 +109,9 @@
return display;
}
- /**
- * Delegates task creation to {@link #TaskBuilder} to avoid the dependency of window hierarchy
- * when starting activity in unit tests.
- */
- void mockTaskRecordFactory(Consumer<TaskBuilder> taskBuilderSetup) {
- final TaskBuilder taskBuilder = new TaskBuilder(mSupervisor).setCreateStack(false);
- if (taskBuilderSetup != null) {
- taskBuilderSetup.accept(taskBuilder);
- }
- final TaskRecord task = taskBuilder.build();
- final TaskRecordFactory factory = mock(TaskRecordFactory.class);
- TaskRecord.setTaskRecordFactory(factory);
- doReturn(task).when(factory).create(any() /* service */, anyInt() /* taskId */,
- any() /* info */, any() /* intent */, any() /* voiceSession */,
- any() /* voiceInteractor */);
- }
-
- void mockTaskRecordFactory() {
- mockTaskRecordFactory(null /* taskBuilderSetup */);
+ /** Sets the default minimum task size to 1 so that tests can use small task sizes */
+ public void removeGlobalMinSizeRestriction() {
+ mService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
}
/**
@@ -204,6 +131,11 @@
private ActivityStack mStack;
private int mActivityFlags;
private int mLaunchMode;
+ private int mResizeMode = RESIZE_MODE_RESIZEABLE;
+ private float mMaxAspectRatio;
+ private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ private boolean mLaunchTaskBehind;
+ private int mConfigChanges;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
@@ -254,6 +186,31 @@
return this;
}
+ ActivityBuilder setResizeMode(int resizeMode) {
+ mResizeMode = resizeMode;
+ return this;
+ }
+
+ ActivityBuilder setMaxAspectRatio(float maxAspectRatio) {
+ mMaxAspectRatio = maxAspectRatio;
+ return this;
+ }
+
+ ActivityBuilder setScreenOrientation(int screenOrientation) {
+ mScreenOrientation = screenOrientation;
+ return this;
+ }
+
+ ActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
+ mLaunchTaskBehind = launchTaskBehind;
+ return this;
+ }
+
+ ActivityBuilder setConfigChanges(int configChanges) {
+ mConfigChanges = configChanges;
+ return this;
+ }
+
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
@@ -279,24 +236,31 @@
}
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
+ aInfo.resizeMode = mResizeMode;
+ aInfo.maxAspectRatio = mMaxAspectRatio;
+ aInfo.screenOrientation = mScreenOrientation;
+ aInfo.configChanges |= mConfigChanges;
+
+ ActivityOptions options = null;
+ if (mLaunchTaskBehind) {
+ options = ActivityOptions.makeTaskLaunchBehind();
+ }
final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
0 /* launchedFromPid */, 0, null, intent, null,
aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
- mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
+ mService.mStackSupervisor, options, null /* sourceRecord */);
spyOn(activity);
- activity.mAppWindowToken = mock(AppWindowToken.class);
- doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility();
- doCallRealMethod().when(activity.mAppWindowToken)
- .setOrientation(anyInt(), any(), any());
- doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
- doNothing().when(activity).removeWindowContainer();
- doReturn(mock(Configuration.class)).when(activity.mAppWindowToken)
- .getRequestedOverrideConfiguration();
-
if (mTaskRecord != null) {
- mTaskRecord.addActivityToTop(activity);
+ // fullscreen value is normally read from resources in ctor, so for testing we need
+ // to set it somewhere else since we can't mock resources.
+ activity.fullscreen = true;
+ activity.setTask(mTaskRecord);
+ activity.createAppWindowToken();
+ spyOn(activity.mAppWindowToken);
+ // Make visible by default...
+ activity.mAppWindowToken.setHidden(false);
}
final WindowProcessController wpc = new WindowProcessController(mService,
@@ -305,6 +269,9 @@
mock(WindowProcessListener.class));
wpc.setThread(mock(IApplicationThread.class));
activity.setProcess(wpc);
+
+ // Resume top activities to make sure all other signals in the system are connected.
+ mService.mRootActivityContainer.resumeFocusedStacksTopActivities();
return activity;
}
}
@@ -313,16 +280,13 @@
* Builder for creating new tasks.
*/
protected static class TaskBuilder {
- // Default package name
- static final String DEFAULT_PACKAGE = "com.bar";
-
private final ActivityStackSupervisor mSupervisor;
private ComponentName mComponent;
private String mPackage;
private int mFlags = 0;
// Task id 0 is reserved in ARC for the home app.
- private int mTaskId = 1;
+ private int mTaskId = SystemServicesTestRule.sNextTaskId++;
private int mUserId = 0;
private IVoiceInteractionSession mVoiceSession;
private boolean mCreateStack = true;
@@ -381,6 +345,7 @@
if (mStack == null && mCreateStack) {
mStack = mSupervisor.mRootActivityContainer.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ spyOn(mStack);
}
final ActivityInfo aInfo = new ActivityInfo();
@@ -396,450 +361,22 @@
intent.setComponent(mComponent);
intent.setFlags(mFlags);
- final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
+ final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
+ spyOn(task);
task.userId = mUserId;
if (mStack != null) {
mStack.moveToFront("test");
mStack.addTask(task, true, "creating test task");
- task.setStack(mStack);
- task.setTask();
- mStack.getTaskStack().addChild(task.mTask, 0);
+ task.createTask(true, true);
+ spyOn(task.mTask);
}
task.touchActiveTime();
return task;
}
-
- private static class TestTaskRecord extends TaskRecord {
- TestTaskRecord(ActivityTaskManagerService service, int taskId, ActivityInfo info,
- Intent intent, IVoiceInteractionSession voiceSession,
- IVoiceInteractor voiceInteractor) {
- super(service, taskId, info, intent, voiceSession, voiceInteractor);
- }
-
- @Override
- void createTask(boolean onTop, boolean showForAllUsers) {
- setTask();
- }
-
- void setTask() {
- Task mockTask = mock(Task.class);
- mockTask.mTaskRecord = this;
- doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any());
- setTask(mock(Task.class));
- }
- }
- }
-
- protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
- private PackageManagerInternal mPmInternal;
- private PermissionPolicyInternal mPermissionPolicyInternal;
-
- // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
- // We keep the reference in order to prevent creating it twice.
- ActivityStackSupervisor mTestStackSupervisor;
-
- ActivityDisplay mDefaultDisplay;
- AppOpsService mAppOpsService;
-
- TestActivityTaskManagerService(Context context) {
- super(context);
- spyOn(this);
-
- mUgmInternal = mock(UriGrantsManagerInternal.class);
- mAppOpsService = mock(AppOpsService.class);
-
- // Make sure permission checks aren't overridden.
- doReturn(AppOpsManager.MODE_DEFAULT)
- .when(mAppOpsService).noteOperation(anyInt(), anyInt(), anyString());
-
- mSupportsMultiWindow = true;
- mSupportsMultiDisplay = true;
- mSupportsSplitScreenMultiWindow = true;
- mSupportsFreeformWindowManagement = true;
- mSupportsPictureInPicture = true;
-
- final TestActivityManagerService am =
- new TestActivityManagerService(mTestInjector, this);
-
- spyOn(getLifecycleManager());
- spyOn(getLockTaskController());
- spyOn(getTaskChangeNotificationController());
- doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
- // allow background activity starts by default
- doReturn(true).when(this).isBackgroundActivityStartsEnabled();
- doNothing().when(this).updateCpuStats();
- }
-
- void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
- ActivityManagerInternal amInternal, WindowManagerService wm, Looper looper) {
- mAmInternal = amInternal;
- initialize(intentFirewall, intentController, looper);
- initRootActivityContainerMocks(wm);
- setWindowManager(wm);
- createDefaultDisplay();
- }
-
- void initRootActivityContainerMocks(WindowManagerService wm) {
- spyOn(mRootActivityContainer);
- mRootActivityContainer.setWindowContainer(mock(RootWindowContainer.class));
- mRootActivityContainer.mWindowManager = wm;
- mRootActivityContainer.mDisplayManager =
- (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
- doNothing().when(mRootActivityContainer).setWindowManager(any());
- // Invoked during {@link ActivityStack} creation.
- doNothing().when(mRootActivityContainer).updateUIDsPresentOnDisplay();
- // Always keep things awake.
- doReturn(true).when(mRootActivityContainer).hasAwakeDisplay();
- // Called when moving activity to pinned stack.
- doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(),
- anyBoolean());
- }
-
- void createDefaultDisplay() {
- // Create a default display and put a home stack on it so that we'll always have
- // something focusable.
- mDefaultDisplay = TestActivityDisplay.create(mStackSupervisor, DEFAULT_DISPLAY);
- spyOn(mDefaultDisplay);
- mRootActivityContainer.addChild(mDefaultDisplay, ActivityDisplay.POSITION_TOP);
- mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
- final TaskRecord task = new TaskBuilder(mStackSupervisor)
- .setStack(mDefaultDisplay.getHomeStack()).build();
- new ActivityBuilder(this).setTask(task).build();
-
- doReturn(mDefaultDisplay).when(mRootActivityContainer).getDefaultDisplay();
- }
-
- @Override
- int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
- return userId;
- }
-
- @Override
- AppOpsService getAppOpsService() {
- return mAppOpsService;
- }
-
- @Override
- void updateCpuStats() {
- }
-
- @Override
- void updateBatteryStats(ActivityRecord component, boolean resumed) {
- }
-
- @Override
- void updateActivityUsageStats(ActivityRecord activity, int event) {
- }
-
- @Override
- protected ActivityStackSupervisor createStackSupervisor() {
- if (mTestStackSupervisor == null) {
- mTestStackSupervisor = new TestActivityStackSupervisor(this, mH.getLooper());
- }
- return mTestStackSupervisor;
- }
-
- @Override
- PackageManagerInternal getPackageManagerInternalLocked() {
- if (mPmInternal == null) {
- mPmInternal = mock(PackageManagerInternal.class);
- doReturn(false)
- .when(mPmInternal)
- .isPermissionsReviewRequired(anyString(), anyInt());
- }
- return mPmInternal;
- }
-
- @Override
- PermissionPolicyInternal getPermissionPolicyInternal() {
- if (mPermissionPolicyInternal == null) {
- mPermissionPolicyInternal = mock(PermissionPolicyInternal.class);
- doReturn(true).when(mPermissionPolicyInternal).checkStartActivity(any(), anyInt(),
- any());
- }
- return mPermissionPolicyInternal;
- }
- }
-
- private static class TestInjector extends ActivityManagerService.Injector {
- private ServiceThread mHandlerThread;
-
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return null;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandlerThread.getThreadHandler();
- }
-
- @Override
- public boolean isNetworkRestrictedForUid(int uid) {
- return false;
- }
-
- void setUp() {
- mHandlerThread = new ServiceThread("ActivityTestsThread",
- Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
- mHandlerThread.start();
- }
-
- void tearDown() {
- // Make sure there are no running messages and then quit the thread so the next test
- // won't be affected.
- mHandlerThread.getThreadHandler().runWithScissors(mHandlerThread::quit,
- 0 /* timeout */);
- }
- }
-
- // TODO: Replace this with a mock object since we are no longer in AMS package.
- /**
- * An {@link ActivityManagerService} subclass which provides a test
- * {@link ActivityStackSupervisor}.
- */
- class TestActivityManagerService extends ActivityManagerService {
-
- TestActivityManagerService(TestInjector testInjector, TestActivityTaskManagerService atm) {
- super(testInjector, testInjector.mHandlerThread);
- spyOn(this);
-
- mWindowManager = prepareMockWindowManager();
- mUgmInternal = mock(UriGrantsManagerInternal.class);
-
- atm.setup(mIntentFirewall, mPendingIntentController, new LocalService(), mWindowManager,
- testInjector.mHandlerThread.getLooper());
-
- mActivityTaskManager = atm;
- mAtmInternal = atm.mInternal;
-
- doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
- PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class);
- doReturn(mockPackageManager).when(this).getPackageManagerInternalLocked();
- doReturn(null).when(mockPackageManager).getDefaultHomeActivity(anyInt());
- doNothing().when(this).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt());
- }
- }
-
- /**
- * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on
- * setup not available in the test environment. Also specifies an injector for
- */
- protected class TestActivityStackSupervisor extends ActivityStackSupervisor {
- private KeyguardController mKeyguardController;
-
- TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
- super(service, looper);
- spyOn(this);
- mWindowManager = prepareMockWindowManager();
- mKeyguardController = mock(KeyguardController.class);
-
- // Do not schedule idle that may touch methods outside the scope of the test.
- doNothing().when(this).scheduleIdleLocked();
- doNothing().when(this).scheduleIdleTimeoutLocked(any());
- // unit test version does not handle launch wake lock
- doNothing().when(this).acquireLaunchWakelock();
- doReturn(mKeyguardController).when(this).getKeyguardController();
-
- mLaunchingActivityWakeLock = mock(PowerManager.WakeLock.class);
-
- initialize();
- }
-
- @Override
- public KeyguardController getKeyguardController() {
- return mKeyguardController;
- }
-
- @Override
- void setWindowManager(WindowManagerService wm) {
- mWindowManager = wm;
- }
- }
-
- protected static class TestActivityDisplay extends ActivityDisplay {
- private final ActivityStackSupervisor mSupervisor;
-
- static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) {
- return create(supervisor, displayId, new DisplayInfo());
- }
-
- static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId,
- DisplayInfo info) {
- if (displayId == DEFAULT_DISPLAY) {
- return new TestActivityDisplay(supervisor,
- supervisor.mRootActivityContainer.mDisplayManager.getDisplay(displayId));
- }
- final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
- info, DEFAULT_DISPLAY_ADJUSTMENTS);
- return new TestActivityDisplay(supervisor, display);
- }
-
- TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
- super(supervisor.mService.mRootActivityContainer, display);
- // Normally this comes from display-properties as exposed by WM. Without that, just
- // hard-code to FULLSCREEN for tests.
- setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- mSupervisor = supervisor;
- }
-
- @SuppressWarnings("TypeParameterUnusedInFormals")
- @Override
- ActivityStack createStackUnchecked(int windowingMode, int activityType,
- int stackId, boolean onTop) {
- return new StackBuilder(mSupervisor.mRootActivityContainer).setDisplay(this)
- .setWindowingMode(windowingMode).setActivityType(activityType)
- .setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build();
- }
-
- @Override
- protected DisplayContent createDisplayContent() {
- final DisplayContent displayContent = mock(DisplayContent.class);
- DockedStackDividerController divider = mock(DockedStackDividerController.class);
- doReturn(divider).when(displayContent).getDockedDividerController();
- return displayContent;
- }
-
- void removeAllTasks() {
- for (int i = 0; i < getChildCount(); i++) {
- final ActivityStack stack = getChildAt(i);
- for (TaskRecord task : (List<TaskRecord>) stack.getAllTasks()) {
- stack.removeTask(task, "removeAllTasks", REMOVE_TASK_MODE_DESTROYING);
- }
- }
- }
- }
-
- private static WindowManagerService sMockWindowManagerService;
-
- private static WindowManagerService prepareMockWindowManager() {
- if (sMockWindowManagerService == null) {
- sMockWindowManagerService = mock(WindowManagerService.class);
- }
-
- sMockWindowManagerService.mRoot = mock(RootWindowContainer.class);
-
- doAnswer((InvocationOnMock invocationOnMock) -> {
- final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
- if (runnable != null) {
- runnable.run();
- }
- return null;
- }).when(sMockWindowManagerService).inSurfaceTransaction(any());
-
- return sMockWindowManagerService;
- }
-
- /**
- * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a
- * method is called. Note that its functionality depends on the implementations of the
- * construction arguments.
- */
- protected static class TestActivityStack
- extends ActivityStack {
- private int mOnActivityRemovedFromStackCount = 0;
-
- static final int IS_TRANSLUCENT_UNSET = 0;
- static final int IS_TRANSLUCENT_FALSE = 1;
- static final int IS_TRANSLUCENT_TRUE = 2;
- private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
-
- static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0;
- static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1;
- static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2;
- private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET;
-
- TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
- int windowingMode, int activityType, boolean onTop, boolean createActivity) {
- super(display, stackId, supervisor, windowingMode, activityType, onTop);
- if (createActivity) {
- new ActivityBuilder(mService).setCreateTask(true).setStack(this).build();
- if (onTop) {
- // We move the task to front again in order to regain focus after activity
- // added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack}
- // could be other stacks (e.g. home stack).
- moveToFront("createActivityStack");
- } else {
- moveToBack("createActivityStack", null);
- }
- }
- }
-
- @Override
- void onActivityRemovedFromStack(ActivityRecord r) {
- mOnActivityRemovedFromStackCount++;
- super.onActivityRemovedFromStack(r);
- }
-
- // Returns the number of times {@link #onActivityRemovedFromStack} has been called
- int onActivityRemovedFromStackInvocationCount() {
- return mOnActivityRemovedFromStackCount;
- }
-
- @Override
- protected void createTaskStack(int displayId, boolean onTop, Rect outBounds) {
- mTaskStack = mock(TaskStack.class);
-
- // Primary pinned stacks require a non-empty out bounds to be set or else all tasks
- // will be moved to the full screen stack.
- if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- outBounds.set(0, 0, 100, 100);
- }
- }
-
- @Override
- TaskStack getTaskStack() {
- return mTaskStack;
- }
-
- void setIsTranslucent(boolean isTranslucent) {
- mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
- }
-
- @Override
- boolean isStackTranslucent(ActivityRecord starting) {
- switch (mIsTranslucent) {
- case IS_TRANSLUCENT_TRUE:
- return true;
- case IS_TRANSLUCENT_FALSE:
- return false;
- case IS_TRANSLUCENT_UNSET:
- default:
- return super.isStackTranslucent(starting);
- }
- }
-
- void setSupportsSplitScreen(boolean supportsSplitScreen) {
- mSupportsSplitScreen = supportsSplitScreen
- ? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE;
- }
-
- @Override
- public boolean supportsSplitScreenWindowingMode() {
- switch (mSupportsSplitScreen) {
- case SUPPORTS_SPLIT_SCREEN_TRUE:
- return true;
- case SUPPORTS_SPLIT_SCREEN_FALSE:
- return false;
- case SUPPORTS_SPLIT_SCREEN_UNSET:
- default:
- return super.supportsSplitScreenWindowingMode();
- }
- }
-
- @Override
- void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
- boolean newTask, boolean keepCurTransition,
- ActivityOptions options) {
- }
}
static class StackBuilder {
@@ -886,27 +423,45 @@
return this;
}
- @SuppressWarnings("TypeParameterUnusedInFormals")
ActivityStack build() {
final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
+ final ActivityStack stack;
+ final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
if (mWindowingMode == WINDOWING_MODE_PINNED) {
- return new ActivityStack(mDisplay, stackId, mRootActivityContainer.mStackSupervisor,
+ stack = new ActivityStack(mDisplay, stackId, supervisor,
mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
@Override
Rect getDefaultPictureInPictureBounds(float aspectRatio) {
return new Rect(50, 50, 100, 100);
}
-
- @Override
- void createTaskStack(int displayId, boolean onTop, Rect outBounds) {
- mTaskStack = mock(TaskStack.class);
- }
};
} else {
- return new TestActivityStack(mDisplay, stackId,
- mRootActivityContainer.mStackSupervisor, mWindowingMode,
- mActivityType, mOnTop, mCreateActivity);
+ stack = new ActivityStack(mDisplay, stackId, supervisor,
+ mWindowingMode, mActivityType, mOnTop);
+
+ if (mCreateActivity) {
+ new ActivityBuilder(supervisor.mService)
+ .setCreateTask(true)
+ .setStack(stack)
+ .build();
+ if (mOnTop) {
+ // We move the task to front again in order to regain focus after activity
+ // added to the stack.
+ // Or {@link ActivityDisplay#mPreferredTopFocusableStack} could be other
+ // stacks (e.g. home stack).
+ stack.moveToFront("createActivityStack");
+ } else {
+ stack.moveToBack("createActivityStack", null);
+ }
+ }
}
+
+ spyOn(stack);
+ spyOn(stack.mTaskStack);
+ doNothing().when(stack).startActivityLocked(
+ any(), any(), anyBoolean(), anyBoolean(), any());
+
+ return stack;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 20379a2..e71c8f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -60,7 +60,7 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- translucentOpening.setFillsParent(false);
+ translucentOpening.setOccludesParent(false);
translucentOpening.setHidden(true);
mDisplayContent.mOpeningApps.add(behind);
mDisplayContent.mOpeningApps.add(translucentOpening);
@@ -78,7 +78,7 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final AppWindowToken translucentClosing = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- translucentClosing.setFillsParent(false);
+ translucentClosing.setOccludesParent(false);
mDisplayContent.mClosingApps.add(translucentClosing);
assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
mAppTransitionController.maybeUpdateTransitToTranslucentAnim(
@@ -94,7 +94,7 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- translucentOpening.setFillsParent(false);
+ translucentOpening.setOccludesParent(false);
translucentOpening.setHidden(true);
mDisplayContent.mOpeningApps.add(behind);
mDisplayContent.mOpeningApps.add(translucentOpening);
@@ -110,10 +110,10 @@
synchronized (mWm.mGlobalLock) {
final AppWindowToken opening = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- opening.setFillsParent(false);
+ opening.setOccludesParent(false);
final AppWindowToken closing = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- closing.setFillsParent(false);
+ closing.setOccludesParent(false);
Task task = opening.getTask();
mDisplayContent.mOpeningApps.add(opening);
mDisplayContent.mClosingApps.add(closing);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index d1dc382..c162b6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -30,12 +30,14 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import android.graphics.Rect;
import android.os.IBinder;
@@ -195,6 +197,7 @@
@Test
public void testCancelRemoteAnimationWhenFreeze() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+ doReturn(false).when(dc).onDescendantOrientationChanged(any(), any());
final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
dc, "exiting app");
final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index d9566a3..e387e18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -36,7 +36,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
@@ -152,10 +152,6 @@
@Test
@FlakyTest(bugId = 131005232)
public void testLandscapeSeascapeRotationByApp() {
- // Some plumbing to get the service ready for rotation updates.
- mWm.mDisplayReady = true;
- mWm.mDisplayEnabled = true;
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("AppWindow");
@@ -185,25 +181,21 @@
@Test
public void testLandscapeSeascapeRotationByPolicy() {
- // Some plumbing to get the service ready for rotation updates.
- mWm.mDisplayReady = true;
- mWm.mDisplayEnabled = true;
-
- final DisplayRotation spiedRotation = spy(mDisplayContent.getDisplayRotation());
- mDisplayContent.setDisplayRotation(spiedRotation);
+ final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
- attrs.setTitle("AppWindow");
+ attrs.setTitle("RotationByPolicy");
final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
mToken.addWindow(appWindow);
// Set initial orientation and update.
- performRotation(spiedRotation, Surface.ROTATION_90);
+ performRotation(displayRotation, Surface.ROTATION_90);
appWindow.mResizeReported = false;
// Update the rotation to perform 180 degree rotation and check that resize was reported.
- performRotation(spiedRotation, Surface.ROTATION_270);
+ performRotation(displayRotation, Surface.ROTATION_270);
assertTrue(appWindow.mResizeReported);
appWindow.removeImmediately();
@@ -211,14 +203,7 @@
private void performRotation(DisplayRotation spiedRotation, int rotationToReport) {
doReturn(rotationToReport).when(spiedRotation).rotationForOrientation(anyInt(), anyInt());
- int oldRotation = mDisplayContent.getRotation();
mWm.updateRotation(false, false);
- // Must manually apply here since ATM doesn't know about the display during this test
- // (meaning it can't perform the normal sendNewConfiguration flow).
- mDisplayContent.applyRotationLocked(oldRotation, mDisplayContent.getRotation());
- // Prevent the next rotation from being deferred by animation.
- mWm.mAnimator.setScreenRotationAnimationLocked(mDisplayContent.getDisplayId(), null);
- mWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
}
@Test
@@ -266,14 +251,14 @@
public void testGetOrientation() {
mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mToken.setFillsParent(false);
- // Can specify orientation if app doesn't fill parent.
+ mToken.setOccludesParent(false);
+ // Can specify orientation if app doesn't occludes parent.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
- mToken.setFillsParent(true);
+ mToken.setOccludesParent(true);
mToken.setHidden(true);
mToken.sendingToBottom = true;
- // Can not specify orientation if app isn't visible even though it fills parent.
+ // Can not specify orientation if app isn't visible even though it occludes parent.
assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
// Can specify orientation if the current orientation candidate is orientation behind.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 388658d..6289768 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -83,7 +83,6 @@
import androidx.test.filters.SmallTest;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.server.wm.utils.WmDisplayCutout;
@@ -653,25 +652,27 @@
@Test
public void testOnDescendantOrientationRequestChanged() {
final DisplayContent dc = createNewDisplay();
- mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
+ dc.getDisplayRotation().setFixedToUserRotation(
+ DisplayRotation.FIXED_TO_USER_ROTATION_DISABLED);
final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
? SCREEN_ORIENTATION_PORTRAIT
: SCREEN_ORIENTATION_LANDSCAPE;
- final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
- window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
- window.mAppToken.setOrientation(newOrientation);
+ final ActivityStack stack =
+ new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer)
+ .setDisplay(dc.mAcitvityDisplay).build();
+ final ActivityRecord activity = stack.topTask().getTopActivity();
- ActivityRecord activityRecord = mock(ActivityRecord.class);
-
- assertTrue("Display should rotate to handle orientation request by default.",
- dc.onDescendantOrientationChanged(window.mToken.token, activityRecord));
+ activity.setRequestedOrientation(newOrientation);
final ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
verify(dc.mAcitvityDisplay).updateDisplayOverrideConfigurationLocked(captor.capture(),
- same(activityRecord), anyBoolean(), same(null));
+ same(activity), anyBoolean(), same(null));
final Configuration newDisplayConfig = captor.getValue();
- assertEquals(Configuration.ORIENTATION_PORTRAIT, newDisplayConfig.orientation);
+ final int expectedOrientation = newOrientation == SCREEN_ORIENTATION_PORTRAIT
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ assertEquals(expectedOrientation, newDisplayConfig.orientation);
}
@Test
@@ -679,22 +680,20 @@
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
DisplayRotation.FIXED_TO_USER_ROTATION_ENABLED);
- mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
? SCREEN_ORIENTATION_PORTRAIT
: SCREEN_ORIENTATION_LANDSCAPE;
- final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
- window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
- window.mAppToken.setOrientation(newOrientation);
+ final ActivityStack stack =
+ new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer)
+ .setDisplay(dc.mAcitvityDisplay).build();
+ final ActivityRecord activity = stack.topTask().getTopActivity();
- ActivityRecord activityRecord = mock(ActivityRecord.class);
+ activity.setRequestedOrientation(newOrientation);
- assertFalse("Display shouldn't rotate to handle orientation request if fixed to"
- + " user rotation.",
- dc.onDescendantOrientationChanged(window.mToken.token, activityRecord));
verify(dc.mAcitvityDisplay, never()).updateDisplayOverrideConfigurationLocked(any(),
- eq(activityRecord), anyBoolean(), same(null));
+ eq(activity), anyBoolean(), same(null));
+ assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index bfede51..f6f8811 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -95,7 +95,7 @@
public void setUp() throws Exception {
deleteRecursively(TEST_FOLDER);
- mWm.setSupportsFreeformWindowManagement(false);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = false;
mWm.setIsPc(false);
mWm.setForceDesktopModeOnExternalDisplays(false);
@@ -134,7 +134,7 @@
@Test
public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -144,7 +144,7 @@
@Test
public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_HasDesktopMode() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.setForceDesktopModeOnExternalDisplays(true);
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -155,7 +155,7 @@
@Test
public void testPrimaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.setIsPc(true);
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -168,7 +168,7 @@
public void testPrimaryDisplayUpdateToFreeform_HasFreeformSupport_IsPc() {
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.setIsPc(true);
mTarget.updateSettingsForDisplay(mPrimaryDisplay);
@@ -187,7 +187,7 @@
@Test
public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_NoDesktopMode() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
@@ -197,7 +197,7 @@
@Test
public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_HasDesktopMode() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.setForceDesktopModeOnExternalDisplays(true);
mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
@@ -208,7 +208,7 @@
@Test
public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() {
- mWm.setSupportsFreeformWindowManagement(true);
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.setIsPc(true);
mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index b28ae40..be2ee29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -331,7 +331,9 @@
mController.layoutTask(task, null /* windowLayout */);
- assertEquals(expected, task.getBounds());
+ // TaskRecord will make adjustments to requested bounds. We only need to guarantee that the
+ // reuqested bounds are expected.
+ assertEquals(expected, task.getRequestedOverrideBounds());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index e4d3770..49d778f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -104,6 +104,7 @@
mDisplayUniqueId = "test:" + Integer.toString(sNextUniqueId++);
final DisplayInfo info = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(info);
info.uniqueId = mDisplayUniqueId;
mTestDisplay = createNewActivityDisplay(info);
mRootActivityContainer.addChild(mTestDisplay, ActivityDisplay.POSITION_TOP);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index 63d9fb9..efd468f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -61,7 +61,7 @@
@Test
public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException {
- mWm.mSupportsPictureInPicture = true;
+ mWm.mAtmService.mSupportsPictureInPicture = true;
mWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener);
verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index b7a85d7..fb4e330 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -32,7 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -53,14 +53,12 @@
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.ComponentName;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -93,11 +91,8 @@
private static final int TEST_QUIET_USER_ID = 20;
private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
private static final UserInfo QUIET_USER_INFO = new UserInfo();
- private static int sLastTaskId = 1;
- private static int sLastStackId = 1;
private static final int INVALID_STACK_ID = 999;
- private TestActivityTaskManagerService mTestService;
private ActivityDisplay mDisplay;
private ActivityDisplay mOtherDisplay;
private ActivityDisplay mSingleTaskDisplay;
@@ -115,13 +110,29 @@
@Before
public void setUp() throws Exception {
mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
- mTestService = new MyTestActivityTaskManagerService(mContext);
- mRecentTasks = (TestRecentTasks) mTestService.getRecentTasks();
+
+ // Set testing displays
+ mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+ mOtherDisplay = createNewActivityDisplay();
+ mSingleTaskDisplay = createNewActivityDisplay();
+ mSingleTaskDisplay.setDisplayToSingleTaskInstance();
+ mRootActivityContainer.addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP);
+ mRootActivityContainer.addChild(mDisplay, ActivityDisplay.POSITION_TOP);
+ mRootActivityContainer.addChild(mSingleTaskDisplay, ActivityDisplay.POSITION_TOP);
+
+ // Set the recent tasks we should use for testing in this class.
+ mRecentTasks = new TestRecentTasks(mService, mTaskPersister);
+ spyOn(mRecentTasks);
+ mService.setRecentTasks(mRecentTasks);
mRecentTasks.loadParametersFromResources(mContext.getResources());
- mRunningTasks = (TestRunningTasks) mTestService.mStackSupervisor.mRunningTasks;
- mHomeStack = mTestService.mRootActivityContainer.getDefaultDisplay().getOrCreateStack(
+
+ // Set the running tasks we should use for testing in this class.
+ mRunningTasks = new TestRunningTasks();
+ mService.mStackSupervisor.setRunningTasks(mRunningTasks);
+
+ mHomeStack = mDisplay.getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- mStack = mTestService.mRootActivityContainer.getDefaultDisplay().createStack(
+ mStack = mDisplay.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
mCallbacksRecorder = new CallbacksRecorder();
mRecentTasks.registerCallback(mCallbacksRecorder);
@@ -723,7 +734,7 @@
ActivityStack stack = mTasks.get(2).getStack();
stack.moveToFront("", mTasks.get(2));
- doReturn(stack).when(mTestService.mRootActivityContainer).getTopDisplayFocusedStack();
+ doReturn(stack).when(mService.mRootActivityContainer).getTopDisplayFocusedStack();
// Simulate the reset from the timeout
mRecentTasks.resetFreezeTaskListReorderingOnTimeout();
@@ -742,10 +753,9 @@
public void testBackStackTasks_expectNoTrim() {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
- final MyTestActivityStackSupervisor supervisor =
- (MyTestActivityStackSupervisor) mTestService.mStackSupervisor;
final ActivityStack homeStack = mDisplay.getHomeStack();
- final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack aboveHomeStack = mDisplay.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
// the tasks belong in stacks above the home stack
@@ -761,11 +771,11 @@
public void testBehindHomeStackTasks_expectTaskTrimmed() {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
- final MyTestActivityStackSupervisor supervisor =
- (MyTestActivityStackSupervisor) mTestService.mStackSupervisor;
- final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack behindHomeStack = mDisplay.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final ActivityStack homeStack = mDisplay.getHomeStack();
- final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack aboveHomeStack = mDisplay.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
// the home stack is trimmed once a new task is added
@@ -783,10 +793,9 @@
public void testOtherDisplayTasks_expectNoTrim() {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
- final MyTestActivityStackSupervisor supervisor =
- (MyTestActivityStackSupervisor) mTestService.mStackSupervisor;
final ActivityStack homeStack = mDisplay.getHomeStack();
- final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor);
+ final ActivityStack otherDisplayStack = mOtherDisplay.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
// removed
@@ -887,16 +896,16 @@
mStack.remove();
// The following APIs should not restore task from recents to the active list.
- assertNotRestoreTask(() -> mTestService.setFocusedTask(taskId));
- assertNotRestoreTask(() -> mTestService.startSystemLockTaskMode(taskId));
- assertNotRestoreTask(() -> mTestService.cancelTaskWindowTransition(taskId));
+ assertNotRestoreTask(() -> mService.setFocusedTask(taskId));
+ assertNotRestoreTask(() -> mService.startSystemLockTaskMode(taskId));
+ assertNotRestoreTask(() -> mService.cancelTaskWindowTransition(taskId));
assertNotRestoreTask(
- () -> mTestService.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */));
+ () -> mService.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */));
assertNotRestoreTask(
- () -> mTestService.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
+ () -> mService.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
false/* toTop */));
assertNotRestoreTask(
- () -> mTestService.setTaskWindowingModeSplitScreenPrimary(taskId,
+ () -> mService.setTaskWindowingModeSplitScreenPrimary(taskId,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
false /* toTop */, false /* animate */, null /* initialBounds */,
true /* showRecents */));
@@ -910,7 +919,7 @@
mRecentTasks.remove(task);
TaskChangeNotificationController controller =
- mTestService.getTaskChangeNotificationController();
+ mService.getTaskChangeNotificationController();
verify(controller, times(2)).notifyTaskListUpdated();
}
@@ -923,7 +932,7 @@
// 2 calls - Once for add and once for remove
TaskChangeNotificationController controller =
- mTestService.getTaskChangeNotificationController();
+ mService.getTaskChangeNotificationController();
verify(controller, times(2)).notifyTaskListUpdated();
}
@@ -938,7 +947,7 @@
// 4 calls - Twice for add and twice for remove
TaskChangeNotificationController controller =
- mTestService.getTaskChangeNotificationController();
+ mService.getTaskChangeNotificationController();
verify(controller, times(4)).notifyTaskListUpdated();
}
@@ -980,7 +989,7 @@
@Test
public void testNotRecentsComponent_denyApiAccess() throws Exception {
- doReturn(PackageManager.PERMISSION_DENIED).when(mTestService)
+ doReturn(PackageManager.PERMISSION_DENIED).when(mService)
.checkGetTasksPermission(anyString(), anyInt(), anyInt());
// Expect the following methods to fail due to recents component not being set
mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
@@ -992,7 +1001,7 @@
@Test
public void testRecentsComponent_allowApiAccessWithoutPermissions() {
- doReturn(PackageManager.PERMISSION_DENIED).when(mTestService)
+ doReturn(PackageManager.PERMISSION_DENIED).when(mService)
.checkGetTasksPermission(anyString(), anyInt(), anyInt());
// Set the recents component and ensure that the following calls do not fail
@@ -1002,62 +1011,62 @@
}
private void doTestRecentTasksApis(boolean expectCallable) {
- assertSecurityException(expectCallable, () -> mTestService.removeStack(INVALID_STACK_ID));
+ assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
assertSecurityException(expectCallable,
- () -> mTestService.removeStacksInWindowingModes(
+ () -> mService.removeStacksInWindowingModes(
new int[]{WINDOWING_MODE_UNDEFINED}));
assertSecurityException(expectCallable,
- () -> mTestService.removeStacksWithActivityTypes(
+ () -> mService.removeStacksWithActivityTypes(
new int[]{ACTIVITY_TYPE_UNDEFINED}));
- assertSecurityException(expectCallable, () -> mTestService.removeTask(0));
+ assertSecurityException(expectCallable, () -> mService.removeTask(0));
assertSecurityException(expectCallable,
- () -> mTestService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+ () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
assertSecurityException(expectCallable,
- () -> mTestService.moveTaskToStack(0, INVALID_STACK_ID, true));
+ () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
- () -> mTestService.setTaskWindowingModeSplitScreenPrimary(0,
+ () -> mService.setTaskWindowingModeSplitScreenPrimary(0,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
- assertSecurityException(expectCallable, () -> mTestService.dismissSplitScreenMode(true));
- assertSecurityException(expectCallable, () -> mTestService.dismissPip(true, 0));
+ assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
+ assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
assertSecurityException(expectCallable,
- () -> mTestService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+ () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
assertSecurityException(expectCallable,
- () -> mTestService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+ () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
assertSecurityException(expectCallable,
- () -> mTestService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
+ () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
new Rect()));
assertSecurityException(expectCallable,
- () -> mTestService.resizePinnedStack(new Rect(), new Rect()));
- assertSecurityException(expectCallable, () -> mTestService.getAllStackInfos());
+ () -> mService.resizePinnedStack(new Rect(), new Rect()));
+ assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
assertSecurityException(expectCallable,
- () -> mTestService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+ () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
assertSecurityException(expectCallable, () -> {
try {
- mTestService.getFocusedStackInfo();
+ mService.getFocusedStackInfo();
} catch (RemoteException e) {
// Ignore
}
});
assertSecurityException(expectCallable,
- () -> mTestService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
+ () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
- () -> mTestService.startActivityFromRecents(0, new Bundle()));
- assertSecurityException(expectCallable, () -> mTestService.getTaskSnapshot(0, true));
- assertSecurityException(expectCallable, () -> mTestService.registerTaskStackListener(null));
+ () -> mService.startActivityFromRecents(0, new Bundle()));
+ assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true));
+ assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null));
assertSecurityException(expectCallable,
- () -> mTestService.unregisterTaskStackListener(null));
- assertSecurityException(expectCallable, () -> mTestService.getTaskDescription(0));
- assertSecurityException(expectCallable, () -> mTestService.cancelTaskWindowTransition(0));
- assertSecurityException(expectCallable, () -> mTestService.startRecentsActivity(null, null,
+ () -> mService.unregisterTaskStackListener(null));
+ assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
+ assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
+ assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
null));
- assertSecurityException(expectCallable, () -> mTestService.cancelRecentsAnimation(true));
- assertSecurityException(expectCallable, () -> mTestService.stopAppSwitches());
- assertSecurityException(expectCallable, () -> mTestService.resumeAppSwitches());
+ assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true));
+ assertSecurityException(expectCallable, () -> mService.stopAppSwitches());
+ assertSecurityException(expectCallable, () -> mService.resumeAppSwitches());
}
private void testGetTasksApis(boolean expectCallable) {
- mTestService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
- mTestService.getTasks(MAX_VALUE);
+ mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+ mService.getTasks(MAX_VALUE);
if (expectCallable) {
assertTrue(mRecentTasks.mLastAllowed);
assertTrue(mRunningTasks.mLastAllowed);
@@ -1072,10 +1081,9 @@
}
private TaskBuilder createTaskBuilder(String packageName, String className) {
- return new TaskBuilder(mTestService.mStackSupervisor)
+ return new TaskBuilder(mService.mStackSupervisor)
.setComponent(new ComponentName(packageName, className))
.setStack(mStack)
- .setTaskId(sLastTaskId++)
.setUserId(TEST_USER_0_ID);
}
@@ -1140,68 +1148,6 @@
}
}
- private class MyTestActivityTaskManagerService extends TestActivityTaskManagerService {
- MyTestActivityTaskManagerService(Context context) {
- super(context);
- }
-
- @Override
- protected RecentTasks createRecentTasks() {
- return spy(new TestRecentTasks(this, mTaskPersister));
- }
-
- @Override
- protected ActivityStackSupervisor createStackSupervisor() {
- if (mTestStackSupervisor == null) {
- mTestStackSupervisor = new MyTestActivityStackSupervisor(this, mH.getLooper());
- }
- return mTestStackSupervisor;
- }
-
- @Override
- void createDefaultDisplay() {
- super.createDefaultDisplay();
- mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
- mOtherDisplay = TestActivityDisplay.create(mTestStackSupervisor, DEFAULT_DISPLAY + 1);
- mSingleTaskDisplay = TestActivityDisplay.create(mTestStackSupervisor,
- DEFAULT_DISPLAY + 2);
- mSingleTaskDisplay.setDisplayToSingleTaskInstance();
- mRootActivityContainer.addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP);
- mRootActivityContainer.addChild(mDisplay, ActivityDisplay.POSITION_TOP);
- mRootActivityContainer.addChild(mSingleTaskDisplay, ActivityDisplay.POSITION_TOP);
- }
- }
-
- private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
- MyTestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
- super(service, looper);
- }
-
- @Override
- RunningTasks createRunningTasks() {
- mRunningTasks = new TestRunningTasks();
- return mRunningTasks;
- }
- }
-
- private static class MyTestActivityStack extends TestActivityStack {
- private ActivityDisplay mDisplay = null;
-
- MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
- super(display, sLastStackId++, supervisor, WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, true /* onTop */, false /* createActivity */);
- mDisplay = display;
- }
-
- @Override
- ActivityDisplay getDisplay() {
- if (mDisplay != null) {
- return mDisplay;
- }
- return super.getDisplay();
- }
- }
-
private static class CallbacksRecorder implements Callbacks {
public final ArrayList<TaskRecord> mAdded = new ArrayList<>();
public final ArrayList<TaskRecord> mTrimmed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index f5a1d75..9ca0180 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -30,6 +30,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
@@ -132,8 +133,14 @@
@Test
public void testIncludedApps_expectTargetAndVisible() {
mWm.setRecentsAnimationController(mController);
- final AppWindowToken homeAppWindow = createAppWindowToken(mDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+ final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+ final AppWindowToken homeAppWindow =
+ new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+ .setStack(homStack)
+ .setCreateTask(true)
+ .build()
+ .mAppWindowToken;
final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final AppWindowToken hiddenAppWindow = createAppWindowToken(mDisplayContent,
@@ -169,7 +176,7 @@
// Simulate the app transition finishing
mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0, 0, 0);
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false);
+ verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
@Test
@@ -201,7 +208,7 @@
spyOn(mController.mRecentScreenshotAnimator.mAnimatable);
mController.mRecentScreenshotAnimator.cancelAnimation();
verify(mController.mRecentScreenshotAnimator.mAnimatable).onAnimationLeashLost(any());
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false);
+ verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 0e119e3..dcc295c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -74,8 +74,9 @@
@Before
public void setUp() throws Exception {
mRecentsAnimationController = mock(RecentsAnimationController.class);
- doReturn(mRecentsAnimationController).when(
- mService.mWindowManager).getRecentsAnimationController();
+ mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
+ doNothing().when(mService.mWindowManager).initializeRecentsAnimation(
+ anyInt(), any(), any(), anyInt(), any());
doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
final RecentTasks recentTasks = mService.getRecentTasks();
@@ -107,16 +108,25 @@
assertTrue(recentActivity.visible);
// Simulate the animation is cancelled without changing the stack order.
- recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, true /* runSychronously */,
- false /* sendUserLeaveHint */);
+ recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
// The non-top recents activity should be invisible by the restored launch-behind state.
assertFalse(recentActivity.visible);
}
@Test
public void testPreloadRecentsActivity() {
- // Ensure that the fake recent component can be resolved by the recents intent.
- mockTaskRecordFactory(builder -> builder.setComponent(mRecentsComponent));
+ final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay();
+ final ActivityStack homeStack =
+ defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+ defaultDisplay.positionChildAtTop(homeStack, false /* includingParents */);
+ ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ if (topRunningHomeActivity == null) {
+ topRunningHomeActivity = new ActivityBuilder(mService)
+ .setStack(homeStack)
+ .setCreateTask(true)
+ .build();
+ }
+
ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.uid = 10001;
@@ -204,6 +214,13 @@
ActivityStack homeStack = display.getHomeStack();
// Assume the home activity support recents.
ActivityRecord targetActivity = homeStack.getTopActivity();
+ if (targetActivity == null) {
+ targetActivity = new ActivityBuilder(mService)
+ .setCreateTask(true)
+ .setStack(homeStack)
+ .build();
+ }
+
// Put another home activity in home stack.
ActivityRecord anotherHomeActivity = new ActivityBuilder(mService)
.setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
@@ -226,13 +243,12 @@
anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
// The current top activity is not the recents so the animation should be canceled.
- verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
+ verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
eq(REORDER_KEEP_IN_PLACE), any() /* reason */);
// The test uses mocked RecentsAnimationController so we have to invoke the callback
// manually to simulate the flow.
- recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, true /* runSychronously */,
- false /* sendUserLeaveHint */);
+ recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
// We should restore the launch-behind of the original target activity.
assertFalse(targetActivity.mLaunchTaskBehind);
}
@@ -269,7 +285,7 @@
fullscreenStack.moveToFront("Activity start");
// Ensure that the recents animation was canceled by cancelAnimationSynchronously().
- verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
+ verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
eq(REORDER_KEEP_IN_PLACE), any());
// Assume recents animation already started, set a state that cancel recents animation
@@ -314,7 +330,7 @@
fullscreenStack.remove();
// Ensure that the recents animation was NOT canceled
- verify(mService.mWindowManager, times(0)).cancelRecentsAnimationSynchronously(
+ verify(mService.mWindowManager, times(0)).cancelRecentsAnimation(
eq(REORDER_KEEP_IN_PLACE), any());
verify(mRecentsAnimationController, times(0)).setCancelOnNextTransitionStart();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index d4f24f9..539a79c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -97,7 +97,7 @@
*/
@Test
public void testRestoringInvalidTask() {
- ((TestActivityDisplay) mRootActivityContainer.getDefaultDisplay()).removeAllTasks();
+ mRootActivityContainer.getDefaultDisplay().removeAllTasks();
TaskRecord task = mRootActivityContainer.anyTaskForId(0 /*taskId*/,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
assertNull(task);
@@ -304,21 +304,23 @@
*/
@Test
public void testResizeDockedStackForSplitScreenPrimary() {
- final Rect taskSize = new Rect(0, 0, 600, 600);
+ final Rect taskSize = new Rect(0, 0, 1000, 1000);
final Rect stackSize = new Rect(0, 0, 300, 300);
// Create primary split-screen stack with a task.
- final ActivityStack primaryStack = mRootActivityContainer.getDefaultDisplay()
- .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final TaskRecord task = new TaskBuilder(mSupervisor).setStack(primaryStack).build();
+ final ActivityStack primaryStack = new StackBuilder(mRootActivityContainer)
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+ .setOnTop(true)
+ .build();
+ final TaskRecord task = primaryStack.topTask();
// Resize dock stack.
mService.resizeDockedStack(stackSize, taskSize, null, null, null);
// Verify dock stack & its task bounds if is equal as resized result.
- assertEquals(primaryStack.getBounds(), stackSize);
- assertEquals(task.getBounds(), taskSize);
+ assertEquals(stackSize, primaryStack.getBounds());
+ assertEquals(taskSize, task.getBounds());
}
/**
@@ -328,8 +330,9 @@
public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
// Create stack/task on default display.
final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
- final TestActivityStack targetStack = (TestActivityStack) new StackBuilder(
- mRootActivityContainer).setOnTop(false).build();
+ final ActivityStack targetStack = new StackBuilder(mRootActivityContainer)
+ .setOnTop(false)
+ .build();
final TaskRecord targetTask = targetStack.getChildAt(0);
// Create Recents on top of the display.
@@ -505,12 +508,10 @@
mockResolveSecondaryHomeActivity();
// Create secondary displays.
- final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+ final TestActivityDisplay secondDisplay = createNewActivityDisplay();
mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
doReturn(true).when(secondDisplay).supportsSystemDecorations();
- // Create mock tasks and other necessary mocks.
- mockTaskRecordFactory();
doReturn(true).when(mRootActivityContainer)
.ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean());
doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay(
@@ -621,7 +622,6 @@
info.applicationInfo.packageName = "android";
info.name = ResolverActivity.class.getName();
doReturn(info).when(mRootActivityContainer).resolveHomeActivity(anyInt(), any());
- mockTaskRecordFactory();
mRootActivityContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY);
final ActivityRecord resolverActivity = mRootActivityContainer.topRunningActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f51ce13..db105dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -96,7 +96,7 @@
mWm.getDefaultDisplayContentLocked().getWindowingMode());
mWm.mIsPc = true;
- mWm.mSupportsFreeformWindowManagement = true;
+ mWm.mAtmService.mSupportsFreeformWindowManagement = true;
mWm.mRoot.onSettingsRetrieved();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index df7c9a4..1ad0e00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -25,67 +28,91 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.nullable;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.net.Uri;
import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
-import android.view.Display;
+import android.provider.DeviceConfig;
import android.view.InputChannel;
+import android.view.Surface;
+import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.server.AnimationThread;
+import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.ServiceThread;
import com.android.server.Watchdog;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.appop.AppOpsService;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.input.InputManagerService;
+import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.utils.MockTracker;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
-import org.mockito.invocation.InvocationOnMock;
+import org.mockito.Mockito;
import org.mockito.quality.Strictness;
+import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * JUnit test rule to create a mock {@link WindowManagerService} instance for tests.
+ * JUnit test rule to correctly setting up system services like {@link WindowManagerService}
+ * and {@link ActivityTaskManagerService} for tests.
*/
public class SystemServicesTestRule implements TestRule {
private static final String TAG = SystemServicesTestRule.class.getSimpleName();
+ static int sNextDisplayId = DEFAULT_DISPLAY + 100;
+ static int sNextTaskId = 100;
+
private final AtomicBoolean mCurrentMessagesProcessed = new AtomicBoolean(false);
- private MockTracker mMockTracker;
+ private Context mContext;
private StaticMockitoSession mMockitoSession;
- private WindowManagerService mWindowManagerService;
- private TestWindowManagerPolicy mWindowManagerPolicy;
-
- /** {@link MockTracker} to track mocks created by {@link SystemServicesTestRule}. */
- private static class Tracker extends MockTracker {
- // This empty extended class is necessary since Mockito distinguishes a listener by it
- // class.
- }
+ ServiceThread mHandlerThread;
+ private ActivityManagerService mAmService;
+ private ActivityTaskManagerService mAtmService;
+ private WindowManagerService mWmService;
+ private TestWindowManagerPolicy mWMPolicy;
+ private WindowState.PowerManagerWrapper mPowerManagerWrapper;
+ private InputManagerService mImService;
+ /**
+ * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
+ */
+ SurfaceControl.Transaction mTransaction;
@Override
public Statement apply(Statement base, Description description) {
@@ -103,8 +130,6 @@
}
private void setUp() {
- mMockTracker = new Tracker();
-
mMockitoSession = mockitoSession()
.spyStatic(LocalServices.class)
.mockStatic(LockGuard.class)
@@ -112,101 +137,220 @@
.strictness(Strictness.LENIENT)
.startMocking();
+ setUpSystemCore();
+ setUpLocalServices();
+ setUpActivityTaskManagerService();
+ setUpWindowManagerService();
+ }
+
+ private void setUpSystemCore() {
+ mHandlerThread = new ServiceThread(
+ "WmTestsThread", THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+ mHandlerThread.start();
+
doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
- final Context context = getInstrumentation().getTargetContext();
- spyOn(context);
+ mContext = getInstrumentation().getTargetContext();
+ spyOn(mContext);
- doReturn(null).when(context)
+ doReturn(null).when(mContext)
.registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class));
- doReturn(null).when(context)
+ doReturn(null).when(mContext)
.registerReceiverAsUser(any(BroadcastReceiver.class), any(UserHandle.class),
any(IntentFilter.class), nullable(String.class), nullable(Handler.class));
- final ContentResolver contentResolver = context.getContentResolver();
+ final ContentResolver contentResolver = mContext.getContentResolver();
spyOn(contentResolver);
doNothing().when(contentResolver)
.registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
anyInt());
+ }
- final AppOpsManager appOpsManager = mock(AppOpsManager.class);
- doReturn(appOpsManager).when(context)
- .getSystemService(eq(Context.APP_OPS_SERVICE));
+ private void setUpLocalServices() {
+ // Tear down any local services just in case.
+ tearDownLocalServices();
+ // UriGrantsManagerInternal
+ final UriGrantsManagerInternal ugmi = mock(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugmi);
+
+ // AppOpsManager
+ final AppOpsManager aom = mock(AppOpsManager.class);
+ doReturn(aom).when(mContext).getSystemService(eq(Context.APP_OPS_SERVICE));
+
+ // DisplayManagerInternal
final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class);
doReturn(dmi).when(() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
+ // ColorDisplayServiceInternal
+ final ColorDisplayService.ColorDisplayServiceInternal cds =
+ mock(ColorDisplayService.ColorDisplayServiceInternal.class);
+ doReturn(cds).when(() -> LocalServices.getService(
+ eq(ColorDisplayService.ColorDisplayServiceInternal.class)));
+
+ final UsageStatsManagerInternal usmi = mock(UsageStatsManagerInternal.class);
+ LocalServices.addService(UsageStatsManagerInternal.class, usmi);
+
+ // PackageManagerInternal
+ final PackageManagerInternal packageManagerInternal = mock(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, packageManagerInternal);
+ doReturn(false).when(packageManagerInternal).isPermissionsReviewRequired(
+ anyString(), anyInt());
+ doReturn(null).when(packageManagerInternal).getDefaultHomeActivity(anyInt());
+
+ // PowerManagerInternal
final PowerManagerInternal pmi = mock(PowerManagerInternal.class);
final PowerSaveState state = new PowerSaveState.Builder().build();
doReturn(state).when(pmi).getLowPowerState(anyInt());
doReturn(pmi).when(() -> LocalServices.getService(eq(PowerManagerInternal.class)));
- final ActivityManagerInternal ami = mock(ActivityManagerInternal.class);
- doReturn(ami).when(() -> LocalServices.getService(eq(ActivityManagerInternal.class)));
+ // PermissionPolicyInternal
+ final PermissionPolicyInternal ppi = mock(PermissionPolicyInternal.class);
+ LocalServices.addService(PermissionPolicyInternal.class, ppi);
+ doReturn(true).when(ppi).checkStartActivity(any(), anyInt(), any());
- final ActivityTaskManagerInternal atmi = mock(ActivityTaskManagerInternal.class);
- doAnswer((InvocationOnMock invocationOnMock) -> {
- final Runnable runnable = invocationOnMock.getArgument(0);
- if (runnable != null) {
- runnable.run();
- }
- return null;
- }).when(atmi).notifyKeyguardFlagsChanged(nullable(Runnable.class), anyInt());
- doReturn(atmi).when(() -> LocalServices.getService(eq(ActivityTaskManagerInternal.class)));
-
- final InputManagerService ims = mock(InputManagerService.class);
+ // InputManagerService
+ mImService = mock(InputManagerService.class);
// InputChannel is final and can't be mocked.
final InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
if (input != null && input.length > 1) {
- doReturn(input[1]).when(ims).monitorInput(anyString(), anyInt());
+ doReturn(input[1]).when(mImService).monitorInput(anyString(), anyInt());
}
- final ActivityTaskManagerService atms = mock(ActivityTaskManagerService.class);
- final TaskChangeNotificationController taskChangeNotificationController = mock(
- TaskChangeNotificationController.class);
- doReturn(taskChangeNotificationController).when(atms).getTaskChangeNotificationController();
- final WindowManagerGlobalLock wmLock = new WindowManagerGlobalLock();
- doReturn(wmLock).when(atms).getGlobalLock();
+ // StatusBarManagerInternal
+ final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
+ doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+ }
- mWindowManagerPolicy = new TestWindowManagerPolicy(this::getWindowManagerService);
- mWindowManagerService = WindowManagerService.main(
- context, ims, false, false, mWindowManagerPolicy, atms, StubTransaction::new);
+ private void setUpActivityTaskManagerService() {
+ // ActivityManagerService
+ mAmService = new ActivityManagerService(
+ new AMTestInjector(mContext, mHandlerThread), mHandlerThread);
+ spyOn(mAmService);
+ doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager();
+ doNothing().when(mAmService).grantEphemeralAccessLocked(
+ anyInt(), any(), anyInt(), anyInt());
- mWindowManagerService.onInitReady();
+ // ActivityManagerInternal
+ final ActivityManagerInternal amInternal = mAmService.mInternal;
+ spyOn(amInternal);
+ doNothing().when(amInternal).trimApplications();
+ doNothing().when(amInternal).updateCpuStats();
+ doNothing().when(amInternal).updateOomAdj();
+ doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());
+ doNothing().when(amInternal).updateActivityUsageStats(
+ any(), anyInt(), anyInt(), any(), any());
+ doNothing().when(amInternal).startProcess(
+ any(), any(), anyBoolean(), anyBoolean(), any(), any());
+ doNothing().when(amInternal).updateOomLevelsForDisplay(anyInt());
+ LocalServices.addService(ActivityManagerInternal.class, amInternal);
- final Display display = mWindowManagerService.mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- // Display creation is driven by the ActivityManagerService via
- // ActivityStackSupervisor. We emulate those steps here.
- DisplayContent displayContent = mWindowManagerService.mRoot
- .createDisplayContent(display, mock(ActivityDisplay.class));
- displayContent.reconfigureDisplayLocked();
+ mAtmService = new TestActivityTaskManagerService(mContext, mAmService);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mAtmService.getAtmInternal());
+ }
- mMockTracker.stopTracking();
+ private void setUpWindowManagerService() {
+ mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
+ mWMPolicy = new TestWindowManagerPolicy(this::getWindowManagerService,
+ mPowerManagerWrapper);
+ mWmService = WindowManagerService.main(
+ mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new);
+ spyOn(mWmService);
+
+ // Setup factory classes to prevent calls to native code.
+ mTransaction = spy(StubTransaction.class);
+ // Return a spied Transaction class than can be used to verify calls.
+ mWmService.mTransactionFactory = () -> mTransaction;
+ // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances.
+ mWmService.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder();
+ // Return mocked Surface instances.
+ mWmService.mSurfaceFactory = () -> mock(Surface.class);
+ mWmService.mSurfaceAnimationRunner = new SurfaceAnimationRunner(
+ null, null, mTransaction, mWmService.mPowerManagerInternal);
+
+ mWmService.onInitReady();
+ mAmService.setWindowManager(mWmService);
+ mWmService.mDisplayEnabled = true;
+ mWmService.mDisplayReady = true;
+ // Set configuration for default display
+ mWmService.getDefaultDisplayContentLocked().reconfigureDisplayLocked();
+
+ // Mock root, some default display, and home stack.
+ spyOn(mWmService.mRoot);
+ final ActivityDisplay display = mAtmService.mRootActivityContainer.getDefaultDisplay();
+ spyOn(display);
+ spyOn(display.mDisplayContent);
+ final ActivityStack homeStack = display.getStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+ spyOn(homeStack);
+ spyOn(homeStack.mTaskStack);
}
private void tearDown() {
waitUntilWindowManagerHandlersIdle();
- removeLocalServices();
- mWindowManagerService = null;
- mWindowManagerPolicy = null;
+ // Unregister display listener from root to avoid issues with subsequent tests.
+ mContext.getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(mAtmService.mRootActivityContainer);
+ // ProptertiesChangesListener is registered in the constructor of WindowManagerService to
+ // a static object, so we need to clean it up in tearDown(), even though we didn't set up
+ // in tests.
+ DeviceConfig.removeOnPropertiesChangedListener(mWmService.mPropertiesChangedListener);
+ mWmService = null;
+ mWMPolicy = null;
+ mPowerManagerWrapper = null;
+
+ tearDownLocalServices();
+ tearDownSystemCore();
+
+ // Needs to explicitly dispose current static threads because there could be messages
+ // scheduled at a later time, and all mocks are invalid when it's executed.
+ DisplayThread.dispose();
+ AnimationThread.dispose();
+ // Reset priority booster because animation thread has been changed.
+ WindowManagerService.sThreadPriorityBooster = new WindowManagerThreadPriorityBooster();
+
+ Mockito.framework().clearInlineMocks();
+ }
+
+ private void tearDownSystemCore() {
if (mMockitoSession != null) {
mMockitoSession.finishMocking();
mMockitoSession = null;
}
- if (mMockTracker != null) {
- mMockTracker.close();
- mMockTracker = null;
+ if (mHandlerThread != null) {
+ // Make sure there are no running messages and then quit the thread so the next test
+ // won't be affected.
+ mHandlerThread.getThreadHandler().runWithScissors(mHandlerThread::quit,
+ 0 /* timeout */);
}
}
- private static void removeLocalServices() {
+ private static void tearDownLocalServices() {
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
}
WindowManagerService getWindowManagerService() {
- return mWindowManagerService;
+ return mWmService;
+ }
+
+ ActivityTaskManagerService getActivityTaskManagerService() {
+ return mAtmService;
+ }
+
+ WindowState.PowerManagerWrapper getPowerManagerWrapper() {
+ return mPowerManagerWrapper;
}
void cleanupWindowManagerHandlers() {
@@ -229,6 +373,7 @@
waitHandlerIdle(wm.mH);
waitHandlerIdle(wm.mAnimationHandler);
waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ waitHandlerIdle(mHandlerThread.getThreadHandler());
}
private void waitHandlerIdle(Handler handler) {
@@ -251,4 +396,121 @@
}
}
}
+
+ protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
+ // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
+ // We keep the reference in order to prevent creating it twice.
+ ActivityStackSupervisor mTestStackSupervisor;
+
+ TestActivityTaskManagerService(Context context, ActivityManagerService ams) {
+ super(context);
+ spyOn(this);
+
+ mSupportsMultiWindow = true;
+ mSupportsMultiDisplay = true;
+ mSupportsSplitScreenMultiWindow = true;
+ mSupportsFreeformWindowManagement = true;
+ mSupportsPictureInPicture = true;
+
+ doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
+ // allow background activity starts by default
+ doReturn(true).when(this).isBackgroundActivityStartsEnabled();
+ doNothing().when(this).updateCpuStats();
+
+ // AppOpsService
+ final AppOpsService aos = mock(AppOpsService.class);
+ doReturn(aos).when(this).getAppOpsService();
+ // Make sure permission checks aren't overridden.
+ doReturn(AppOpsManager.MODE_DEFAULT)
+ .when(aos).noteOperation(anyInt(), anyInt(), anyString());
+
+ setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class));
+ ams.mActivityTaskManager = this;
+ ams.mAtmInternal = mInternal;
+ onActivityManagerInternalAdded();
+ initialize(
+ ams.mIntentFirewall, ams.mPendingIntentController, mHandlerThread.getLooper());
+ spyOn(getLifecycleManager());
+ spyOn(getLockTaskController());
+ spyOn(getTaskChangeNotificationController());
+ initRootActivityContainerMocks();
+ }
+
+ void initRootActivityContainerMocks() {
+ spyOn(mRootActivityContainer);
+ // Invoked during {@link ActivityStack} creation.
+ doNothing().when(mRootActivityContainer).updateUIDsPresentOnDisplay();
+ // Always keep things awake.
+ doReturn(true).when(mRootActivityContainer).hasAwakeDisplay();
+ // Called when moving activity to pinned stack.
+ doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(),
+ anyBoolean());
+ }
+
+ @Override
+ int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
+ return userId;
+ }
+
+ @Override
+ protected ActivityStackSupervisor createStackSupervisor() {
+ if (mTestStackSupervisor == null) {
+ mTestStackSupervisor = new TestActivityStackSupervisor(this, mH.getLooper());
+ }
+ return mTestStackSupervisor;
+ }
+ }
+
+ /**
+ * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on
+ * setup not available in the test environment. Also specifies an injector for
+ */
+ protected class TestActivityStackSupervisor extends ActivityStackSupervisor {
+
+ TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
+ super(service, looper);
+ spyOn(this);
+
+ // Do not schedule idle that may touch methods outside the scope of the test.
+ doNothing().when(this).scheduleIdleLocked();
+ doNothing().when(this).scheduleIdleTimeoutLocked(any());
+ // unit test version does not handle launch wake lock
+ doNothing().when(this).acquireLaunchWakelock();
+ doReturn(mock(KeyguardController.class)).when(this).getKeyguardController();
+
+ mLaunchingActivityWakeLock = mock(PowerManager.WakeLock.class);
+
+ initialize();
+ }
+ }
+
+ // TODO: Can we just mock this?
+ private static class AMTestInjector extends ActivityManagerService.Injector {
+ private ServiceThread mHandlerThread;
+
+ AMTestInjector(Context context, ServiceThread handlerThread) {
+ super(context);
+ mHandlerThread = handlerThread;
+ }
+
+ @Override
+ public Context getContext() {
+ return getInstrumentation().getTargetContext();
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return null;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public boolean isNetworkRestrictedForUid(int uid) {
+ return false;
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 2cebebf..e6c9b9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,6 +249,20 @@
}
@Test
+ public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
+ final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FULLSCREEN);
+ final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+ source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
public void testKeepsPictureInPictureLaunchModeInOptions() {
final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
WINDOWING_MODE_FREEFORM);
@@ -571,6 +585,23 @@
}
@Test
+ public void testRespectsLaunchBoundsWithFreeformSourceOnFullscreenDisplay() {
+ final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FULLSCREEN);
+ final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+ source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ final Rect expected = new Rect(0, 0, 150, 150);
+ options.setLaunchBounds(expected);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ assertEquals(expected, mResult.mBounds);
+ }
+
+ @Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
WINDOWING_MODE_FREEFORM);
@@ -1277,17 +1308,18 @@
}
private ActivityRecord createSourceActivity(TestActivityDisplay display) {
- final TestActivityStack stack = display.createStack(display.getWindowingMode(),
+ final ActivityStack stack = display.createStack(display.getWindowingMode(),
ACTIVITY_TYPE_STANDARD, true);
return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
}
private void addFreeformTaskTo(TestActivityDisplay display, Rect bounds) {
- final TestActivityStack stack = display.createStack(display.getWindowingMode(),
+ final ActivityStack stack = display.createStack(display.getWindowingMode(),
ACTIVITY_TYPE_STANDARD, true);
stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
- task.setBounds(bounds);
+ // Just work around the unnecessary adjustments for bounds.
+ task.getWindowConfiguration().setBounds(bounds);
}
private void assertEquivalentWindowingMode(int expected, int actual, int parentWindowingMode) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index a0302f6..c83401b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -28,14 +28,19 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
+import static com.android.server.wm.DisplayRotation.FIXED_TO_USER_ROTATION_ENABLED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
@@ -64,6 +69,7 @@
import android.util.Xml;
import android.view.DisplayInfo;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import com.android.internal.app.IVoiceInteractor;
@@ -102,6 +108,7 @@
public void setUp() throws Exception {
TaskRecord.setTaskRecordFactory(null);
mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
+ removeGlobalMinSizeRestriction();
}
@Test
@@ -165,6 +172,7 @@
/** Ensures that bounds on freeform stacks are not clipped. */
@Test
+ @FlakyTest(bugId = 137879065)
public void testAppBounds_FreeFormBounds() {
final Rect freeFormBounds = new Rect(mParentBounds);
freeFormBounds.offset(10, 10);
@@ -174,6 +182,7 @@
/** Ensures that fully contained bounds are not clipped. */
@Test
+ @FlakyTest(bugId = 137879065)
public void testAppBounds_ContainedBounds() {
final Rect insetBounds = new Rect(mParentBounds);
insetBounds.inset(5, 5, 5, 5);
@@ -182,6 +191,7 @@
}
@Test
+ @FlakyTest(bugId = 137879065)
public void testFitWithinBounds() {
final Rect parentBounds = new Rect(10, 10, 200, 200);
ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
@@ -221,6 +231,7 @@
/** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
@Test
+ @FlakyTest(bugId = 137879065)
public void testBoundsOnModeChangeFreeformToFullscreen() {
ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display)
@@ -248,18 +259,6 @@
}
/**
- * This is a temporary hack to trigger an onConfigurationChange at the task level after an
- * orientation is requested. Normally this is done by the onDescendentOrientationChanged call
- * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here.
- * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged.
- */
- private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) {
- activity.setRequestedOrientation(orientation);
- ConfigurationContainer taskRecord = activity.getParent();
- taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration());
- }
-
- /**
* Tests that a task with forced orientation has orientation-consistent bounds within the
* parent.
*/
@@ -268,49 +267,48 @@
final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
DisplayInfo info = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(info);
info.logicalWidth = fullScreenBounds.width();
info.logicalHeight = fullScreenBounds.height();
ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
- // Override display orientation. Normally this is available via DisplayContent, but DC
- // is mocked-out.
- display.getRequestedOverrideConfiguration().orientation =
- Configuration.ORIENTATION_LANDSCAPE;
- display.onRequestedOverrideConfigurationChanged(
- display.getRequestedOverrideConfiguration());
+ // Fix the display orientation to landscape which is the natural rotation (0) for the test
+ // display.
+ final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
+ dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
+ dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+
ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
TaskRecord task = stack.getChildAt(0);
ActivityRecord root = task.getTopActivity();
- assertEquals(root, task.getTopActivity());
assertEquals(fullScreenBounds, task.getBounds());
// Setting app to fixed portrait fits within parent
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT);
+ root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(root, task.getRootActivity());
assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
- assertTrue(task.getBounds().width() < task.getBounds().height());
+ assertThat(task.getBounds().width()).isLessThan(task.getBounds().height());
assertEquals(fullScreenBounds.height(), task.getBounds().height());
// Top activity gets used
ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
assertEquals(top, task.getTopActivity());
- setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(task.getBounds().width() > task.getBounds().height());
+ top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
assertEquals(task.getBounds().width(), fullScreenBounds.width());
// Setting app to unspecified restores
- setActivityRequestedOrientation(top, SCREEN_ORIENTATION_UNSPECIFIED);
+ top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
assertEquals(fullScreenBounds, task.getBounds());
// Setting app to fixed landscape and changing display
- setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
- // simulate display orientation changing (normally done via DisplayContent)
- display.getRequestedOverrideConfiguration().orientation =
- Configuration.ORIENTATION_PORTRAIT;
- display.setBounds(fullScreenBoundsPort);
- assertTrue(task.getBounds().width() > task.getBounds().height());
+ top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ // Fix the display orientation to portrait which is 90 degrees for the test display.
+ dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90);
+
+ assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
// in FREEFORM, no constraint
@@ -323,7 +321,7 @@
// FULLSCREEN letterboxes bounds
stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertTrue(task.getBounds().width() > task.getBounds().height());
+ assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
// FREEFORM restores bounds as before
@@ -335,6 +333,7 @@
public void testIgnoresForcedOrientationWhenParentHandles() {
final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
DisplayInfo info = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(info);
info.logicalWidth = fullScreenBounds.width();
info.logicalHeight = fullScreenBounds.height();
ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
@@ -355,7 +354,7 @@
// Setting app to fixed portrait fits within parent, but TaskRecord shouldn't adjust the
// bounds because its parent says it will handle it at a later time.
- setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT);
+ root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(root, task.getRootActivity());
assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
assertEquals(fullScreenBounds, task.getBounds());
@@ -646,7 +645,7 @@
final ActivityRecord activity0 = task0.getChildAt(0);
final TaskRecord task1 = getTestTask();
- final ActivityRecord activity1 = task0.getChildAt(0);
+ final ActivityRecord activity1 = task1.getChildAt(0);
assertEquals(task0.taskId,
ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
new file mode 100644
index 0000000..c143969
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 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.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.hardware.display.DisplayManagerGlobal;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+class TestActivityDisplay extends ActivityDisplay {
+ private final ActivityStackSupervisor mSupervisor;
+
+ static TestActivityDisplay create(ActivityStackSupervisor supervisor) {
+ return create(supervisor, SystemServicesTestRule.sNextDisplayId++);
+ }
+
+ static TestActivityDisplay create(ActivityStackSupervisor supervisor, DisplayInfo info) {
+ return create(supervisor, SystemServicesTestRule.sNextDisplayId++, info);
+ }
+
+ static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) {
+ final DisplayInfo info = new DisplayInfo();
+ supervisor.mService.mContext.getDisplay().getDisplayInfo(info);
+ return create(supervisor, displayId, info);
+ }
+
+ static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId,
+ DisplayInfo info) {
+ if (displayId == DEFAULT_DISPLAY) {
+ synchronized (supervisor.mService.mGlobalLock) {
+ return new TestActivityDisplay(supervisor,
+ supervisor.mRootActivityContainer.mDisplayManager.getDisplay(displayId));
+ }
+ }
+ final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+ info, DEFAULT_DISPLAY_ADJUSTMENTS);
+
+ synchronized (supervisor.mService.mGlobalLock) {
+ return new TestActivityDisplay(supervisor, display);
+ }
+ }
+
+ private TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
+ super(supervisor.mService.mRootActivityContainer, display);
+ // Normally this comes from display-properties as exposed by WM. Without that, just
+ // hard-code to FULLSCREEN for tests.
+ setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mSupervisor = supervisor;
+ spyOn(this);
+ spyOn(mDisplayContent);
+ doAnswer(invocation -> {
+ // Bypass all the rotation animation and display freezing stuff for testing and just
+ // set the rotation we want for the display
+ final DisplayContent dc = mDisplayContent;
+ final int oldRotation = dc.getRotation();
+ final int rotation = dc.getDisplayRotation().rotationForOrientation(
+ dc.getLastOrientation(), oldRotation);
+ if (oldRotation == rotation) {
+ return false;
+ }
+ dc.setLayoutNeeded();
+ dc.setRotation(rotation);
+ return true;
+ }).when(mDisplayContent).updateRotationUnchecked(anyBoolean());
+ }
+
+ @SuppressWarnings("TypeParameterUnusedInFormals")
+ @Override
+ ActivityStack createStackUnchecked(int windowingMode, int activityType,
+ int stackId, boolean onTop) {
+ return new ActivityTestsBase.StackBuilder(mSupervisor.mRootActivityContainer)
+ .setDisplay(this)
+ .setWindowingMode(windowingMode)
+ .setActivityType(activityType)
+ .setStackId(stackId)
+ .setOnTop(onTop)
+ .setCreateActivity(false)
+ .build();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 2e5ce69..bb89446 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -40,12 +40,14 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowState.PowerManagerWrapper;
import java.io.PrintWriter;
import java.util.function.Supplier;
class TestWindowManagerPolicy implements WindowManagerPolicy {
private final Supplier<WindowManagerService> mWmSupplier;
+ private final PowerManagerWrapper mPowerManagerWrapper;
int mRotationToReport = 0;
boolean mKeyguardShowingAndNotOccluded = false;
@@ -53,8 +55,10 @@
private Runnable mRunnableWhenAddingSplashScreen;
- TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier) {
+ TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier,
+ PowerManagerWrapper powerManagerWrapper) {
mWmSupplier = wmSupplier;
+ mPowerManagerWrapper = powerManagerWrapper;
}
@Override
@@ -121,7 +125,7 @@
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
"Starting window", 0 /* ownerId */, false /* internalWindows */, wm,
- mock(Session.class), iWindow);
+ mock(Session.class), iWindow, mPowerManagerWrapper);
atoken.startingWindow = window;
}
if (mRunnableWhenAddingSplashScreen != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index acfc2ea..921f105 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -113,6 +114,7 @@
@Test
public void testAddChildSetsSurfacePosition() {
+ reset(mTransaction);
try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) {
WindowContainer child = new WindowContainer(mWm);
child.setBounds(1, 1, 10, 10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 06bcdf8..60cefe8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -57,6 +57,7 @@
private final IWindow mIWindow = new TestIWindow();
private final Rect mEmptyRect = new Rect();
+ private DisplayContent mTestDisplayContent;
static class FrameTestWindowState extends WindowState {
boolean mDockedResizingForTest = false;
@@ -77,6 +78,9 @@
@Before
public void setUp() throws Exception {
mStubStack = mock(TaskStack.class);
+ DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
+ testDisplayInfo.displayCutout = null;
+ mTestDisplayContent = createNewDisplay(testDisplayInfo);
}
// Do not use this function directly in the tests below. Instead, use more explicit function
@@ -100,6 +104,10 @@
assertRect(w.getStableInsets(), left, top, right, bottom);
}
+ private void assertFrame(WindowState w, Rect frame) {
+ assertEquals(w.getFrameLw(), frame);
+ }
+
private void assertFrame(WindowState w, int left, int top, int right, int bottom) {
assertRect(w.getFrameLw(), left, top, right, bottom);
}
@@ -380,9 +388,10 @@
}
@Test
+ @FlakyTest(bugId = 137879065)
public void testLayoutLetterboxedWindow() {
// First verify task behavior in multi-window mode.
- final DisplayInfo displayInfo = mWm.getDefaultDisplayContentLocked().getDisplayInfo();
+ final DisplayInfo displayInfo = mTestDisplayContent.getDisplayInfo();
final int logicalWidth = displayInfo.logicalWidth;
final int logicalHeight = displayInfo.logicalHeight;
@@ -413,13 +422,14 @@
final Rect cf = new Rect(xInset, 0, logicalWidth - xInset, logicalHeight);
Configuration config = new Configuration(w.mAppToken.getRequestedOverrideConfiguration());
config.windowConfiguration.setBounds(cf);
+ config.windowConfiguration.setAppBounds(cf);
w.mAppToken.onRequestedOverrideConfigurationChanged(config);
pf.set(0, 0, logicalWidth, logicalHeight);
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
task.setBounds(null);
windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect);
w.computeFrameLw();
- assertFrame(w, cf.left, cf.top, cf.right, cf.bottom);
+ assertFrame(w, cf);
assertContentFrame(w, cf);
assertContentInset(w, 0, 0, 0, 0);
}
@@ -483,7 +493,7 @@
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
task.setWindowingMode(WINDOWING_MODE_FREEFORM);
- DisplayContent dc = mWm.getDefaultDisplayContentLocked();
+ DisplayContent dc = mTestDisplayContent;
dc.mInputMethodTarget = w;
WindowState mockIme = mock(WindowState.class);
Mockito.doReturn(true).when(mockIme).isVisibleNow();
@@ -537,7 +547,7 @@
attrs.width = width;
attrs.height = height;
- AppWindowToken token = createAppWindowToken(mWm.getDefaultDisplayContentLocked(),
+ AppWindowToken token = createAppWindowToken(mTestDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
FrameTestWindowState ws = new FrameTestWindowState(mWm, mIWindow, token, attrs);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 36698ea..b731628 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -346,24 +346,28 @@
firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
- reset(sPowerManagerWrapper);
+ final WindowState.PowerManagerWrapper powerManagerWrapper =
+ mSystemServicesTestRule.getPowerManagerWrapper();
+ reset(powerManagerWrapper);
firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
- reset(sPowerManagerWrapper);
+ reset(powerManagerWrapper);
secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
}
private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) {
- reset(sPowerManagerWrapper);
+ final WindowState.PowerManagerWrapper powerManagerWrapper =
+ mSystemServicesTestRule.getPowerManagerWrapper();
+ reset(powerManagerWrapper);
appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
if (expectedWakeupCalled) {
- verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
} else {
- verify(sPowerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
}
// If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
// because the state will be consumed.
@@ -517,13 +521,19 @@
@Test
public void testGetTransformationMatrix() {
+ final int PARENT_WINDOW_OFFSET = 1;
+ final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
+ final int WINDOW_OFFSET = 3;
+ final float OFFSET_SUM =
+ PARENT_WINDOW_OFFSET + DISPLAY_IN_PARENT_WINDOW_OFFSET + WINDOW_OFFSET;
+
synchronized (mWm.mGlobalLock) {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
- win0.getFrameLw().offsetTo(1, 0);
final DisplayContent dc = createNewDisplay();
+ win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
dc.reparentDisplayContent(win0, win0.getSurfaceControl());
- dc.updateLocation(win0, 2, 0);
+ dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
final float[] values = new float[9];
final Matrix matrix = new Matrix();
@@ -531,12 +541,12 @@
final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
win1.mHasSurface = true;
win1.mSurfaceControl = mock(SurfaceControl.class);
- win1.getFrameLw().offsetTo(3, 0);
+ win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
win1.updateSurfacePosition(t);
win1.getTransformationMatrix(values, matrix);
matrix.getValues(values);
- assertEquals(6f, values[Matrix.MTRANS_X], 0f);
+ assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index dc461d1..d1cf1c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,8 +20,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.Process.SYSTEM_UID;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -38,33 +36,27 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
import static org.mockito.Mockito.mock;
-
import android.content.Context;
import android.content.res.Configuration;
-import android.hardware.display.DisplayManagerGlobal;
import android.testing.DexmakerShareClassLoaderRule;
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IWindow;
-import android.view.Surface;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
import com.android.server.AttributeCache;
-import com.android.server.wm.utils.MockTracker;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
-import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -79,10 +71,6 @@
WindowManagerService mWm;
private final IWindow mIWindow = new TestIWindow();
private Session mMockSession;
- // The default display is removed in {@link #setUp} and then we iterate over all displays to
- // make sure we don't collide with any existing display. If we run into no other display, the
- // added display should be treated as default. This cannot be the default display
- private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
static int sNextStackId = 1000;
/** Non-default display. */
@@ -99,8 +87,6 @@
WindowState mChildAppWindowBelow;
HashSet<WindowState> mCommonWindows;
- private MockTracker mMockTracker;
-
/**
* Spied {@link Transaction} class than can be used to verify calls.
*/
@@ -112,49 +98,27 @@
@Rule
public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
- static WindowState.PowerManagerWrapper sPowerManagerWrapper;
-
@BeforeClass
public static void setUpOnceBase() {
AttributeCache.init(getInstrumentation().getTargetContext());
-
- sPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
- }
-
- @AfterClass
- public static void tearDownOnceBase() throws IOException {
- sPowerManagerWrapper = null;
}
@Before
public void setUpBase() {
- mMockTracker = new MockTracker();
-
// If @Before throws an exception, the error isn't logged. This will make sure any failures
// in the set up are clear. This can be removed when b/37850063 is fixed.
try {
mMockSession = mock(Session.class);
- mTransaction = spy(StubTransaction.class);
final Context context = getInstrumentation().getTargetContext();
mWm = mSystemServicesTestRule.getWindowManagerService();
-
- // Setup factory classes to prevent calls to native code.
-
- // Return a spied Transaction class than can be used to verify calls.
- mWm.mTransactionFactory = () -> mTransaction;
- // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances.
- mWm.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder();
- // Return mocked Surface instances.
- mWm.mSurfaceFactory = () -> mock(Surface.class);
+ mTransaction = mSystemServicesTestRule.mTransaction;
beforeCreateDisplay();
context.getDisplay().getDisplayInfo(mDisplayInfo);
mDisplayContent = createNewDisplay();
- mWm.mDisplayEnabled = true;
- mWm.mDisplayReady = true;
// Set-up some common windows.
mCommonWindows = new HashSet<>();
@@ -211,12 +175,6 @@
nonCommonWindows.pollLast().removeImmediately();
}
- for (int i = mWm.mRoot.mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent displayContent = mWm.mRoot.mChildren.get(i);
- if (!displayContent.isDefaultDisplay) {
- displayContent.removeImmediately();
- }
- }
// Remove app transition & window freeze timeout callbacks to prevent unnecessary
// actions after test.
mWm.getDefaultDisplayContentLocked().mAppTransition
@@ -230,11 +188,7 @@
} catch (Exception e) {
Log.e(TAG, "Failed to tear down test", e);
throw e;
- } finally {
- mMockTracker.close();
- mMockTracker = null;
}
-
}
private WindowState createCommonWindow(WindowState parent, int type, String name) {
@@ -356,12 +310,13 @@
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
int ownerId, boolean ownerCanAddInternalSystemWindow) {
return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
- mWm, mMockSession, mIWindow);
+ mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper());
}
static WindowState createWindow(WindowState parent, int type, WindowToken token,
String name, int ownerId, boolean ownerCanAddInternalSystemWindow,
- WindowManagerService service, Session session, IWindow iWindow) {
+ WindowManagerService service, Session session, IWindow iWindow,
+ WindowState.PowerManagerWrapper powerManagerWrapper) {
synchronized (service.mGlobalLock) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
attrs.setTitle(name);
@@ -369,7 +324,7 @@
final WindowState w = new WindowState(service, session, iWindow, token, parent,
OP_NONE,
0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
- sPowerManagerWrapper);
+ powerManagerWrapper);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);
@@ -408,13 +363,11 @@
}
/** Creates a {@link DisplayContent} and adds it to the system. */
- DisplayContent createNewDisplay(DisplayInfo displayInfo) {
- final int displayId = sNextDisplayId++;
- final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
- displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- synchronized (mWm.mGlobalLock) {
- return new DisplayContent(display, mWm, mock(ActivityDisplay.class));
- }
+ DisplayContent createNewDisplay(DisplayInfo info) {
+ final ActivityDisplay display =
+ TestActivityDisplay.create(mWm.mAtmService.mStackSupervisor, info);
+ mWm.mAtmService.mRootActivityContainer.addChild(display, POSITION_TOP);
+ return display.mDisplayContent;
}
/**
@@ -428,17 +381,7 @@
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.state = displayState;
- final int displayId = sNextDisplayId++;
- final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
- displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- synchronized (mWm.mGlobalLock) {
- // Display creation is driven by DisplayWindowController via ActivityStackSupervisor.
- // We skip those steps here.
- final ActivityDisplay mockAd = mock(ActivityDisplay.class);
- final DisplayContent displayContent = mWm.mRoot.createDisplayContent(display, mockAd);
- displayContent.reconfigureDisplayLocked();
- return displayContent;
- }
+ return createNewDisplay(displayInfo);
}
/** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java
index a6e675a..7f09482 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java
@@ -89,7 +89,7 @@
for (final Object mock : mMocks.keySet()) {
if (MockUtil.isMock(mock)) {
- Mockito.reset(mock);
+ mMockitoFramework.clearInlineMock(mock);
}
}
mMocks.clear();
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 43fb304..42a5501 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -692,7 +692,7 @@
}
/**
- * Contains all sent text-based SMS messages in the SMS app.
+ * Contains all draft text-based SMS messages in the SMS app.
*/
public static final class Draft implements BaseColumns, TextBasedSmsColumns {
@@ -808,7 +808,15 @@
}
/**
- * Contains all sent text-based SMS messages in the SMS app.
+ * Contains a view of SMS conversations (also referred to as threads). This is similar to
+ * {@link Threads}, but only includes SMS messages and columns relevant to SMS
+ * conversations.
+ * <p>
+ * Note that this view ignores any information about MMS messages, it is a
+ * view of conversations as if MMS messages did not exist at all. This means that all
+ * relevant information, such as snippets and message count, will ignore any MMS messages
+ * that might be in the same thread through other views and present only data based on the
+ * SMS messages in that thread.
*/
public static final class Conversations
implements BaseColumns, TextBasedSmsColumns {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 60de214..654b54d 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1026,6 +1026,18 @@
"call_forwarding_map_non_number_to_voicemail_bool";
/**
+ * When {@code true}, the phone will always tell the IMS stack to keep RTT enabled and
+ * determine on a per-call basis (based on extras from the dialer app) whether a call should be
+ * an RTT call or not.
+ *
+ * When {@code false}, the old behavior is used, where the toggle in accessibility settings is
+ * used to set the IMS stack's RTT enabled state.
+ * @hide
+ */
+ public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
+ "ignore_rtt_mode_setting_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -1391,6 +1403,13 @@
"read_only_apn_fields_string_array";
/**
+ * Default value of APN types field if not specified by user when adding/modifying an APN.
+ * @hide
+ */
+ public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY =
+ "apn_settings_default_apn_types_string_array";
+
+ /**
* Boolean indicating if intent for emergency call state changes should be broadcast
* @hide
*/
@@ -2656,25 +2675,34 @@
"call_waiting_service_class_int";
/**
- * This configuration allow the system UI to display different 5G icon for different 5G status.
+ * This configuration allow the system UI to display different 5G icon for different 5G
+ * scenario.
*
- * There are four 5G status:
+ * There are five 5G scenarios:
* 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
* millimeter wave.
* 2. connected: device currently connected to 5G cell as the secondary cell but not using
* millimeter wave.
- * 3. not_restricted: device camped on a network that has 5G capability(not necessary to connect
- * a 5G cell as a secondary cell) and the use of 5G is not restricted.
- * 4. restricted: device camped on a network that has 5G capability(not necessary to connect a
+ * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary
+ * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
+ * currently in IDLE state.
+ * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary
+ * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
+ * currently in CONNECTED state.
+ * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a
* 5G cell as a secondary cell) but the use of 5G is restricted.
*
* The configured string contains multiple key-value pairs separated by comma. For each pair,
* the key and value is separated by a colon. The key is corresponded to a 5G status above and
* the value is the icon name. Use "None" as the icon name if no icon should be shown in a
- * specific 5G status.
+ * specific 5G scenario. If the scenario is "None", config can skip this key and value.
*
- * Here is an example of the configuration:
- * "connected_mmwave:5GPlus,connected:5G,not_restricted:None,restricted:None"
+ * Icon name options: "5G_Plus", "5G".
+ *
+ * Here is an example:
+ * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no
+ * define.
+ * The configuration is: "connected_mmwave:5G_Plus,connected:5G"
*
* @hide
*/
@@ -3131,6 +3159,13 @@
public static final String KEY_SUPPORT_WPS_OVER_IMS_BOOL =
"support_wps_over_ims_bool";
+ /**
+ * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates
+ * @hide
+ */
+ public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
+ "carrier_certificate_string_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -3247,6 +3282,7 @@
sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, new String[] {"dun"});
sDefaults.putStringArray(KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, null);
+ sDefaults.putStringArray(KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
@@ -3290,6 +3326,7 @@
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
+ sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -3509,7 +3546,7 @@
sDefaults.putBoolean(KEY_USE_CALLER_ID_USSD_BOOL, false);
sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING,
- "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None");
+ "connected_mmwave:5G,connected:5G");
sDefaults.putInt(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0);
sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
/* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */
@@ -3537,7 +3574,7 @@
sDefaults.putStringArray(KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_USE_USIM_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, false);
- sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false);
+ sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, true);
sDefaults.putString(KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING, "");
sDefaults.putBoolean(KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN,
false);
@@ -3552,6 +3589,7 @@
});
sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
sDefaults.putAll(Ims.getDefaults());
+ sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
}
/**
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 258a873..432978d 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -61,7 +62,7 @@
mType = type;
// Only allow INT_MAX if unknown string mcc/mnc
- if (mcc == null || mcc.matches("^[0-9]{3}$")) {
+ if (mcc == null || isMcc(mcc)) {
mMccStr = mcc;
} else if (mcc.isEmpty() || mcc.equals(String.valueOf(Integer.MAX_VALUE))) {
// If the mccStr is empty or unknown, set it as null.
@@ -73,7 +74,7 @@
log("invalid MCC format: " + mcc);
}
- if (mnc == null || mnc.matches("^[0-9]{2,3}$")) {
+ if (mnc == null || isMnc(mnc)) {
mMncStr = mnc;
} else if (mnc.isEmpty() || mnc.equals(String.valueOf(Integer.MAX_VALUE))) {
// If the mncStr is empty or unknown, set it as null.
@@ -262,4 +263,30 @@
if ((value < rangeMin || value > rangeMax) && value != special) return CellInfo.UNAVAILABLE;
return value;
}
+
+ /** @hide */
+ private static boolean isMcc(@NonNull String mcc) {
+ // ensure no out of bounds indexing
+ if (mcc.length() != 3) return false;
+
+ // Character.isDigit allows all unicode digits, not just [0-9]
+ for (int i = 0; i < 3; i++) {
+ if (mcc.charAt(i) < '0' || mcc.charAt(i) > '9') return false;
+ }
+
+ return true;
+ }
+
+ /** @hide */
+ private static boolean isMnc(@NonNull String mnc) {
+ // ensure no out of bounds indexing
+ if (mnc.length() < 2 || mnc.length() > 3) return false;
+
+ // Character.isDigit allows all unicode digits, not just [0-9]
+ for (int i = 0; i < mnc.length(); i++) {
+ if (mnc.charAt(i) < '0' || mnc.charAt(i) > '9') return false;
+ }
+
+ return true;
+ }
}
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 1c64bcd..89b9665 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -24,7 +24,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
/**
* Network service callback. Object of this class is passed to NetworkServiceProvider upon
@@ -61,11 +60,11 @@
/** Request failed */
public static final int RESULT_ERROR_FAILED = 5;
- private final WeakReference<INetworkServiceCallback> mCallback;
+ private final INetworkServiceCallback mCallback;
/** @hide */
public NetworkServiceCallback(INetworkServiceCallback callback) {
- mCallback = new WeakReference<>(callback);
+ mCallback = callback;
}
/**
@@ -78,15 +77,14 @@
*/
public void onRequestNetworkRegistrationInfoComplete(int result,
@Nullable NetworkRegistrationInfo state) {
- INetworkServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
- callback.onRequestNetworkRegistrationInfoComplete(result, state);
+ mCallback.onRequestNetworkRegistrationInfoComplete(result, state);
} catch (RemoteException e) {
Rlog.e(mTag, "Failed to onRequestNetworkRegistrationInfoComplete on the remote");
}
} else {
- Rlog.e(mTag, "Weak reference of callback is null.");
+ Rlog.e(mTag, "onRequestNetworkRegistrationInfoComplete callback is null.");
}
}
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a8491d3..bb2269f 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -38,6 +38,7 @@
import android.util.DisplayMetrics;
import android.util.Log;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -147,7 +148,14 @@
* The access rules for this subscription, if it is embedded and defines any.
*/
@Nullable
- private UiccAccessRule[] mAccessRules;
+ private UiccAccessRule[] mNativeAccessRules;
+
+ /**
+ * The carrier certificates for this subscription that are saved in carrier configs.
+ * The other carrier certificates are embedded on Uicc and stored as part of mNativeAccessRules.
+ */
+ @Nullable
+ private UiccAccessRule[] mCarrierConfigAccessRules;
/**
* The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the
@@ -206,12 +214,12 @@
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
- @Nullable UiccAccessRule[] accessRules, String cardString) {
+ @Nullable UiccAccessRule[] nativeAccessRules, String cardString) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
- roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
+ roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
false, null, false, TelephonyManager.UNKNOWN_CARRIER_ID,
SubscriptionManager.PROFILE_CLASS_DEFAULT,
- SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
}
/**
@@ -220,12 +228,12 @@
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
- @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic,
- @Nullable String groupUUID, int carrierId, int profileClass) {
+ @Nullable UiccAccessRule[] nativeAccessRules, String cardString,
+ boolean isOpportunistic, @Nullable String groupUUID, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
- roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
+ roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
isOpportunistic, groupUUID, false, carrierId, profileClass,
- SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
}
/**
@@ -234,9 +242,10 @@
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
- @Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
+ @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
- int carrierId, int profileClass, int subType, @Nullable String groupOwner) {
+ int carrierId, int profileClass, int subType, @Nullable String groupOwner,
+ @Nullable UiccAccessRule[] carrierConfigAccessRules) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -251,7 +260,7 @@
this.mMnc = mnc;
this.mCountryIso = countryIso;
this.mIsEmbedded = isEmbedded;
- this.mAccessRules = accessRules;
+ this.mNativeAccessRules = nativeAccessRules;
this.mCardString = cardString;
this.mCardId = cardId;
this.mIsOpportunistic = isOpportunistic;
@@ -261,6 +270,7 @@
this.mProfileClass = profileClass;
this.mSubscriptionType = subType;
this.mGroupOwner = groupOwner;
+ this.mCarrierConfigAccessRules = carrierConfigAccessRules;
}
/**
@@ -541,7 +551,6 @@
*
* @param context Context of the application to check.
* @return whether the app is authorized to manage this subscription per its metadata.
- * @throws UnsupportedOperationException if this subscription is not embedded.
* @hide
* @deprecated - Do not use.
*/
@@ -557,16 +566,13 @@
* @param context Any context.
* @param packageName Package name of the app to check.
* @return whether the app is authorized to manage this subscription per its metadata.
- * @throws UnsupportedOperationException if this subscription is not embedded.
* @hide
* @deprecated - Do not use.
*/
@Deprecated
public boolean canManageSubscription(Context context, String packageName) {
- if (!isEmbedded()) {
- throw new UnsupportedOperationException("Not an embedded subscription");
- }
- if (mAccessRules == null) {
+ List<UiccAccessRule> allAccessRules = getAllAccessRules();
+ if (allAccessRules == null) {
return false;
}
PackageManager packageManager = context.getPackageManager();
@@ -576,7 +582,7 @@
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("Unknown package: " + packageName, e);
}
- for (UiccAccessRule rule : mAccessRules) {
+ for (UiccAccessRule rule : allAccessRules) {
if (rule.getCarrierPrivilegeStatus(packageInfo)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return true;
@@ -586,17 +592,31 @@
}
/**
- * @return the {@link UiccAccessRule}s dictating who is authorized to manage this subscription.
+ * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who
+ * is authorized to manage this subscription.
+ * TODO and fix it properly in R / master: either deprecate this and have 3 APIs
+ * native + carrier + all, or have this return all by default.
* @throws UnsupportedOperationException if this subscription is not embedded.
* @hide
*/
@SystemApi
public @Nullable List<UiccAccessRule> getAccessRules() {
- if (!isEmbedded()) {
- throw new UnsupportedOperationException("Not an embedded subscription");
+ if (mNativeAccessRules == null) return null;
+ return Arrays.asList(mNativeAccessRules);
+ }
+
+ /**
+ * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs
+ * dictating who is authorized to manage this subscription.
+ * @hide
+ */
+ public @Nullable List<UiccAccessRule> getAllAccessRules() {
+ List<UiccAccessRule> merged = new ArrayList<>();
+ if (mNativeAccessRules != null) merged.addAll(getAccessRules());
+ if (mCarrierConfigAccessRules != null) {
+ merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
}
- if (mAccessRules == null) return null;
- return Arrays.asList(mAccessRules);
+ return merged.isEmpty() ? null : merged;
}
/**
@@ -651,7 +671,7 @@
String countryIso = source.readString();
Bitmap iconBitmap = source.readParcelable(Bitmap.class.getClassLoader());
boolean isEmbedded = source.readBoolean();
- UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR);
+ UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
String cardString = source.readString();
int cardId = source.readInt();
boolean isOpportunistic = source.readBoolean();
@@ -663,11 +683,14 @@
String[] ehplmns = source.readStringArray();
String[] hplmns = source.readStringArray();
String groupOwner = source.readString();
+ UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
+ UiccAccessRule.CREATOR);
SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
- countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic,
- groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner);
+ countryIso, isEmbedded, nativeAccessRules, cardString, cardId, isOpportunistic,
+ groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner,
+ carrierConfigAccessRules);
info.setAssociatedPlmns(ehplmns, hplmns);
return info;
}
@@ -694,7 +717,7 @@
dest.writeString(mCountryIso);
dest.writeParcelable(mIconBitmap, flags);
dest.writeBoolean(mIsEmbedded);
- dest.writeTypedArray(mAccessRules, flags);
+ dest.writeTypedArray(mNativeAccessRules, flags);
dest.writeString(mCardString);
dest.writeInt(mCardId);
dest.writeBoolean(mIsOpportunistic);
@@ -706,6 +729,7 @@
dest.writeStringArray(mEhplmns);
dest.writeStringArray(mHplmns);
dest.writeString(mGroupOwner);
+ dest.writeTypedArray(mCarrierConfigAccessRules, flags);
}
@Override
@@ -736,9 +760,9 @@
+ " carrierId=" + mCarrierId + " displayName=" + mDisplayName
+ " carrierName=" + mCarrierName + " nameSource=" + mNameSource
+ " iconTint=" + mIconTint + " mNumber=" + Rlog.pii(Build.IS_DEBUGGABLE, mNumber)
- + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc
- + " mnc=" + mMnc + " mCountryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded
- + " accessRules=" + Arrays.toString(mAccessRules)
+ + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
+ + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
+ + " nativeAccessRules " + Arrays.toString(mNativeAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
+ " mIsGroupDisabled=" + mIsGroupDisabled
@@ -746,14 +770,15 @@
+ " ehplmns=" + Arrays.toString(mEhplmns)
+ " hplmns=" + Arrays.toString(mHplmns)
+ " subscriptionType=" + mSubscriptionType
- + " mGroupOwner=" + mGroupOwner + "}";
+ + " mGroupOwner=" + mGroupOwner
+ + " carrierConfigAccessRules=" + mCarrierConfigAccessRules + "}";
}
@Override
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc,
- mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
+ mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mNativeAccessRules,
mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner);
}
@@ -789,7 +814,7 @@
&& Objects.equals(mGroupOwner, toCompare.mGroupOwner)
&& TextUtils.equals(mDisplayName, toCompare.mDisplayName)
&& TextUtils.equals(mCarrierName, toCompare.mCarrierName)
- && Arrays.equals(mAccessRules, toCompare.mAccessRules)
+ && Arrays.equals(mNativeAccessRules, toCompare.mNativeAccessRules)
&& mProfileClass == toCompare.mProfileClass
&& Arrays.equals(mEhplmns, toCompare.mEhplmns)
&& Arrays.equals(mHplmns, toCompare.mHplmns);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8c53c51..26574c4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -571,6 +571,16 @@
public static final String ACCESS_RULES = "access_rules";
/**
+ * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+ * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs.
+ * Only present if there are access rules in CarrierConfigs
+ * <p>TYPE: BLOB
+ * @hide
+ */
+ public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
+ "access_rules_from_carrier_configs";
+
+ /**
* TelephonyProvider column name identifying whether an embedded subscription is on a removable
* card. Such subscriptions are marked inaccessible as soon as the current card is removed.
* Otherwise, they will remain accessible unless explicitly deleted. Only present if
@@ -2578,7 +2588,6 @@
*
* @param info The subscription to check.
* @return whether the app is authorized to manage this subscription per its metadata.
- * @throws IllegalArgumentException if this subscription is not embedded.
*/
public boolean canManageSubscription(SubscriptionInfo info) {
return canManageSubscription(info, mContext.getPackageName());
@@ -2594,14 +2603,10 @@
* @param info The subscription to check.
* @param packageName Package name of the app to check.
* @return whether the app is authorized to manage this subscription per its access rules.
- * @throws IllegalArgumentException if this subscription is not embedded.
* @hide
*/
public boolean canManageSubscription(SubscriptionInfo info, String packageName) {
- if (!info.isEmbedded()) {
- throw new IllegalArgumentException("Not an embedded subscription");
- }
- if (info.getAccessRules() == null) {
+ if (info.getAllAccessRules() == null) {
return false;
}
PackageManager packageManager = mContext.getPackageManager();
@@ -2611,7 +2616,7 @@
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("Unknown package: " + packageName, e);
}
- for (UiccAccessRule rule : info.getAccessRules()) {
+ for (UiccAccessRule rule : info.getAllAccessRules()) {
if (rule.getCarrierPrivilegeStatus(packageInfo)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return true;
@@ -3002,7 +3007,7 @@
// to the caller.
boolean hasCarrierPrivilegePermission = TelephonyManager.from(mContext)
.hasCarrierPrivileges(info.getSubscriptionId())
- || (info.isEmbedded() && canManageSubscription(info));
+ || canManageSubscription(info);
return hasCarrierPrivilegePermission;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 35b435d..553bff2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11047,6 +11047,8 @@
* The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current
* network returned by {@link #getNetworkCountryIso()}.
*
+ * <p>There may be a delay of several minutes before reporting that no country is detected.
+ *
* @see #EXTRA_NETWORK_COUNTRY
* @see #getNetworkCountryIso()
*/
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 89d30c0d..11dc78a 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -27,7 +27,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.List;
/**
@@ -64,11 +63,11 @@
/** Request sent in illegal state */
public static final int RESULT_ERROR_ILLEGAL_STATE = 4;
- private final WeakReference<IDataServiceCallback> mCallback;
+ private final IDataServiceCallback mCallback;
/** @hide */
public DataServiceCallback(IDataServiceCallback callback) {
- mCallback = new WeakReference<>(callback);
+ mCallback = callback;
}
/**
@@ -80,14 +79,15 @@
*/
public void onSetupDataCallComplete(@ResultCode int result,
@Nullable DataCallResponse response) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
if (DBG) Rlog.d(TAG, "onSetupDataCallComplete");
- callback.onSetupDataCallComplete(result, response);
+ mCallback.onSetupDataCallComplete(result, response);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onSetupDataCallComplete on the remote");
}
+ } else {
+ Rlog.e(TAG, "onSetupDataCallComplete: callback is null!");
}
}
@@ -98,14 +98,15 @@
* @param result The result code. Must be one of the {@link ResultCode}.
*/
public void onDeactivateDataCallComplete(@ResultCode int result) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
if (DBG) Rlog.d(TAG, "onDeactivateDataCallComplete");
- callback.onDeactivateDataCallComplete(result);
+ mCallback.onDeactivateDataCallComplete(result);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onDeactivateDataCallComplete on the remote");
}
+ } else {
+ Rlog.e(TAG, "onDeactivateDataCallComplete: callback is null!");
}
}
@@ -116,13 +117,14 @@
* @param result The result code. Must be one of the {@link ResultCode}.
*/
public void onSetInitialAttachApnComplete(@ResultCode int result) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
- callback.onSetInitialAttachApnComplete(result);
+ mCallback.onSetInitialAttachApnComplete(result);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onSetInitialAttachApnComplete on the remote");
}
+ } else {
+ Rlog.e(TAG, "onSetInitialAttachApnComplete: callback is null!");
}
}
@@ -133,13 +135,14 @@
* @param result The result code. Must be one of the {@link ResultCode}.
*/
public void onSetDataProfileComplete(@ResultCode int result) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
- callback.onSetDataProfileComplete(result);
+ mCallback.onSetDataProfileComplete(result);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onSetDataProfileComplete on the remote");
}
+ } else {
+ Rlog.e(TAG, "onSetDataProfileComplete: callback is null!");
}
}
@@ -153,13 +156,14 @@
*/
public void onRequestDataCallListComplete(@ResultCode int result,
@NonNull List<DataCallResponse> dataCallList) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
- callback.onRequestDataCallListComplete(result, dataCallList);
+ mCallback.onRequestDataCallListComplete(result, dataCallList);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onRequestDataCallListComplete on the remote");
}
+ } else {
+ Rlog.e(TAG, "onRequestDataCallListComplete: callback is null!");
}
}
@@ -170,14 +174,15 @@
* @param dataCallList List of the current active data connection.
*/
public void onDataCallListChanged(@NonNull List<DataCallResponse> dataCallList) {
- IDataServiceCallback callback = mCallback.get();
- if (callback != null) {
+ if (mCallback != null) {
try {
if (DBG) Rlog.d(TAG, "onDataCallListChanged");
- callback.onDataCallListChanged(dataCallList);
+ mCallback.onDataCallListChanged(dataCallList);
} catch (RemoteException e) {
Rlog.e(TAG, "Failed to onDataCallListChanged on the remote");
}
+ } else {
+ Rlog.e(TAG, "onDataCallListChanged: callback is null!");
}
}
}
diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp
index 78ebcb9..0278b43 100644
--- a/tools/aapt2/optimize/ResourceDeduper.cpp
+++ b/tools/aapt2/optimize/ResourceDeduper.cpp
@@ -63,13 +63,14 @@
// Compare compatible configs for this entry and ensure the values are
// equivalent.
const ConfigDescription& node_configuration = node_value->config;
- for (const auto& sibling : entry_->values) {
- if (!sibling->value) {
+ for (const auto& sibling : parent->children()) {
+ ResourceConfigValue* sibling_value = sibling->value();
+ if (!sibling_value->value) {
// Sibling was already removed.
continue;
}
- if (node_configuration.IsCompatibleWith(sibling->config) &&
- !node_value->value->Equals(sibling->value.get())) {
+ if (node_configuration.IsCompatibleWith(sibling_value->config) &&
+ !node_value->value->Equals(sibling_value->value.get())) {
// The configurations are compatible, but the value is
// different, so we can't remove this value.
return;
diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp
index 2e098ae..048e318 100644
--- a/tools/aapt2/optimize/ResourceDeduper_test.cpp
+++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp
@@ -80,11 +80,58 @@
.Build();
ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config));
EXPECT_THAT(table, HasValue("android:string/keep", land_config));
}
+TEST(ResourceDeduperTest, SameValuesAreDedupedIncompatibleSiblings) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+ const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night");
+ // Chosen because this configuration is not compatible with ldrtl-night.
+ const ConfigDescription ldrtl_notnight_config = test::ParseConfigOrDie("ldrtl-notnight");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_notnight_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config)));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_notnight_config));
+}
+
+TEST(ResourceDeduperTest, SameValuesAreDedupedCompatibleNonSiblings) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+ const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night");
+ // Chosen because this configuration is compatible with ldrtl.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, land_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config)));
+ EXPECT_THAT(table, HasValue("android:string/keep", land_config));
+}
+
TEST(ResourceDeduperTest, LocalesValuesAreKept) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const ConfigDescription default_config = {};
diff --git a/wifi/java/android/net/wifi/rtt/ResponderLocation.java b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
index e1d82f8..970a75d 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderLocation.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
@@ -605,11 +605,11 @@
// Negative 2's complement value
// Note: The Math.pow(...) method cannot return a NaN value because the bitFieldSize
// for Lat or Lng is limited to exactly 34 bits.
- angle = Math.scalb(fields[offset] - Math.pow(2, bitFieldSizes[offset]),
+ angle = Math.scalb((double) fields[offset] - Math.pow(2, bitFieldSizes[offset]),
-LATLNG_FRACTION_BITS);
} else {
// Positive 2's complement value
- angle = Math.scalb(fields[offset], -LATLNG_FRACTION_BITS);
+ angle = Math.scalb((double) fields[offset], -LATLNG_FRACTION_BITS);
}
if (angle > limit) {
angle = limit;
@@ -732,10 +732,11 @@
int maxBssidIndicator = (int) buffer[SUBELEMENT_BSSID_MAX_INDICATOR_INDEX] & BYTE_MASK;
int bssidListLength = (buffer.length - 1) / BYTES_IN_A_BSSID;
- // Check the max number of BSSIDs agrees with the list length.
- if (maxBssidIndicator != bssidListLength) {
- return false;
- }
+ // The maxBSSIDIndicator is ignored. Its use is still being clarified in 802.11REVmd,
+ // which is not published at this time. This field will be used in a future
+ // release of Android after 802.11REVmd is public. Here, we interpret the following
+ // params as an explicit list of BSSIDs.
+
int bssidOffset = SUBELEMENT_BSSID_LIST_INDEX;
for (int i = 0; i < bssidListLength; i++) {
diff --git a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
index 47c30409..b02eebb 100644
--- a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
@@ -37,7 +37,7 @@
*/
@RunWith(JUnit4.class)
public class ResponderLocationTest {
- private static final double LATLNG_TOLERANCE_DEGREES = 0.00001;
+ private static final double LATLNG_TOLERANCE_DEGREES = 0.000_000_05D; // 5E-8 = 6mm of meridian
private static final double ALT_TOLERANCE_METERS = 0.01;
private static final double HEIGHT_TOLERANCE_METERS = 0.01;
private static final int INDEX_ELEMENT_TYPE = 2;
@@ -103,7 +103,7 @@
private static final byte[] sTestBssidListSE = {
(byte) 0x07, // Subelement BSSID list
(byte) 13, // length dependent on number of BSSIDs in list
- (byte) 0x02, // Number of BSSIDs in list
+ (byte) 0x00, // List is explicit; no expansion of list required
(byte) 0x01, // BSSID #1 (MSB)
(byte) 0x02,
(byte) 0x03,
@@ -266,11 +266,11 @@
assertTrue(valid);
assertTrue(lciValid);
assertFalse(zValid);
- assertEquals(0.0009765625, responderLocation.getLatitudeUncertainty());
- assertEquals(-33.857009, responderLocation.getLatitude(),
+ assertEquals(0.0009765625D, responderLocation.getLatitudeUncertainty());
+ assertEquals(-33.8570095D, responderLocation.getLatitude(),
LATLNG_TOLERANCE_DEGREES);
- assertEquals(0.0009765625, responderLocation.getLongitudeUncertainty());
- assertEquals(151.215200, responderLocation.getLongitude(),
+ assertEquals(0.0009765625D, responderLocation.getLongitudeUncertainty());
+ assertEquals(151.2152005D, responderLocation.getLongitude(),
LATLNG_TOLERANCE_DEGREES);
assertEquals(1, responderLocation.getAltitudeType());
assertEquals(64.0, responderLocation.getAltitudeUncertainty());
@@ -282,11 +282,11 @@
assertEquals(1, responderLocation.getLciVersion());
// Testing Location Object
- assertEquals(-33.857009, location.getLatitude(),
+ assertEquals(-33.8570095D, location.getLatitude(),
LATLNG_TOLERANCE_DEGREES);
- assertEquals(151.215200, location.getLongitude(),
+ assertEquals(151.2152005D, location.getLongitude(),
LATLNG_TOLERANCE_DEGREES);
- assertEquals((0.0009765625 + 0.0009765625) / 2, location.getAccuracy(),
+ assertEquals((0.0009765625D + 0.0009765625D) / 2, location.getAccuracy(),
LATLNG_TOLERANCE_DEGREES);
assertEquals(11.2, location.getAltitude(), ALT_TOLERANCE_METERS);
assertEquals(64.0, location.getVerticalAccuracyMeters(), ALT_TOLERANCE_METERS);