Merge "Fix potential IOOB error in icon reordering" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 0c504b6..3669103 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5448,7 +5448,6 @@
}
@FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
- ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
method public int checkContentUriPermission(@NonNull android.net.Uri, int);
method @Nullable public String getPackage();
method public int getUid();
@@ -7425,6 +7424,7 @@
method public int onStartCommand(android.content.Intent, int, int);
method public void onTaskRemoved(android.content.Intent);
method public void onTimeout(int);
+ method @FlaggedApi("android.app.introduce_new_service_ontimeout_callback") public void onTimeout(int, int);
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
@@ -10045,11 +10045,22 @@
method public CharSequence coerceToText(android.content.Context);
method public String getHtmlText();
method public android.content.Intent getIntent();
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent();
method public CharSequence getText();
method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
method public android.net.Uri getUri();
}
+ @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final class ClipData.Item.Builder {
+ ctor public ClipData.Item.Builder();
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build();
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri);
+ }
+
public class ClipDescription implements android.os.Parcelable {
ctor public ClipDescription(CharSequence, String[]);
ctor public ClipDescription(android.content.ClipDescription);
@@ -11336,6 +11347,7 @@
field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
+ field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
@@ -52896,9 +52908,11 @@
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
+ field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000
field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
+ field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0f1da41..7936e9f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -282,6 +282,7 @@
field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission.READ_CLIPBOARD_IN_BACKGROUND";
@@ -409,6 +410,7 @@
field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
@@ -11457,6 +11459,29 @@
package android.provider {
+ public static class BlockedNumberContract.BlockedNumbers {
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void endBlockSuppression(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static android.provider.BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus getBlockSuppressionStatus(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean getBlockedNumberSetting(@NonNull android.content.Context, @NonNull String);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void notifyEmergencyContact(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void setBlockedNumberSetting(@NonNull android.content.Context, @NonNull String, boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean shouldShowEmergencyCallNotification(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static int shouldSystemBlockNumber(@NonNull android.content.Context, @NonNull String, int, boolean);
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED = "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE = "block_payphone_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE = "block_private_number_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = "block_unavailable_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN = "block_unknown_calls_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED = "block_numbers_not_in_contacts_setting";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification";
+ }
+
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus {
+ ctor public BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus(boolean, long);
+ method public boolean getIsSuppressed();
+ method public long getUntilTimestampMillis();
+ }
+
public class CallLog {
method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2c00c99..de6a848 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1232,6 +1232,15 @@
}
@Override
+ public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "scheduleTimeoutServiceForType. token=" + token);
+ }
+ sendMessage(H.TIMEOUT_SERVICE_FOR_TYPE, token, startId, fgsType);
+ }
+
+ @Override
public final void bindApplication(
String processName,
ApplicationInfo appInfo,
@@ -2288,6 +2297,8 @@
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
+ public static final int TIMEOUT_SERVICE_FOR_TYPE = 172;
+
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -2341,6 +2352,7 @@
case DUMP_RESOURCES: return "DUMP_RESOURCES";
case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
case PING: return "PING";
+ case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE";
}
}
return Integer.toString(code);
@@ -2427,6 +2439,14 @@
case PING:
((RemoteCallback) msg.obj).sendResult(null);
break;
+ case TIMEOUT_SERVICE_FOR_TYPE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "serviceTimeoutForType: " + msg.obj);
+ }
+ handleTimeoutServiceForType((IBinder) msg.obj, msg.arg1, msg.arg2);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
case CONFIGURATION_CHANGED:
mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
break;
@@ -5136,6 +5156,26 @@
Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
}
}
+
+ private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ Service s = mServices.get(token);
+ if (s != null) {
+ try {
+ if (localLOGV) Slog.v(TAG, "Timeout service " + s);
+
+ s.callOnTimeLimitExceeded(startId, fgsType);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to call onTimeLimitExceeded on service " + s + ": " + e, e);
+ }
+ Slog.i(TAG, "handleTimeoutServiceForType: exception for " + token, e);
+ }
+ } else {
+ Slog.wtf(TAG, "handleTimeoutServiceForType: token=" + token + " not found.");
+ }
+ }
+
/**
* Resume the activity.
* @param r Target activity record.
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
index a440dbc..44e8a0a 100644
--- a/core/java/android/app/ComponentCaller.java
+++ b/core/java/android/app/ComponentCaller.java
@@ -42,6 +42,9 @@
private final IBinder mActivityToken;
private final IBinder mCallerToken;
+ /**
+ * @hide
+ */
public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) {
mActivityToken = activityToken;
mCallerToken = callerToken;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ceeaf5d..cc0aafd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -942,6 +942,8 @@
/** Returns if the service is a short-service is still "alive" and past the timeout. */
boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
+ /** Returns if the service has a time-limit restricted type and is past the time limit. */
+ boolean hasServiceTimeLimitExceeded(in ComponentName className, in IBinder token);
void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 59e0e99..a04620c 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -178,5 +178,6 @@
in TranslationSpec targetSpec, in List<AutofillId> viewIds,
in UiTranslationSpec uiTranslationSpec);
void scheduleTimeoutService(IBinder token, int startId);
+ void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType);
void schedulePing(in RemoteCallback pong);
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index a155457..d470299 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -20,6 +20,7 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.text.TextUtils.formatSimple;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1161,4 +1162,37 @@
*/
public void onTimeout(int startId) {
}
+
+ /** @hide */
+ public final void callOnTimeLimitExceeded(int startId, int fgsType) {
+ // Note, because all the service callbacks (and other similar callbacks, e.g. activity
+ // callbacks) are delivered using the main handler, it's possible the service is already
+ // stopped when before this method is called, so we do a double check here.
+ if (mToken == null) {
+ Log.w(TAG, "Service already destroyed, skipping onTimeLimitExceeded()");
+ return;
+ }
+ try {
+ if (!mActivityManager.hasServiceTimeLimitExceeded(
+ new ComponentName(this, mClassName), mToken)) {
+ Log.w(TAG, "Service no longer relevant, skipping onTimeLimitExceeded()");
+ return;
+ }
+ } catch (RemoteException ex) {
+ }
+ if (Flags.introduceNewServiceOntimeoutCallback()) {
+ onTimeout(startId, fgsType);
+ }
+ }
+
+ /**
+ * Callback called when a particular foreground service type has timed out.
+ *
+ * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+ * the service started.
+ * @param fgsType the foreground service type which caused the timeout.
+ */
+ @FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK)
+ public void onTimeout(int startId, int fgsType) {
+ }
}
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index c0b299b..ff23f09 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -27,3 +27,10 @@
description: "API to add OnUidImportanceListener with targetted UIDs"
bug: "286258140"
}
+
+flag {
+ namespace: "backstage_power"
+ name: "introduce_new_service_ontimeout_callback"
+ description: "Add a new callback in Service to indicate a FGS has reached its timeout."
+ bug: "317799821"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 67759f4..eb357fe 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,13 @@
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
@@ -207,6 +213,7 @@
final CharSequence mText;
final String mHtmlText;
final Intent mIntent;
+ final PendingIntent mPendingIntent;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Uri mUri;
private TextLinks mTextLinks;
@@ -214,12 +221,91 @@
// if the data is obtained from {@link #copyForTransferWithActivityInfo}
private ActivityInfo mActivityInfo;
+ /**
+ * A builder for a ClipData Item.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @SuppressLint("PackageLayering")
+ public static final class Builder {
+ private CharSequence mText;
+ private String mHtmlText;
+ private Intent mIntent;
+ private PendingIntent mPendingIntent;
+ private Uri mUri;
+
+ /**
+ * Sets the text for the item to be constructed.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Builder setText(@Nullable CharSequence text) {
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Sets the HTML text for the item to be constructed.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Builder setHtmlText(@Nullable String htmlText) {
+ mHtmlText = htmlText;
+ return this;
+ }
+
+ /**
+ * Sets the Intent for the item to be constructed.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from
+ * improperly manipulating the intent to launch another activity as this caller, the
+ * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}).
+ * The system will clean up the PendingIntent when it is no longer used.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ if (pendingIntent != null && !pendingIntent.isImmutable()) {
+ throw new IllegalArgumentException("Expected pending intent to be immutable");
+ }
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the URI for the item to be constructed.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Builder setUri(@Nullable Uri uri) {
+ mUri = uri;
+ return this;
+ }
+
+ /**
+ * Constructs a new Item with the properties set on this builder.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @NonNull
+ public Item build() {
+ return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri);
+ }
+ }
+
/** @hide */
public Item(Item other) {
mText = other.mText;
mHtmlText = other.mHtmlText;
mIntent = other.mIntent;
+ mPendingIntent = other.mPendingIntent;
mUri = other.mUri;
mActivityInfo = other.mActivityInfo;
mTextLinks = other.mTextLinks;
@@ -229,10 +315,7 @@
* Create an Item consisting of a single block of (possibly styled) text.
*/
public Item(CharSequence text) {
- mText = text;
- mHtmlText = null;
- mIntent = null;
- mUri = null;
+ this(text, null, null, null, null);
}
/**
@@ -245,30 +328,21 @@
* </p>
*/
public Item(CharSequence text, String htmlText) {
- mText = text;
- mHtmlText = htmlText;
- mIntent = null;
- mUri = null;
+ this(text, htmlText, null, null, null);
}
/**
* Create an Item consisting of an arbitrary Intent.
*/
public Item(Intent intent) {
- mText = null;
- mHtmlText = null;
- mIntent = intent;
- mUri = null;
+ this(null, null, intent, null, null);
}
/**
* Create an Item consisting of an arbitrary URI.
*/
public Item(Uri uri) {
- mText = null;
- mHtmlText = null;
- mIntent = null;
- mUri = uri;
+ this(null, null, null, null, uri);
}
/**
@@ -276,10 +350,7 @@
* text, Intent, and/or URI.
*/
public Item(CharSequence text, Intent intent, Uri uri) {
- mText = text;
- mHtmlText = null;
- mIntent = intent;
- mUri = uri;
+ this(text, null, intent, null, uri);
}
/**
@@ -289,6 +360,14 @@
* will not be done from HTML formatted text into plain text.
*/
public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+ this(text, htmlText, intent, null, uri);
+ }
+
+ /**
+ * Builder ctor.
+ */
+ private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent,
+ Uri uri) {
if (htmlText != null && text == null) {
throw new IllegalArgumentException(
"Plain text must be supplied if HTML text is supplied");
@@ -296,6 +375,7 @@
mText = text;
mHtmlText = htmlText;
mIntent = intent;
+ mPendingIntent = pendingIntent;
mUri = uri;
}
@@ -321,6 +401,15 @@
}
/**
+ * Returns the pending intent in this Item.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
* Retrieve the raw URI contained in this Item.
*/
public Uri getUri() {
@@ -777,7 +866,7 @@
throw new NullPointerException("item is null");
}
mIcon = null;
- mItems = new ArrayList<Item>();
+ mItems = new ArrayList<>();
mItems.add(item);
mClipDescription.setIsStyledText(isStyledText());
}
@@ -794,7 +883,7 @@
throw new NullPointerException("item is null");
}
mIcon = null;
- mItems = new ArrayList<Item>();
+ mItems = new ArrayList<>();
mItems.add(item);
mClipDescription.setIsStyledText(isStyledText());
}
@@ -826,7 +915,7 @@
public ClipData(ClipData other) {
mClipDescription = other.mClipDescription;
mIcon = other.mIcon;
- mItems = new ArrayList<Item>(other.mItems);
+ mItems = new ArrayList<>(other.mItems);
}
/**
@@ -1042,6 +1131,35 @@
}
/**
+ * Checks if this clip data has a pending intent that is an activity type.
+ * @hide
+ */
+ public boolean hasActivityPendingIntents() {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Cleans up all pending intents in the ClipData.
+ * @hide
+ */
+ public void cleanUpPendingIntents() {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mPendingIntent != null) {
+ item.mPendingIntent.cancel();
+ }
+ }
+ }
+
+ /**
* Prepare this {@link ClipData} to leave an app process.
*
* @hide
@@ -1243,6 +1361,7 @@
TextUtils.writeToParcel(item.mText, dest, flags);
dest.writeString8(item.mHtmlText);
dest.writeTypedObject(item.mIntent, flags);
+ dest.writeTypedObject(item.mPendingIntent, flags);
dest.writeTypedObject(item.mUri, flags);
dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
dest.writeTypedObject(item.mTextLinks, flags);
@@ -1262,10 +1381,11 @@
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
String htmlText = in.readString8();
Intent intent = in.readTypedObject(Intent.CREATOR);
+ PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
Uri uri = in.readTypedObject(Uri.CREATOR);
ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
- Item item = new Item(text, htmlText, intent, uri);
+ Item item = new Item(text, htmlText, intent, pendingIntent, uri);
item.setActivityInfo(info);
item.setTextLinks(textLinks);
mItems.add(item);
@@ -1273,7 +1393,7 @@
}
public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR =
- new Parcelable.Creator<ClipData>() {
+ new Parcelable.Creator<>() {
@Override
public ClipData createFromParcel(Parcel source) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 08871d4..333c363 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -6156,6 +6157,17 @@
public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
/**
+ * A CharSequence of additional text describing the content being shared. This text will be
+ * displayed to the user as a part of the sharesheet when included in an
+ * {@link #ACTION_CHOOSER} {@link Intent}.
+ *
+ * <p>e.g. When sharing a photo, metadata could inform the user that location data is included
+ * in the photo they are sharing.</p>
+ */
+ @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA)
+ public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
+
+ /**
* A {@link IntentSender} to start after instant app installation success.
* @hide
*/
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 2b378b1..5b0cee7 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -142,6 +142,28 @@
* the {@link android.R.attr#foregroundServiceType} attribute.
* Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
* transfer over network between device and cloud.
+ *
+ * <p>This type has time limit of 6 hours starting from Android version
+ * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+ * A foreground service of this type must be stopped within the timeout by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+ * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>If the service isn't stopped within the timeout,
+ * {@link android.app.Service#onTimeout(int, int)} will be called.
+ *
+ * <p>Also note, even though
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC} can be used on
+ * Android versions prior to {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, since
+ * {@link android.app.Service#onTimeout(int, int)} did not exist on such versions, it will
+ * never be called.
+ *
+ * Because of this, developers must make sure to stop the foreground service even if
+ * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+ *
+ * @see android.app.Service#onTimeout(int, int)
*/
@RequiresPermission(
value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
@@ -483,6 +505,27 @@
* Constant corresponding to {@code mediaProcessing} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Media processing use cases such as video or photo editing and processing.
+ *
+ * This type has time limit of 6 hours.
+ * A foreground service of this type must be stopped within the timeout by
+ * {@link android.app.Service#stopSelf()},
+ * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+ * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>If the service isn't stopped within the timeout,
+ * {@link android.app.Service#onTimeout(int, int)} will be called.
+ *
+ * <p>Also note, even though
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING} was added in
+ * Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, it can be also used
+ * on prior android versions (just like other new foreground service types can be used).
+ * However, because {@link android.app.Service#onTimeout(int, int)} did not exist on prior
+ * versions, it will never be called on such versions.
+ * Because of this, developers must make sure to stop the foreground service even if
+ * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+ *
+ * @see android.app.Service#onTimeout(int, int)
*/
@RequiresPermission(
value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
similarity index 96%
rename from services/core/java/com/android/server/devicestate/DeviceState.java
rename to core/java/android/hardware/devicestate/DeviceState.java
index 2ba59b0..5a34905 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.devicestate;
+package android.hardware.devicestate;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -22,7 +22,6 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.hardware.devicestate.DeviceStateManager;
import com.android.internal.util.Preconditions;
@@ -38,9 +37,9 @@
* state of the system. This is useful for variable-state devices, like foldable or rollable
* devices, that can be configured by users into differing hardware states, which each may have a
* different expected use case.
+ * @hide
*
- * @see DeviceStateProvider
- * @see DeviceStateManagerService
+ * @see DeviceStateManager
*/
public final class DeviceState {
/**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ce03798..de32423 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1427,8 +1427,8 @@
public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
/**
- * Specifies if a user is not allowed to run in the background and should be stopped during
- * user switch. The default value is <code>false</code>.
+ * Specifies if a user is not allowed to run in the background and should be stopped and locked
+ * during user switch. The default value is <code>false</code>.
*
* <p>This restriction can be set by device owners and profile owners.
*
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 5d00b29..4075e90 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -15,12 +15,20 @@
*/
package android.provider;
+import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.WorkerThread;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -214,6 +222,333 @@
* <p>TYPE: String</p>
*/
public static final String COLUMN_E164_NUMBER = "e164_number";
+
+ /**
+ * A protected broadcast intent action for letting components with
+ * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
+ * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
+ "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+
+ /**
+ * Preference key of block numbers not in contacts setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
+ "block_numbers_not_in_contacts_setting";
+
+ /**
+ * Preference key of block private number calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
+ "block_private_number_calls_setting";
+
+ /**
+ * Preference key of block payphone calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
+ "block_payphone_calls_setting";
+
+ /**
+ * Preference key of block unknown calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
+ "block_unknown_calls_setting";
+
+ /**
+ * Preference key for whether should show an emergency call notification.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
+ "show_emergency_call_notification";
+
+ /**
+ * Preference key of block unavailable calls setting.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE =
+ "block_unavailable_calls_setting";
+
+ /**
+ * Notifies the provider that emergency services were contacted by the user.
+ * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
+ * of the contents of the provider for a duration defined by
+ * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
+ * the provider unless {@link #endBlockSuppression(Context)} is called.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void notifyEmergencyContact(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", context.getOpPackageName());
+ context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "notifyEmergencyContact: provider not ready.");
+ }
+ }
+
+ /**
+ * Notifies the provider to disable suppressing blocking. If emergency services were not
+ * contacted recently at all, calling this method is a no-op.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void endBlockSuppression(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ String caller = context.getOpPackageName();
+ Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller);
+ context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_END_BLOCK_SUPPRESSION, null, null);
+ }
+
+ /**
+ * Returns {@code true} if {@code phoneNumber} is blocked taking
+ * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
+ * have not been contacted recently and enhanced call blocking not been enabled, this
+ * method is equivalent to {@link #isBlocked(Context, String)}.
+ *
+ * @param context the context of the caller.
+ * @param phoneNumber the number to check.
+ * @param numberPresentation the presentation code associated with the call.
+ * @param isNumberInContacts indicates if the provided number exists as a contact.
+ * @return result code indicating if the number should be blocked, and if so why.
+ * Valid values are: {@link #STATUS_NOT_BLOCKED}, {@link #STATUS_BLOCKED_IN_LIST},
+ * {@link #STATUS_BLOCKED_NOT_IN_CONTACTS}, {@link #STATUS_BLOCKED_PAYPHONE},
+ * {@link #STATUS_BLOCKED_RESTRICTED}, {@link #STATUS_BLOCKED_UNKNOWN_NUMBER}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static int shouldSystemBlockNumber(@NonNull Context context,
+ @NonNull String phoneNumber, @TelecomManager.Presentation int numberPresentation,
+ boolean isNumberInContacts) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ String caller = context.getOpPackageName();
+ Bundle extras = new Bundle();
+ extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, numberPresentation);
+ extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, isNumberInContacts);
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
+ int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) :
+ BlockedNumberContract.STATUS_NOT_BLOCKED;
+ Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s",
+ Log.piiHandle(phoneNumber), caller,
+ SystemContract.blockStatusToString(blockResult));
+ return blockResult;
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "shouldSystemBlockNumber: provider not ready.");
+ return BlockedNumberContract.STATUS_NOT_BLOCKED;
+ }
+ }
+
+ /**
+ * Returns the current status of block suppression.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static @NonNull BlockSuppressionStatus getBlockSuppressionStatus(
+ @NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ final Bundle res = context.getContentResolver().call(
+ AUTHORITY_URI, SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
+ BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus(
+ res.getBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, false),
+ res.getLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
+ Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s",
+ context.getOpPackageName(), blockSuppressionStatus);
+ return blockSuppressionStatus;
+ }
+
+ /**
+ * Check whether should show the emergency call notification.
+ *
+ * @param context the context of the caller.
+ * @return {@code true} if should show emergency call notification. {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static boolean shouldShowEmergencyCallNotification(@NonNull Context context) {
+ verifyBlockedNumbersPermission(context);
+ try {
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
+ return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the enhanced block setting is enabled.
+ *
+ * @param context the context of the caller.
+ * @param key the key of the setting to check, can be
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+ * @return {@code true} if the setting is enabled. {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static boolean getBlockedNumberSetting(
+ @NonNull Context context, @NonNull String key) {
+ verifyBlockedNumbersPermission(context);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+ try {
+ final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
+ return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
+ } catch (NullPointerException | IllegalArgumentException ex) {
+ // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+ // either of these happen.
+ Log.w(null, "getEnhancedBlockSetting: provider not ready.");
+ return false;
+ }
+ }
+
+ /**
+ * Set the enhanced block setting enabled status.
+ *
+ * @param context the context of the caller.
+ * @param key the key of the setting to set, can be
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+ * {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+ * @param value the enabled statue of the setting to set.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_BLOCKED_NUMBERS,
+ android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+ })
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static void setBlockedNumberSetting(@NonNull Context context,
+ @NonNull String key, boolean value) {
+ verifyBlockedNumbersPermission(context);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+ extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
+ context.getContentResolver().call(AUTHORITY_URI,
+ SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING, null, extras);
+ }
+
+ /**
+ * Represents the current status of
+ * {@link #shouldSystemBlockNumber(Context, String, int, boolean)}. If emergency services
+ * have been contacted recently, {@link #mIsSuppressed} is {@code true}, and blocking
+ * is disabled until the timestamp {@link #mUntilTimestampMillis}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final class BlockSuppressionStatus {
+ private boolean mIsSuppressed;
+
+ /**
+ * Timestamp in milliseconds from epoch.
+ */
+ private long mUntilTimestampMillis;
+
+ public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
+ this.mIsSuppressed = isSuppressed;
+ this.mUntilTimestampMillis = untilTimestampMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "[BlockSuppressionStatus; isSuppressed=" + mIsSuppressed + ", until="
+ + mUntilTimestampMillis + "]";
+ }
+
+ public boolean getIsSuppressed() {
+ return mIsSuppressed;
+ }
+
+ public long getUntilTimestampMillis() {
+ return mUntilTimestampMillis;
+ }
+ }
+
+ /**
+ * Verifies that the caller holds both the
+ * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} permission and the
+ * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} permission.
+ *
+ * @param context
+ * @throws SecurityException if the caller is missing the necessary permissions
+ */
+ private static void verifyBlockedNumbersPermission(Context context) {
+ context.enforceCallingOrSelfPermission(Manifest.permission.READ_BLOCKED_NUMBERS,
+ "Caller does not have the android.permission.READ_BLOCKED_NUMBERS permission");
+ context.enforceCallingOrSelfPermission(Manifest.permission.WRITE_BLOCKED_NUMBERS,
+ "Caller does not have the android.permission.WRITE_BLOCKED_NUMBERS permission");
+ }
}
/** @hide */
@@ -558,7 +893,7 @@
* {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
- * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+ * {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
* @return {@code true} if the setting is enabled. {@code false} otherwise.
*/
public static boolean getEnhancedBlockSetting(Context context, String key) {
@@ -586,7 +921,7 @@
* {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
* {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
- * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+ * {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
* @param value the enabled statue of the setting to set.
*/
public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 6410609..2028c40 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -711,18 +711,21 @@
public void draw(Canvas c, Path highlight, Paint highlightpaint,
int cursorOffset) {
if (mDirect != null && highlight == null) {
+ float leftShift = 0;
if (getUseBoundsForWidth()) {
- c.save();
RectF drawingRect = computeDrawingBoundingBox();
if (drawingRect.left < 0) {
- c.translate(-drawingRect.left, 0);
+ leftShift = -drawingRect.left;
+ c.translate(leftShift, 0);
}
}
c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
- if (getUseBoundsForWidth()) {
- c.restore();
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ c.translate(-leftShift, 0);
}
} else {
super.draw(c, highlight, highlightpaint, cursorOffset);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8ddb42d..e5d199a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -464,11 +464,12 @@
@Nullable Path selectionPath,
@Nullable Paint selectionPaint,
int cursorOffsetVertical) {
+ float leftShift = 0;
if (mUseBoundsForWidth) {
- canvas.save();
RectF drawingRect = computeDrawingBoundingBox();
if (drawingRect.left < 0) {
- canvas.translate(-drawingRect.left, 0);
+ leftShift = -drawingRect.left;
+ canvas.translate(leftShift, 0);
}
}
final long lineRange = getLineRangeForDraw(canvas);
@@ -479,8 +480,10 @@
drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
cursorOffsetVertical, firstLine, lastLine);
drawText(canvas, firstLine, lastLine);
- if (mUseBoundsForWidth) {
- canvas.restore();
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ canvas.translate(-leftShift, 0);
}
}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 0bce26e..c1a61a7 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -105,6 +105,5 @@
return res;
}
- // private static native void nativeFlush(long nativeDataSourcePointer);
private static native void nativeFlush(TracingContext thiz, long ctxPointer);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b5b81d1..29cc859 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,7 @@
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
import android.window.InputTransferToken;
import android.window.ScreenCapture;
import android.window.TrustedPresentationThresholds;
@@ -1091,4 +1092,10 @@
@EnforcePermission("DETECT_SCREEN_RECORDING")
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
+
+ /**
+ * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+ * (ie. not handled by any window which can handle the drag).
+ */
+ void setUnhandledDragListener(IUnhandledDragListener listener);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c22986b..3dfd68a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -43,6 +43,7 @@
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
import static java.lang.Math.max;
@@ -68,6 +69,7 @@
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UiThread;
+import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.ClipData;
@@ -5329,6 +5331,34 @@
public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11;
/**
+ * Flag indicating that a drag can cross window boundaries (within the same application). When
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+ * with this flag set, only visible windows belonging to the same application (ie. share the
+ * same UID) with targetSdkVersion >= {@link android.os.Build.VERSION_CODES#N API 24} will be
+ * able to participate in the drag operation and receive the dragged content.
+ *
+ * If both DRAG_FLAG_GLOBAL_SAME_APPLICATION and DRAG_FLAG_GLOBAL are set, then
+ * DRAG_FLAG_GLOBAL_SAME_APPLICATION takes precedence and the drag will only go to visible
+ * windows from the same application.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 1 << 12;
+
+ /**
+ * Flag indicating that an unhandled drag should be delegated to the system to be started if no
+ * visible window wishes to handle the drop. When using this flag, the caller must provide
+ * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched
+ * (not a broadcast, service, etc). See
+ * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}.
+ *
+ * The system can decide to launch the intent or not based on factors like the current screen
+ * size or windowing mode. If the system does not launch the intent, it will be canceled via the
+ * normal drag and drop flow.
+ */
+ @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+ public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13;
+
+ /**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
*/
private float mVerticalScrollFactor;
@@ -22269,6 +22299,9 @@
* Retrieve a unique token identifying the window this view is attached to.
* @return Return the window's token for use in
* {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+ * This token maybe null if this view is not attached to a window.
+ * @see #isAttachedToWindow() for current window attach state
+ * @see OnAttachStateChangeListener to listen to window attach/detach state changes
*/
public IBinder getWindowToken() {
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
@@ -28496,9 +28529,29 @@
Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
return false;
}
+ if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) {
+ Log.w(VIEW_LOG_TAG, "startDragAndDrop called with both DRAG_FLAG_GLOBAL "
+ + "and DRAG_FLAG_GLOBAL_SAME_APPLICATION, the drag will default to "
+ + "DRAG_FLAG_GLOBAL_SAME_APPLICATION");
+ flags &= ~DRAG_FLAG_GLOBAL;
+ }
if (data != null) {
- data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
+ data.prepareToLeaveProcess(
+ (flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
+ if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) {
+ if (!data.hasActivityPendingIntents()) {
+ // Reset the flag if there is no launchable activity intent
+ flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG;
+ Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+ + "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+ + "contains non-activity PendingIntents");
+ }
+ }
+ } else {
+ data.prepareToLeaveProcess((flags & DRAG_FLAG_GLOBAL) != 0);
+ }
}
Rect bounds = new Rect();
@@ -28524,6 +28577,7 @@
if (token != null) {
root.setLocalDragState(myLocalState);
mAttachInfo.mDragToken = token;
+ mAttachInfo.mDragData = data;
mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
setAccessibilityDragStarted(true);
}
@@ -28601,8 +28655,12 @@
if (mAttachInfo.mDragSurface != null) {
mAttachInfo.mDragSurface.release();
}
+ if (mAttachInfo.mDragData != null) {
+ mAttachInfo.mDragData.cleanUpPendingIntents();
+ }
mAttachInfo.mDragSurface = surface;
mAttachInfo.mDragToken = token;
+ mAttachInfo.mDragData = data;
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
if (a11yEnabled) {
@@ -31516,11 +31574,15 @@
IBinder mDragToken;
/**
+ * Used to track the data of the current drag operation for cleanup later.
+ */
+ ClipData mDragData;
+
+ /**
* The drag shadow surface for the current drag operation.
*/
public Surface mDragSurface;
-
/**
* The view that currently has a tooltip displayed.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 07c9795..28a7334 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8599,6 +8599,10 @@
mAttachInfo.mDragSurface.release();
mAttachInfo.mDragSurface = null;
}
+ if (mAttachInfo.mDragData != null) {
+ mAttachInfo.mDragData.cleanUpPendingIntents();
+ mAttachInfo.mDragData = null;
+ }
}
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index ae00b70..2fb5213 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -520,11 +520,16 @@
public void registerTrustedPresentationListener(@NonNull IBinder window,
@NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
@NonNull Consumer<Boolean> listener) {
+ Objects.requireNonNull(window, "window must not be null");
+ Objects.requireNonNull(thresholds, "thresholds must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(listener, "listener must not be null");
mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
}
@Override
public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+ Objects.requireNonNull(listener, "listener must not be null");
mGlobal.unregisterTrustedPresentationListener(listener);
}
diff --git a/core/java/android/window/IUnhandledDragCallback.aidl b/core/java/android/window/IUnhandledDragCallback.aidl
new file mode 100644
index 0000000..7806b1f
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.window;
+
+import android.view.DragEvent;
+
+/**
+ * A callback for notifying the system when the unhandled drop is complete.
+ * {@hide}
+ */
+oneway interface IUnhandledDragCallback {
+ /**
+ * Called when the IUnhandledDropListener has fully handled the drop, and the drag can be
+ * cleaned up. If handled is `true`, then cleanup of the drag and drag surface will be
+ * immediate, otherwise, the system will treat the drag as a cancel back to the start of the
+ * drag.
+ */
+ void notifyUnhandledDropComplete(boolean handled);
+}
diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IUnhandledDragListener.aidl
new file mode 100644
index 0000000..52e9895
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.window;
+
+import android.view.DragEvent;
+import android.window.IUnhandledDragCallback;
+
+/**
+ * An interface to a handler for global drags that are not consumed (ie. not handled by any window).
+ * {@hide}
+ */
+oneway interface IUnhandledDragListener {
+ /**
+ * Called when the user finishes the drag gesture but no windows have reported handling the
+ * drop. The DragEvent is populated with the drag surface for the listener to animate. The
+ * listener *MUST* call the provided callback exactly once when it has finished handling the
+ * drop. If the listener calls the callback with `true` then it is responsible for removing
+ * and releasing the drag surface passed through the DragEvent.
+ */
+ void onUnhandledDrop(in DragEvent event, in IUnhandledDragCallback callback);
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index e9a8d4b..1f4abc1 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -45,6 +45,7 @@
TimeoutKind.APP_REGISTERED,
TimeoutKind.SHORT_FGS_TIMEOUT,
TimeoutKind.JOB_SERVICE,
+ TimeoutKind.FGS_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
@@ -59,6 +60,7 @@
int SHORT_FGS_TIMEOUT = 8;
int JOB_SERVICE = 9;
int APP_START = 10;
+ int FGS_TIMEOUT = 11;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -186,6 +188,12 @@
return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
}
+ /** Record for a "foreground service" timeout. */
+ @NonNull
+ public static TimeoutRecord forFgsTimeout(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason);
+ }
+
/** Record for a job related timeout. */
@NonNull
public static TimeoutRecord forJobService(String reason) {
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index f62ff38..e11067d 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -22,6 +22,7 @@
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
@@ -548,6 +549,8 @@
return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
case TimeoutKind.JOB_SERVICE:
return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
+ case TimeoutKind.FGS_TIMEOUT:
+ return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
default:
return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..a2ce212 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3829,6 +3829,7 @@
@hide This is not a third-party API (intended for OEMs and system apps). -->
<permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
@@ -7033,12 +7034,16 @@
<!-- Allows the holder to read blocked numbers. See
{@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
android:protectionLevel="signature" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
android:protectionLevel="signature" />
diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp
index 7748de5..60848b3 100644
--- a/core/tests/devicestatetests/Android.bp
+++ b/core/tests/devicestatetests/Android.bp
@@ -29,6 +29,8 @@
"androidx.test.rules",
"frameworks-base-testutils",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
+ "testng",
],
libs: ["android.test.runner"],
platform_apis: true,
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
rename to core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index d54524e..396d403 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.devicestate;
+package android.hardware.devicestate;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -25,18 +25,17 @@
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
/**
- * Unit tests for {@link DeviceState}.
+ * Unit tests for {@link android.hardware.devicestate.DeviceState}.
* <p/>
* Run with <code>atest DeviceStateTest</code>.
*/
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
public final class DeviceStateTest {
@Test
public void testConstruct() {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index d8713f7a..fdb5208 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,9 @@
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
<!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
<permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+ <!-- Permission required for CTS test BlockedNumberContractTest -->
+ <permission name="android.permission.WRITE_BLOCKED_NUMBERS" />
+ <permission name="android.permission.READ_BLOCKED_NUMBERS" />
<!-- Permission required for CTS test - PackageManagerTest -->
<permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
</privapp-permissions>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ead5ad2..bd9d89c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -64,6 +64,7 @@
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.draganddrop.UnhandledDragController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -558,6 +559,14 @@
@WMSingleton
@Provides
+ static UnhandledDragController provideUnhandledDragController(
+ IWindowManager wmService,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new UnhandledDragController(wmService, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 605600f..ba08b09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -143,7 +143,7 @@
final SurfaceControl leash = change.getLeash();
final Rect startBounds = change.getStartAbsBounds();
- startT.setPosition(leash, startBounds.left, startBounds.right)
+ startT.setPosition(leash, startBounds.left, startBounds.top)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash);
mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
@@ -154,7 +154,7 @@
SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
final Rect animationValue = (Rect) animator.getAnimatedValue();
- t.setPosition(leash, animationValue.left, animationValue.right)
+ t.setPosition(leash, animationValue.left, animationValue.top)
.setWindowCrop(leash, animationValue.width(), animationValue.height())
.show(leash);
mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
new file mode 100644
index 0000000..ccf48d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.util.Log
+import android.view.DragEvent
+import android.view.IWindowManager
+import android.window.IUnhandledDragCallback
+import android.window.IUnhandledDragListener
+import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import java.util.function.Consumer
+
+/**
+ * Manages the listener and callbacks for unhandled global drags.
+ */
+class UnhandledDragController(
+ val wmService: IWindowManager,
+ mainExecutor: ShellExecutor
+) {
+ private var callback: UnhandledDragAndDropCallback? = null
+
+ private val unhandledDragListener: IUnhandledDragListener =
+ object : IUnhandledDragListener.Stub() {
+ override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
+ mainExecutor.execute() {
+ this@UnhandledDragController.onUnhandledDrop(event, callback)
+ }
+ }
+ }
+
+ /**
+ * Listener called when an unhandled drag is started.
+ */
+ interface UnhandledDragAndDropCallback {
+ /**
+ * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
+ * dropped on a window that does not want to handle it).
+ *
+ * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
+ * also responsible for releasing up the drag surface provided via the drag event.
+ */
+ fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
+ }
+
+ /**
+ * Sets a listener for callbacks when an unhandled drag happens.
+ */
+ fun setListener(listener: UnhandledDragAndDropCallback?) {
+ val updateWm = (callback == null && listener != null)
+ || (callback != null && listener == null)
+ callback = listener
+ if (updateWm) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "%s unhandled drag listener",
+ if (callback != null) "Registering" else "Unregistering")
+ wmService.setUnhandledDragListener(
+ if (callback != null) unhandledDragListener else null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to set unhandled drag listener")
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onUnhandledDrop: %s", dragEvent)
+ if (callback == null) {
+ wmCallback.notifyUnhandledDropComplete(false)
+ }
+
+ callback?.onUnhandledDrop(dragEvent) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Notifying onUnhandledDrop complete: %b", it)
+ wmCallback.notifyUnhandledDropComplete(it)
+ }
+ }
+
+ companion object {
+ private val TAG = UnhandledDragController::class.java.simpleName
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
new file mode 100644
index 0000000..522f052
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.view.DragEvent
+import android.view.DragEvent.ACTION_DROP
+import android.view.IWindowManager
+import android.view.SurfaceControl
+import android.window.IUnhandledDragCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback
+import java.util.function.Consumer
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for the unhandled drag controller.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UnhandledDragControllerTest : ShellTestCase() {
+ @Mock
+ private lateinit var mIWindowManager: IWindowManager
+
+ @Mock
+ private lateinit var mMainExecutor: ShellExecutor
+
+ private lateinit var mController: UnhandledDragController
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mController = UnhandledDragController(mIWindowManager, mMainExecutor)
+ }
+
+ @Test
+ fun setListener_registersUnregistersWithWM() {
+ mController.setListener(object : UnhandledDragAndDropCallback {})
+ mController.setListener(object : UnhandledDragAndDropCallback {})
+ mController.setListener(object : UnhandledDragAndDropCallback {})
+ verify(mIWindowManager, Mockito.times(1))
+ .setUnhandledDragListener(ArgumentMatchers.any())
+
+ reset(mIWindowManager)
+ mController.setListener(null)
+ mController.setListener(null)
+ mController.setListener(null)
+ verify(mIWindowManager, Mockito.times(1))
+ .setUnhandledDragListener(ArgumentMatchers.isNull())
+ }
+
+ @Test
+ fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
+ // Simulate an unhandled drop
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ null, null, false)
+ val wmCallback = mock(IUnhandledDragCallback::class.java)
+ mController.onUnhandledDrop(dropEvent, wmCallback)
+
+ verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
+ }
+
+ @Test
+ fun onUnhandledDrop_withListener_expectNotifyHandled() {
+ val lastDragEvent = arrayOfNulls<DragEvent>(1)
+
+ // Set a listener to listen for unhandled drops
+ mController.setListener(object : UnhandledDragAndDropCallback {
+ override fun onUnhandledDrop(dragEvent: DragEvent,
+ onFinishedCallback: Consumer<Boolean>) {
+ lastDragEvent[0] = dragEvent
+ onFinishedCallback.accept(true)
+ dragEvent.dragSurface.release()
+ }
+ })
+
+ // Simulate an unhandled drop
+ val dragSurface = mock(SurfaceControl::class.java)
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ dragSurface, null, false)
+ val wmCallback = mock(IUnhandledDragCallback::class.java)
+ mController.onUnhandledDrop(dropEvent, wmCallback)
+
+ verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
+ verify(dragSurface).release()
+ assertEquals(lastDragEvent.get(0), dropEvent)
+ }
+}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index eba26d4..f332f81 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -873,6 +873,9 @@
/**
* Called when the corresponding TV input selected to a track.
+ *
+ * If the track is deselected and no track is currently selected,
+ * trackId is an empty string.
*/
public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
}
@@ -1845,6 +1848,10 @@
if (DEBUG) {
Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")");
}
+ // TvInputService accepts a Null String, but onTrackSelected expects NonNull.
+ if (trackId == null) {
+ trackId = "";
+ }
onTrackSelected(type, trackId);
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 926e181..c086baa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -862,6 +862,8 @@
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+ <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..f397627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -444,7 +444,8 @@
UserHandle.of(userId))) {
boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts);
- } catch (RemoteException | InterruptedException | AssertionError e) {
+ } catch (RemoteException | InterruptedException | AssertionError
+ | IllegalStateException e) {
Log.i(TAG, "failed to get CA certs", e);
idWithCert = new Pair<Integer, Boolean>(userId, null);
} finally {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cd45b03..b8f6b3f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -480,9 +480,14 @@
/**
* The available ANR timers.
*/
+ // ActivityManagerConstants.SERVICE_TIMEOUT/ActivityManagerConstants.SERVICE_BACKGROUND_TIMEOUT
private final ProcessAnrTimer mActiveServiceAnrTimer;
+ // see ServiceRecord$ShortFgsInfo#getAnrTime()
private final ServiceAnrTimer mShortFGSAnrTimer;
+ // ActivityManagerConstants.DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS
private final ServiceAnrTimer mServiceFGAnrTimer;
+ // see ServiceRecord#getEarliestStopTypeAndTime()
+ private final ServiceAnrTimer mFGSAnrTimer;
// allowlisted packageName.
ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -744,10 +749,13 @@
"SERVICE_TIMEOUT");
this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
- "FGS_TIMEOUT");
+ "SHORT_FGS_TIMEOUT");
this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
"SERVICE_FOREGROUND_TIMEOUT");
+ this.mFGSAnrTimer = new ServiceAnrTimer(service,
+ ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+ "FGS_TIMEOUT");
}
void systemServicesReady() {
@@ -1570,6 +1578,7 @@
}
maybeStopShortFgsTimeoutLocked(service);
+ maybeStopFgsTimeoutLocked(service);
final int uid = service.appInfo.uid;
final String packageName = service.name.getPackageName();
@@ -1758,6 +1767,7 @@
}
maybeStopShortFgsTimeoutLocked(r);
+ maybeStopFgsTimeoutLocked(r);
final int uid = r.appInfo.uid;
final String packageName = r.name.getPackageName();
@@ -2243,6 +2253,8 @@
// Whether to extend the SHORT_SERVICE time out.
boolean extendShortServiceTimeout = false;
+ // Whether to extend the timeout for a time-limited FGS type.
+ boolean extendFgsTimeout = false;
// Whether setFgsRestrictionLocked() is called in here. Only used for logging.
boolean fgsRestrictionRecalculated = false;
@@ -2287,6 +2299,19 @@
final boolean isOldTypeShortFgsAndTimedOut =
r.shouldTriggerShortFgsTimeout(nowUptime);
+ // Calling startForeground on a FGS type which has a time limit will only be
+ // allowed if the app is in a state where it can normally start another FGS.
+ // The timeout will behave as follows:
+ // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE>
+ // - If the start succeeds, the timeout is reset.
+ // B) <TIME_LIMITED_TYPE> -> non-time-limited type
+ // - If the start succeeds, the timeout will stop.
+ // C) non-time-limited type -> <TIME_LIMITED_TYPE>
+ // - If the start succeeds, the timeout will start.
+ final boolean isOldTypeTimeLimited = r.isFgsTimeLimited();
+ final boolean isNewTypeTimeLimited =
+ r.canFgsTypeTimeOut(foregroundServiceType);
+
// If true, we skip the BFSL check.
boolean bypassBfslCheck = false;
@@ -2355,6 +2380,35 @@
// "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
}
}
+ } else if (r.isForeground && isOldTypeTimeLimited) {
+
+ // See if the app could start an FGS or not.
+ r.clearFgsAllowStart();
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ BackgroundStartPrivileges.NONE, false /* isBindService */);
+ fgsRestrictionRecalculated = true;
+
+ final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService
+ || r.isFgsAllowedStart();
+
+ if (fgsStartAllowed) {
+ if (isNewTypeTimeLimited) {
+ // Note: in the future, we may want to look into metrics to see if
+ // apps are constantly switching between a time-limited type and a
+ // non-time-limited type or constantly calling startForeground()
+ // opportunistically on the same type to gain runtime and apply the
+ // stricter timeout. For now, always extend the timeout if the app
+ // is in a state where it's allowed to start a FGS.
+ extendFgsTimeout = true;
+ } else {
+ // FGS type is changing from a time-restricted type to one without
+ // a time limit so proceed as normal.
+ // The timeout will stop later, in maybeUpdateFgsTrackingLocked().
+ }
+ } else {
+ // This case will be handled in the BFSL check below.
+ }
} else if (r.mStartForegroundCount == 0) {
/*
If the service was started with startService(), not
@@ -2596,6 +2650,7 @@
maybeUpdateShortFgsTrackingLocked(r,
extendShortServiceTimeout);
+ maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2631,6 +2686,7 @@
}
maybeStopShortFgsTimeoutLocked(r);
+ maybeStopFgsTimeoutLocked(r);
// Adjust notification handling before setting isForeground to false, because
// that state is relevant to the notification policy side.
@@ -3608,6 +3664,116 @@
}
}
+ void onFgsTimeout(ServiceRecord sr) {
+ synchronized (mAm) {
+ final long nowUptime = SystemClock.uptimeMillis();
+ final int fgsType = sr.getTimedOutFgsType(nowUptime);
+ if (fgsType == -1) {
+ mFGSAnrTimer.discard(sr);
+ return;
+ }
+ Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ + ") timed out: " + sr);
+ mFGSAnrTimer.accept(sr);
+ traceInstant("FGS timed out: ", sr);
+
+ logFGSStateChangeLocked(sr,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+ nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
+ FGS_STOP_REASON_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+ false /* fgsRestrictionRecalculated */
+ );
+ try {
+ sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
+ } catch (RemoteException e) {
+ Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
+ }
+
+ // ANR the service after giving the service some time to clean up.
+ // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that
+ // is not "now". Compute the time from "now" when starting the anr timer.
+ final long anrTime = sr.getEarliestStopTypeAndTime().second
+ + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis();
+ mFGSAnrTimer.start(sr, anrTime);
+ }
+ }
+
+ private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) {
+ if (!sr.isFgsTimeLimited()) {
+ // Reset timers if they exist.
+ sr.setIsFgsTimeLimited(false);
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ return;
+ }
+
+ if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) {
+ traceInstant("FGS start: ", sr);
+ sr.setIsFgsTimeLimited(true);
+
+ // We'll restart the timeout.
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second);
+ }
+ }
+
+ private void maybeStopFgsTimeoutLocked(ServiceRecord sr) {
+ sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state.
+ if (!sr.isFgsTimeLimited()) {
+ return; // if none of the types are time-limited, return.
+ }
+ Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ }
+
+ boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) {
+ final int userId = UserHandle.getCallingUserId();
+ final long ident = mAm.mInjector.clearCallingIdentity();
+ try {
+ ServiceRecord sr = findServiceLocked(className, token, userId);
+ if (sr == null) {
+ return false;
+ }
+ final long nowUptime = SystemClock.uptimeMillis();
+ return sr.getTimedOutFgsType(nowUptime) != -1;
+ } finally {
+ mAm.mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ void onFgsAnrTimeout(ServiceRecord sr) {
+ final long nowUptime = SystemClock.uptimeMillis();
+ final int fgsType = sr.getTimedOutFgsType(nowUptime);
+ if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) {
+ return; // no timed out FGS type was found
+ }
+ final String reason = "A foreground service of type "
+ + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ + " did not stop within a timeout: " + sr.getComponentName();
+
+ final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+
+ tr.mLatencyTracker.waitingOnAMSLockStarted();
+ synchronized (mAm) {
+ tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+ Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+ traceInstant("FGS ANR: ", sr);
+ mAm.appNotResponding(sr.app, tr);
+
+ // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+ // dialog really doesn't remember the "cause" (especially if there have been multiple
+ // ANRs), so it's not doable.
+ }
+ }
+
private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
psr.mAllowlistManager = false;
for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -3621,6 +3787,7 @@
private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
maybeStopShortFgsTimeoutLocked(service);
+ maybeStopFgsTimeoutLocked(service);
final ProcessServiceRecord psr = service.app.mServices;
psr.stopService(service);
psr.updateBoundClientUids();
@@ -5893,6 +6060,7 @@
Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
maybeStopShortFgsTimeoutLocked(r);
}
+ maybeStopFgsTimeoutLocked(r);
// Report to all of the connections that the service is no longer
// available.
@@ -6015,6 +6183,7 @@
final boolean exitingFg = r.isForeground;
if (exitingFg) {
maybeStopShortFgsTimeoutLocked(r);
+ maybeStopFgsTimeoutLocked(r);
decActiveForegroundAppLocked(smap, r);
synchronized (mAm.mProcessStats.mLock) {
ServiceState stracker = r.getTracker();
@@ -8643,7 +8812,7 @@
event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
} else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT;
- }else {
+ } else {
// Unknown event.
return;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 72e62c3..d97731c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1052,6 +1052,27 @@
public volatile long mShortFgsProcStateExtraWaitDuration =
DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
+ /** Timeout for a mediaProcessing FGS, in milliseconds. */
+ private static final String KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION =
+ "media_processing_fgs_timeout_duration";
+
+ /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+ static final long DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+ /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+ public volatile long mMediaProcessingFgsTimeoutDuration =
+ DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION;
+
+ /** Timeout for a dataSync FGS, in milliseconds. */
+ private static final String KEY_DATA_SYNC_FGS_TIMEOUT_DURATION =
+ "data_sync_fgs_timeout_duration";
+
+ /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+ static final long DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+ /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+ public volatile long mDataSyncFgsTimeoutDuration = DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION;
+
/**
* If enabled, when starting an application, the system will wait for a
* {@link ActivityManagerService#finishAttachApplication} from the app before scheduling
@@ -1082,6 +1103,20 @@
public volatile long mShortFgsAnrExtraWaitDuration =
DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
+ /**
+ * If a service of a timeout-enforced type doesn't finish within this duration after its
+ * timeout, then we'll declare an ANR.
+ * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
+ * the app will be ANR'ed 1 hour and 10 seconds after it started.
+ */
+ private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+
+ /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+ static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+
+ /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+ public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+
/** @see #KEY_USE_TIERED_CACHED_ADJ */
public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
@@ -1264,9 +1299,18 @@
case KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION:
updateShortFgsProcStateExtraWaitDuration();
break;
+ case KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION:
+ updateMediaProcessingFgsTimeoutDuration();
+ break;
+ case KEY_DATA_SYNC_FGS_TIMEOUT_DURATION:
+ updateDataSyncFgsTimeoutDuration();
+ break;
case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
updateShortFgsAnrExtraWaitDuration();
break;
+ case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
+ updateFgsAnrExtraWaitDuration();
+ break;
case KEY_PROACTIVE_KILLS_ENABLED:
updateProactiveKillsEnabled();
break;
@@ -2064,6 +2108,27 @@
DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
}
+ private void updateMediaProcessingFgsTimeoutDuration() {
+ mMediaProcessingFgsTimeoutDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION,
+ DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+ }
+
+ private void updateDataSyncFgsTimeoutDuration() {
+ mDataSyncFgsTimeoutDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DATA_SYNC_FGS_TIMEOUT_DURATION,
+ DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
+ }
+
+ private void updateFgsAnrExtraWaitDuration() {
+ mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FGS_ANR_EXTRA_WAIT_DURATION,
+ DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+ }
+
private void updateEnableWaitForFinishAttachApplication() {
mEnableWaitForFinishAttachApplication = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2295,6 +2360,13 @@
pw.print(" "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
+ pw.print(" "); pw.print(KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+ pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
+ pw.print(" "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
+ pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
+ pw.print(" "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
+ pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+
pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2750344..bfdcb95 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1702,6 +1702,8 @@
static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
+ static final int SERVICE_FGS_TIMEOUT_MSG = 84;
+ static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2064,6 +2066,12 @@
case BIND_APPLICATION_TIMEOUT_HARD_MSG: {
handleBindApplicationTimeoutHard((ProcessRecord) msg.obj);
} break;
+ case SERVICE_FGS_TIMEOUT_MSG: {
+ mServices.onFgsTimeout((ServiceRecord) msg.obj);
+ } break;
+ case SERVICE_FGS_ANR_TIMEOUT_MSG: {
+ mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+ } break;
}
}
}
@@ -13794,6 +13802,13 @@
}
@Override
+ public boolean hasServiceTimeLimitExceeded(ComponentName className, IBinder token) {
+ synchronized (this) {
+ return mServices.hasServiceTimedOutLocked(className, token);
+ }
+ }
+
+ @Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2771572..3c8d7fc 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -56,6 +56,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -236,6 +237,8 @@
boolean mFgsNotificationShown;
// Whether FGS package has permissions to show notifications.
boolean mFgsHasNotificationPermission;
+ // Whether the FGS contains a type that is time limited.
+ private boolean mFgsIsTimeLimited;
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@@ -915,6 +918,7 @@
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
pw.print(" foregroundId="); pw.print(foregroundId);
pw.printf(" types=%08X", foregroundServiceType);
+ pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
if (isShortFgs() && mShortFgsInfo != null) {
@@ -1789,6 +1793,83 @@
+ " " + (mShortFgsInfo == null ? "" : mShortFgsInfo.getDescription());
}
+ /**
+ * @return true if one of the types of this FGS has a time limit.
+ */
+ public boolean isFgsTimeLimited() {
+ return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType);
+ }
+
+ /**
+ * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}).
+ */
+ public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) {
+ this.mFgsIsTimeLimited = fgsIsTimeLimited;
+ }
+
+ /**
+ * @return whether {@link #mFgsIsTimeLimited} was previously set or not.
+ */
+ public boolean wasFgsPreviouslyTimeLimited() {
+ return mFgsIsTimeLimited;
+ }
+
+ /**
+ * @return the FGS type if the service has reached its time limit, otherwise -1.
+ */
+ public int getTimedOutFgsType(long nowUptime) {
+ if (!isAppAlive() || !isFgsTimeLimited()) {
+ return -1;
+ }
+
+ final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime();
+ if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) {
+ return fgsTypeAndStopTime.first;
+ }
+ return -1; // no fgs type exceeded time limit
+ }
+
+ /**
+ * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS
+ * should be stopped (fgs start time + time limit for most restrictive type)
+ */
+ Pair<Integer, Long> getEarliestStopTypeAndTime() {
+ int fgsType = -1;
+ long timeout = 0;
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
+ timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
+ }
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+ // update the timeout and type if this type has a more restrictive time limit
+ if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+ timeout = ams.mConstants.mDataSyncFgsTimeoutDuration;
+ }
+ }
+ // Add the logic for time limits introduced in the future for other fgs types here.
+ return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout));
+ }
+
+ /**
+ * Check if the given types contain a type which is time restricted.
+ */
+ boolean canFgsTypeTimeOut(int fgsType) {
+ // The below conditionals are not simplified on purpose to help with readability.
+ if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+ return true;
+ }
+ if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+ return true;
+ }
+ // Additional types which have time limits should be added here in the future.
+ return false;
+ }
+
private boolean isAppAlive() {
if (app == null) {
return false;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 96c6be8..55ac4cf 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1350,41 +1350,57 @@
}
/**
- * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
- * Total number of unlocked user storage is limited by mMaxRunningUsers.
- * If there are more unlocked users, evict and lock the least recently stopped user and
- * lock that user's data. Regardless of the mode, ephemeral user is always locked
- * immediately.
+ * Returns which user, if any, should be locked when the given user is stopped.
+ *
+ * For typical (non-mDelayUserDataLocking) devices and users, this will be the provided user.
+ *
+ * However, for some devices or users (based on {@link #canDelayDataLockingForUser(int)}),
+ * storage once unlocked is kept unlocked, even after the user is stopped, so the user to be
+ * locked (if any) may differ.
+ *
+ * For mDelayUserDataLocking devices, the total number of unlocked user storage is limited
+ * (currently by mMaxRunningUsers). If there are more unlocked users, evict and lock the least
+ * recently stopped user and lock that user's data.
+ *
+ * Regardless of the mode, ephemeral user is always locked immediately.
*
* @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
*/
@GuardedBy("mLock")
private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
- int userIdToLock = userId;
- // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
- // state maximum running unlocked users specifically
- if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
- && !getUserInfo(userId).isEphemeral()
- && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+ if (!canDelayDataLockingForUser(userId)
+ || !allowDelayedLocking
+ || getUserInfo(userId).isEphemeral()
+ || hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+ return userId;
+ }
+
+ // Once we reach here, we are in a delayed locking scenario.
+ // Now, no user will be locked, unless the device's policy dictates we should based on the
+ // maximum of such users allowed for the device.
+ if (mDelayUserDataLocking) {
// arg should be object, not index
mLastActiveUsersForDelayedLocking.remove((Integer) userId);
mLastActiveUsersForDelayedLocking.add(0, userId);
int totalUnlockedUsers = mStartedUsers.size()
+ mLastActiveUsersForDelayedLocking.size();
+ // TODO: Decouple the delayed locking flows from mMaxRunningUsers. These users aren't
+ // running so this calculation shouldn't be based on this parameter. Also note that
+ // that if these devices ever support background running users (such as profiles), the
+ // implementation is incorrect since starting such users can cause the max to be
+ // exceeded.
if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
- userIdToLock = mLastActiveUsersForDelayedLocking.get(
+ final int userIdToLock = mLastActiveUsersForDelayedLocking.get(
mLastActiveUsersForDelayedLocking.size() - 1);
mLastActiveUsersForDelayedLocking
.remove(mLastActiveUsersForDelayedLocking.size() - 1);
- Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
- + " lock user:" + userIdToLock);
- } else {
- Slogf.i(TAG, "finishUserStopped, user:" + userId + ", skip locking");
- // do not lock
- userIdToLock = UserHandle.USER_NULL;
+ Slogf.i(TAG, "finishUserStopped: should stop user " + userId
+ + " but should lock user " + userIdToLock);
+ return userIdToLock;
}
}
- return userIdToLock;
+ Slogf.i(TAG, "finishUserStopped: should stop user " + userId + " but without any locking");
+ return UserHandle.USER_NULL;
}
/**
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index cb15abc..cd064ae 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,11 +19,11 @@
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED;
@@ -40,6 +40,7 @@
import android.app.ActivityManagerInternal;
import android.app.IProcessObserver;
import android.content.Context;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerInternal;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 8c6068d..02c9bb3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.Binder;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index d5945f4..65b393a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.hardware.devicestate.DeviceState;
import android.util.Dumpable;
import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 20485c1..d92629f 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index f5f2fa8..6c3fd83d 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b8a63cd..96c0c8a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3560,8 +3560,7 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
- if (!Flags.useHandwritingListenerForTooltype()
- && lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+ if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
curMethod.updateEditorToolType(lastClickToolType);
}
mVisibilityApplier.performShowIme(windowToken, statsToken,
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null) {
+ if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index afcf5a0..76952b3 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -29,6 +29,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.os.Environment;
import android.os.PowerManager;
import android.util.ArrayMap;
@@ -40,7 +41,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.input.InputManagerInternal;
import com.android.server.policy.devicestate.config.Conditions;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 6a3cf43..a3e2869 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+
import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -30,15 +33,20 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Slog;
import android.view.Display;
+import android.view.DragEvent;
import android.view.IWindow;
import android.view.InputDevice;
import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragCallback;
+import android.window.IUnhandledDragListener;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
import java.util.Objects;
@@ -59,6 +67,7 @@
static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
static final int MSG_ANIMATION_END = 2;
static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
+ static final int MSG_UNHANDLED_DROP_LISTENER_TIMEOUT = 4;
/**
* Drag state per operation.
@@ -72,6 +81,21 @@
private WindowManagerService mService;
private final Handler mHandler;
+ // The unhandled drag listener for handling cross-window drags that end with no target window
+ private IUnhandledDragListener mUnhandledDragListener;
+ private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mService.mGlobalLock) {
+ if (hasPendingUnhandledDropCallback()) {
+ onUnhandledDropCallback(false /* consumedByListeners */);
+ }
+ setUnhandledDragListener(null);
+ }
+ }
+ };
+
/**
* Callback which is used to sync drag state with the vendor-specific code.
*/
@@ -83,10 +107,16 @@
mHandler = new DragHandler(service, looper);
}
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+
boolean dragDropActiveLocked() {
return mDragState != null && !mDragState.isClosing();
}
+ @VisibleForTesting
boolean dragSurfaceRelinquishedToDropTarget() {
return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
}
@@ -96,6 +126,32 @@
mCallback.set(callback);
}
+ /**
+ * Sets the listener for unhandled cross-window drags.
+ */
+ public void setUnhandledDragListener(IUnhandledDragListener listener) {
+ if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) {
+ mUnhandledDragListener.asBinder().unlinkToDeath(
+ mUnhandledDragListenerDeathRecipient, 0);
+ }
+ mUnhandledDragListener = listener;
+ if (listener != null && listener.asBinder() != null) {
+ try {
+ mUnhandledDragListener.asBinder().linkToDeath(
+ mUnhandledDragListenerDeathRecipient, 0);
+ } catch (RemoteException e) {
+ mUnhandledDragListener = null;
+ }
+ }
+ }
+
+ /**
+ * Returns whether there is an unhandled drag listener set.
+ */
+ boolean hasUnhandledDragListener() {
+ return mUnhandledDragListener != null;
+ }
+
void sendDragStartedIfNeededLocked(WindowState window) {
mDragState.sendDragStartedIfNeededLocked(window);
}
@@ -247,6 +303,10 @@
}
}
+ /**
+ * This is called from the drop target window that received ACTION_DROP
+ * (see DragState#reportDropWindowLock()).
+ */
void reportDropResult(IWindow window, boolean consumed) {
IBinder token = window.asBinder();
if (DEBUG_DRAG) {
@@ -273,22 +333,89 @@
// so be sure to halt the timeout even if the later WindowState
// lookup fails.
mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+
WindowState callingWin = mService.windowForClientLocked(null, window, false);
if (callingWin == null) {
Slog.w(TAG_WM, "Bad result-reporting window " + window);
return; // !!! TODO: throw here?
}
- mDragState.mDragResult = consumed;
- mDragState.mRelinquishDragSurfaceToDropTarget = consumed
- && mDragState.targetInterceptsGlobalDrag(callingWin);
- mDragState.endDragLocked();
+ // If the drop was not consumed by the target window, then check if it should be
+ // consumed by the system unhandled drag listener
+ if (!consumed && notifyUnhandledDrop(mDragState.mUnhandledDropEvent,
+ "window-drop")) {
+ // If the unhandled drag listener is notified, then defer ending the drag until
+ // the listener calls back
+ return;
+ }
+
+ final boolean relinquishDragSurfaceToDropTarget =
+ consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+ mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
}
} finally {
mCallback.get().postReportDropResult();
}
}
+ /**
+ * Notifies the unhandled drag listener if needed.
+ * @return whether the listener was notified and subsequent drag completion should be deferred
+ * until the listener calls back
+ */
+ boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
+ final boolean isLocalDrag =
+ (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
+ if (!com.android.window.flags.Flags.delegateUnhandledDrags()
+ || mUnhandledDragListener == null
+ || isLocalDrag) {
+ // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
+ // purely local drag
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
+ + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")");
+ return false;
+ }
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
+ try {
+ // Schedule timeout for the unhandled drag listener to call back
+ sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
+ mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
+ @Override
+ public void notifyUnhandledDropComplete(boolean consumedByListener) {
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
+ synchronized (mService.mGlobalLock) {
+ onUnhandledDropCallback(consumedByListener);
+ }
+ }
+ });
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+ return false;
+ }
+ }
+
+ /**
+ * Called when the unhandled drag listener has completed handling the drop
+ * (if it was notififed).
+ */
+ @VisibleForTesting
+ void onUnhandledDropCallback(boolean consumedByListener) {
+ mHandler.removeMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null);
+ // If handled, then the listeners assume responsibility of cleaning up the drag surface
+ mDragState.mDragResult = consumedByListener;
+ mDragState.mRelinquishDragSurfaceToDropTarget = consumedByListener;
+ mDragState.closeLocked();
+ }
+
+ /**
+ * Returns whether we are currently waiting for the unhandled drag listener to callback after
+ * it was notified of an unhandled drag.
+ */
+ boolean hasPendingUnhandledDropCallback() {
+ return mHandler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT);
+ }
+
void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "cancelDragAndDrop");
@@ -436,8 +563,8 @@
synchronized (mService.mGlobalLock) {
// !!! TODO: ANR the drag-receiving app
if (mDragState != null) {
- mDragState.mDragResult = false;
- mDragState.endDragLocked();
+ mDragState.endDragLocked(false /* consumed */,
+ false /* relinquishDragSurfaceToDropTarget */);
}
}
break;
@@ -473,6 +600,13 @@
}
break;
}
+
+ case MSG_UNHANDLED_DROP_LISTENER_TIMEOUT: {
+ synchronized (mService.mGlobalLock) {
+ onUnhandledDropCallback(false /* consumedByListener */);
+ }
+ break;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d302f06..76038b9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -147,6 +147,11 @@
*/
private boolean mIsClosing;
+ // Stores the last drop event which was reported to a valid drop target window, or null
+ // otherwise. This drop event will contain private info and should only be consumed by the
+ // unhandled drag listener.
+ DragEvent mUnhandledDropEvent;
+
DragState(WindowManagerService service, DragDropController controller, IBinder token,
SurfaceControl surface, int flags, IBinder localWin) {
mService = service;
@@ -287,14 +292,54 @@
mData = null;
mThumbOffsetX = mThumbOffsetY = 0;
mNotifiedWindows = null;
+ if (mUnhandledDropEvent != null) {
+ mUnhandledDropEvent.recycle();
+ mUnhandledDropEvent = null;
+ }
// Notifies the controller that the drag state is closed.
mDragDropController.onDragStateClosedLocked(this);
}
/**
+ * Creates the drop event for this drag gesture. If `touchedWin` is null, then the drop event
+ * will be created for dispatching to the unhandled drag and the drag surface will be provided
+ * as a part of the dispatched event.
+ */
+ private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
+ boolean includeDragSurface) {
+ if (touchedWin != null) {
+ final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+ final DragAndDropPermissionsHandler dragAndDropPermissions;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+ && mData != null) {
+ dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
+ mData,
+ mUid,
+ touchedWin.getOwningPackage(),
+ mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+ mSourceUserId,
+ targetUserId);
+ } else {
+ dragAndDropPermissions = null;
+ }
+ if (mSourceUserId != targetUserId) {
+ if (mData != null) {
+ mData.fixUris(mSourceUserId);
+ }
+ }
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions);
+ } else {
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */);
+ }
+ }
+
+ /**
* Notify the drop target and tells it about the data. If the drop event is not sent to the
- * target, invokes {@code endDragLocked} immediately.
+ * target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to
+ * handle the drop.
*/
boolean reportDropWindowLock(IBinder token, float x, float y) {
if (mAnimator != null) {
@@ -302,41 +347,27 @@
}
final WindowState touchedWin = mService.mInputToWindowMap.get(token);
+ final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
+ true /* includePrivateInfo */);
if (!isWindowNotified(touchedWin)) {
- // "drop" outside a valid window -- no recipient to apply a
- // timeout to, and we can send the drag-ended message immediately.
- mDragResult = false;
- endDragLocked();
+ // Delegate to the unhandled drag listener as a first pass
+ if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
+ // The unhandled drag listener will call back to notify whether it has consumed
+ // the drag, so return here
+ return true;
+ }
+
+ // "drop" outside a valid window -- no recipient to apply a timeout to, and we can send
+ // the drag-ended message immediately.
+ endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
return false;
}
if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
- final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
-
- final DragAndDropPermissionsHandler dragAndDropPermissions;
- if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
- && mData != null) {
- dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
- mData,
- mUid,
- touchedWin.getOwningPackage(),
- mFlags & DRAG_FLAGS_URI_PERMISSIONS,
- mSourceUserId,
- targetUserId);
- } else {
- dragAndDropPermissions = null;
- }
- if (mSourceUserId != targetUserId) {
- if (mData != null) {
- mData.fixUris(mSourceUserId);
- }
- }
final IBinder clientToken = touchedWin.mClient.asBinder();
- final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
- mData, targetInterceptsGlobalDrag(touchedWin),
- dragAndDropPermissions);
+ final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
try {
touchedWin.mClient.dispatchDragEvent(event);
@@ -345,7 +376,7 @@
DragDropController.DRAG_TIMEOUT_MS);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
- endDragLocked();
+ endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
return false;
} finally {
if (MY_PID != touchedWin.mSession.mPid) {
@@ -353,6 +384,7 @@
}
}
mToken = clientToken;
+ mUnhandledDropEvent = unhandledDropEvent;
return true;
}
@@ -478,6 +510,9 @@
boolean containsAppExtras) {
final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin);
+ }
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
@@ -523,14 +558,25 @@
return false;
}
if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) {
+ // Window should not be a target
return false;
}
- if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
+ final boolean isGlobalSameAppDrag = (mFlags & View.DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0;
+ final boolean isGlobalDrag = (mFlags & View.DRAG_FLAG_GLOBAL) != 0;
+ final boolean isAnyGlobalDrag = isGlobalDrag || isGlobalSameAppDrag;
+ if (!isAnyGlobalDrag || !targetWindowSupportsGlobalDrag(targetWin)) {
// Drag is limited to the current window.
if (!isLocalWindow) {
return false;
}
}
+ if (isGlobalSameAppDrag) {
+ // Drag is limited to app windows from the same uid or windows that can intercept global
+ // drag
+ if (!interceptsGlobalDrag && mUid != targetWin.getUid()) {
+ return false;
+ }
+ }
return interceptsGlobalDrag
|| mCrossProfileCopyAllowed
@@ -547,7 +593,10 @@
/**
* @return whether the given window {@param targetWin} can intercept global drags.
*/
- public boolean targetInterceptsGlobalDrag(WindowState targetWin) {
+ public boolean targetInterceptsGlobalDrag(@Nullable WindowState targetWin) {
+ if (targetWin == null) {
+ return false;
+ }
return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0;
}
@@ -561,9 +610,6 @@
if (isWindowNotified(newWin)) {
return;
}
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
- }
sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
containsApplicationExtras(mDataDescription));
}
@@ -578,7 +624,13 @@
return false;
}
- void endDragLocked() {
+ /**
+ * Ends the current drag, animating the drag surface back to the source if the drop was not
+ * consumed by the receiving window.
+ */
+ void endDragLocked(boolean dropConsumed, boolean relinquishDragSurfaceToDropTarget) {
+ mDragResult = dropConsumed;
+ mRelinquishDragSurfaceToDropTarget = relinquishDragSurfaceToDropTarget;
if (mAnimator != null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4ea76e1..de8d9f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
import android.window.InputTransferToken;
import android.window.ScreenCapture;
import android.window.SystemPerformanceHinter;
@@ -10026,4 +10027,16 @@
void onProcessActivityVisibilityChanged(int uid, boolean visible) {
mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
}
+
+ /**
+ * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+ * (ie. not handled by any window which can handle the drag).
+ */
+ @Override
+ public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException {
+ mAtmService.enforceTaskPermission("setUnhandledDragListener");
+ synchronized (mGlobalLock) {
+ mDragDropController.setUnhandledDragListener(listener);
+ }
+ }
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index 8b22718..bc264a4 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -16,11 +16,12 @@
package com.android.server.policy;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+
import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
@@ -36,7 +37,6 @@
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
import com.android.server.policy.feature.flags.FeatureFlags;
-import com.android.server.policy.feature.flags.FeatureFlagsImpl;
import java.io.PrintWriter;
import java.util.function.Predicate;
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 021a667..bf2619b 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -34,6 +34,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
@@ -48,7 +49,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.feature.flags.FeatureFlags;
import com.android.server.policy.feature.flags.FeatureFlagsImpl;
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index 04cebab..930f4a6 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -51,6 +51,7 @@
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputSensorInfo;
import android.os.Handler;
@@ -58,7 +59,6 @@
import android.testing.AndroidTestingRunner;
import android.view.Display;
-import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider.Listener;
import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 26934d8..4307ec5 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -682,19 +682,19 @@
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
setUpAndStartUserInBackground(TEST_USER_ID);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
/* keyEvictedCallback= */ null, /* expectLocking= */ true);
setUpAndStartUserInBackground(TEST_USER_ID1);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
setUpAndStartUserInBackground(TEST_USER_ID2);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
/* keyEvictedCallback= */ null, /* expectLocking= */ true);
setUpAndStartUserInBackground(TEST_USER_ID3);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
@@ -739,21 +739,21 @@
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
- // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+ // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
setUpAndStartUserInBackground(TEST_USER_ID);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
/* keyEvictedCallback= */ null, /* expectLocking= */ false);
setUpAndStartUserInBackground(TEST_USER_ID1);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
setUpAndStartUserInBackground(TEST_USER_ID2);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
/* keyEvictedCallback= */ null, /* expectLocking= */ true);
setUpAndStartUserInBackground(TEST_USER_ID3);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
@@ -843,7 +843,7 @@
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ false);
}
@@ -855,19 +855,20 @@
mSetFlagsRule.disableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
}
+ /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@Test
- public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+ public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
@@ -875,10 +876,14 @@
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
- /* keyEvictedCallback */ null, /* expectLocking= */ true);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ false);
}
+ /**
+ * Tests that when a device/user (managed profile) does not permit delayed locking, then
+ * even if allowDelayedLocking is true, the user will still be locked.
+ */
@Test
public void testStopManagedProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
@@ -886,7 +891,7 @@
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
- assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
}
@@ -1087,29 +1092,29 @@
mUserStates.put(userId, mUserController.getStartedUserState(userId));
}
- private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+ private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
- int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
- delayedLocking, null, keyEvictedCallback);
+ int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+ allowDelayedLocking, null, keyEvictedCallback);
assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
- assertUserLockedOrUnlockedState(userId, delayedLocking, expectLocking);
+ assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
}
private void assertProfileLockedOrUnlockedAfterStopping(int userId, boolean expectLocking)
throws Exception {
boolean profileStopped = mUserController.stopProfile(userId);
assertThat(profileStopped).isTrue();
- assertUserLockedOrUnlockedState(userId, /* delayedLocking= */ false, expectLocking);
+ assertUserLockedOrUnlockedState(userId, /* allowDelayedLocking= */ false, expectLocking);
}
- private void assertUserLockedOrUnlockedState(int userId, boolean delayedLocking,
+ private void assertUserLockedOrUnlockedState(int userId, boolean allowDelayedLocking,
boolean expectLocking) throws InterruptedException, RemoteException {
// fake all interim steps
UserState ussUser = mUserStates.get(userId);
ussUser.setState(UserState.STATE_SHUTDOWN);
// Passing delayedLocking invalidates incorrect internal data passing but currently there is
// no easy way to get that information passed through lambda.
- mUserController.finishUserStopped(ussUser, delayedLocking);
+ mUserController.finishUserStopped(ussUser, allowDelayedLocking);
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
.lockCeStorage(userId);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index fa39364..b705077 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -31,6 +31,7 @@
import static org.testng.Assert.assertThrows;
import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateRequest;
import android.hardware.devicestate.IDeviceStateManagerCallback;
@@ -72,7 +73,8 @@
new DeviceState(0, "DEFAULT", 0 /* flags */);
private static final DeviceState OTHER_DEVICE_STATE =
new DeviceState(1, "OTHER", 0 /* flags */);
- private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ private static final DeviceState
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
// A device state that is not reported as being supported for the default test provider.
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b3d25f2..cfdb586 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -25,6 +25,7 @@
import static junit.framework.Assert.assertNull;
import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
@@ -48,8 +49,10 @@
@RunWith(AndroidJUnit4.class)
public final class OverrideRequestControllerTest {
- private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
- private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+ private static final DeviceState
+ TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
+ private static final DeviceState
+ TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
private TestStatusChangeListener mStatusListener;
private OverrideRequestController mController;
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7e40f96..16909ab 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -40,12 +40,12 @@
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.os.PowerManager;
import androidx.annotation.NonNull;
import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.input.InputManagerInternal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 1fb7cd8..9e2b1ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -32,14 +32,17 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.PendingIntent;
@@ -49,9 +52,12 @@
import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Message;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.view.DragEvent;
@@ -61,6 +67,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragListener;
import androidx.test.filters.SmallTest;
@@ -533,14 +540,98 @@
});
}
+ @Test
+ public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
+ assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+ final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ doReturn(mock(Binder.class)).when(listener).asBinder();
+ mTarget.setUnhandledDragListener(listener);
+ doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
+ verify(listener, times(0)).onUnhandledDrop(any(), any());
+ }
+
+ @Test
+ public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
+ assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+ final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ doReturn(mock(Binder.class)).when(listener).asBinder();
+ mTarget.setUnhandledDragListener(listener);
+ final int invalidXY = 100_000;
+ startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+ // Notify the unhandled drag listener
+ mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.reportDropResult(mWindow.mClient, false);
+ mTarget.onUnhandledDropCallback(true);
+ mToken = null;
+ try {
+ verify(listener, times(1)).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
+ }
+
+ @Test
+ public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
+ assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+ final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ doReturn(mock(Binder.class)).when(listener).asBinder();
+ mTarget.setUnhandledDragListener(listener);
+ final int invalidXY = 100_000;
+ startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+ // Notify the unhandled drag listener
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.onUnhandledDropCallback(true);
+ mToken = null;
+ try {
+ verify(listener, times(1)).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
+ }
+
+ @Test
+ public void testUnhandledDragListenerCallbackTimeout() {
+ assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+ final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ doReturn(mock(Binder.class)).when(listener).asBinder();
+ mTarget.setUnhandledDragListener(listener);
+ final int invalidXY = 100_000;
+ startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+ // Notify the unhandled drag listener
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+
+ // Verify that the unhandled drop listener callback timeout has been scheduled
+ final Handler handler = mTarget.getHandler();
+ assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+
+ // Force trigger the timeout and verify that it actually cleans up the drag & timeout
+ handler.handleMessage(Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+ assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+ assertFalse(mTarget.dragDropActiveLocked());
+ mToken = null;
+ });
+ }
+
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
startDrag(flags, data, () -> {
mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
- mTarget.handleMotionEvent(false, dropX, dropY);
+ mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY);
mToken = mWindow.mClient.asBinder();
});
}
+ /**
+ * Starts a drag with the given parameters, calls Runnable `r` after drag is started.
+ */
private void startDrag(int flag, ClipData data, Runnable r) {
final SurfaceSession appSession = new SurfaceSession();
try {