summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/appsearch/framework/api/current.txt5
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java89
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/view/DragAndDropPermissions.java59
-rw-r--r--core/java/android/view/RoundedCorner.java2
-rw-r--r--core/java/android/view/RoundedCorners.java8
-rw-r--r--core/java/android/widget/Editor.java3
-rw-r--r--core/java/android/widget/SpellChecker.java249
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java4
-rw-r--r--core/tests/coretests/src/android/view/RoundedCornerTest.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java29
-rw-r--r--packages/Connectivity/framework/src/android/net/VpnTransportInfo.java4
-rw-r--r--packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java2
-rw-r--r--services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java35
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java29
19 files changed, 399 insertions, 150 deletions
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 5ded446ff81a..386c6e23f865 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -18,9 +18,10 @@ package android.app.appsearch {
}
public static final class AppSearchManager.SearchContext.Builder {
- ctor public AppSearchManager.SearchContext.Builder();
+ ctor @Deprecated public AppSearchManager.SearchContext.Builder();
+ ctor public AppSearchManager.SearchContext.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build();
- method @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
+ method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
}
public interface AppSearchMigrationHelper {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a62bb504debc..0c6b86b68636 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -30,17 +30,19 @@ import java.util.function.Consumer;
* Provides access to the centralized AppSearch index maintained by the system.
*
* <p>AppSearch is a search library for managing structured data featuring:
+ *
* <ul>
- * <li>A fully offline on-device solution
- * <li>A set of APIs for applications to index documents and retrieve them via full-text search
- * <li>APIs for applications to allow the System to display their content on system UI surfaces
- * <li>Similarly, APIs for applications to allow the System to share their content with other
- * specified applications.
+ * <li>A fully offline on-device solution
+ * <li>A set of APIs for applications to index documents and retrieve them via full-text search
+ * <li>APIs for applications to allow the System to display their content on system UI surfaces
+ * <li>Similarly, APIs for applications to allow the System to share their content with other
+ * specified applications.
* </ul>
*
* <p>Applications create a database by opening an {@link AppSearchSession}.
*
* <p>Example:
+ *
* <pre>
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
*
@@ -51,11 +53,12 @@ import java.util.function.Consumer;
* });</pre>
*
* <p>After opening the session, a schema must be set in order to define the organizational
- * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema
- * is composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique
- * type of data.
+ * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
+ * composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
+ * of data.
*
* <p>Example:
+ *
* <pre>
* AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
* .addProperty(new StringPropertyConfig.Builder("subject")
@@ -73,15 +76,16 @@ import java.util.function.Consumer;
* });</pre>
*
* <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
- * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a
- * logical group of documents. For example, a namespace can be created to group documents on a
- * per-account basis. A URI identifies a single document within a namespace. The combination
- * of URI and namespace uniquely identifies a {@link GenericDocument} in the database.
+ * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a logical
+ * group of documents. For example, a namespace can be created to group documents on a per-account
+ * basis. A URI identifies a single document within a namespace. The combination of URI and
+ * namespace uniquely identifies a {@link GenericDocument} in the database.
*
- * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database
- * and indexed by calling {@link AppSearchSession#put}.
+ * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
+ * indexed by calling {@link AppSearchSession#put}.
*
* <p>Example:
+ *
* <pre>
* // Although for this example we use GenericDocument directly, we recommend extending
* // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
@@ -106,18 +110,12 @@ import java.util.function.Consumer;
* and namespace.
*
* <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
- * operation. Remove operations can be done by URI and namespace via
- * {@link AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)},
- * or by query via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
+ * operation. Remove operations can be done by URI and namespace via {@link
+ * AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)}, or by query via
+ * {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
- /**
- * The default empty database name.
- *
- * @hide
- */
- public static final String DEFAULT_DATABASE_NAME = "";
private final IAppSearchManager mService;
private final Context mContext;
@@ -149,10 +147,42 @@ public class AppSearchManager {
/** Builder for {@link SearchContext} objects. */
public static final class Builder {
- private String mDatabaseName = DEFAULT_DATABASE_NAME;
+ private String mDatabaseName;
private boolean mBuilt = false;
/**
+ * TODO(b/181887768): This method exists only for dogfooder transition and must be
+ * removed.
+ *
+ * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+ * method exists only for dogfooder transition and must be removed.
+ */
+ @Deprecated
+ public Builder() {
+ mDatabaseName = "";
+ }
+
+ /**
+ * Creates a new {@link SearchContext.Builder}.
+ *
+ * <p>{@link AppSearchSession} will create or open a database under the given name.
+ *
+ * <p>Databases with different names are fully separate with distinct types, namespaces,
+ * and data.
+ *
+ * <p>Database name cannot contain {@code '/'}.
+ *
+ * @param databaseName The name of the database.
+ * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ */
+ public Builder(@NonNull String databaseName) {
+ Objects.requireNonNull(databaseName);
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
+ mDatabaseName = databaseName;
+ }
+
+ /**
* Sets the name of the database associated with {@link AppSearchSession}.
*
* <p>{@link AppSearchSession} will create or open a database under the given name.
@@ -164,16 +194,21 @@ public class AppSearchManager {
*
* <p>If not specified, defaults to the empty string.
*
+ * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
+ * removed.
+ *
* @param databaseName The name of the database.
* @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+ * method exists only for dogfooder transition and must be removed.
*/
+ @Deprecated
@NonNull
public Builder setDatabaseName(@NonNull String databaseName) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(databaseName);
- if (databaseName.contains("/")) {
- throw new IllegalArgumentException("Database name cannot contain '/'");
- }
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
mDatabaseName = databaseName;
return this;
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bfc205b9f9a6..251b3c46e681 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11615,7 +11615,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
- method public boolean isNrDualConnectivityEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11701,6 +11701,7 @@ package android.telephony {
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+ field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3af07635c185..0358fe56203c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2586,9 +2586,7 @@ class ContextImpl extends Context {
@Override
public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
if (display == null) {
- throw new UnsupportedOperationException("Token context can only be created from "
- + "other visual contexts, such as Activity or one created with "
- + "Context#createDisplayContext(Display)");
+ throw new IllegalArgumentException("Display must not be null");
}
final ContextImpl tokenContext = createBaseWindowContext(token, display);
tokenContext.setResources(createWindowContextResources());
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index d47604d6ec41..16204d852a3a 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -16,12 +16,15 @@
package android.view;
+import static java.lang.Integer.toHexString;
+
import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
@@ -56,9 +59,28 @@ import com.android.internal.view.IDragAndDropPermissions;
*/
public final class DragAndDropPermissions implements Parcelable {
- private final IDragAndDropPermissions mDragAndDropPermissions;
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Permissions for a drop can be granted in one of two ways:
+ * <ol>
+ * <li>An app can explicitly request permissions using
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
+ * revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
+ * <li>The platform can request permissions on behalf of the app (e.g. in
+ * {@link android.widget.Editor}). In this case permissions are revoked automatically when
+ * the app process terminates. See {@link #takeTransient()}.
+ * </ol>
+ *
+ * <p>In order to implement the second case above, we create a static token object here. This
+ * ensures that the token stays alive for the lifetime of the app process, allowing us to
+ * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
+ * {@code DragAndDropPermissionsHandler}.
+ */
+ private static IBinder sAppToken;
- private IBinder mTransientToken;
+ private final IDragAndDropPermissions mDragAndDropPermissions;
/**
* Create a new {@link DragAndDropPermissions} object to control the access permissions for
@@ -81,30 +103,51 @@ public final class DragAndDropPermissions implements Parcelable {
}
/**
- * Take the permissions and bind their lifetime to the activity.
+ * Take permissions, binding their lifetime to the activity.
+ *
+ * <p>Note: This API is exposed to apps via
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}.
+ *
* @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean take(IBinder activityToken) {
try {
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling take() with activity-bound token: "
+ + toHexString(activityToken.hashCode()));
+ }
mDragAndDropPermissions.take(activityToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": take() failed with a RemoteException", e);
return false;
}
return true;
}
/**
- * Take the permissions. Must call {@link #release} explicitly.
+ * Take permissions transiently. Permissions will be revoked when the app process terminates.
+ *
+ * <p>Note: This API is not exposed to apps.
+ *
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean takeTransient() {
try {
- mTransientToken = new Binder();
- mDragAndDropPermissions.takeTransient(mTransientToken);
+ if (sAppToken == null) {
+ sAppToken = new Binder();
+ }
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
+ + toHexString(sAppToken.hashCode()));
+ }
+ mDragAndDropPermissions.takeTransient(sAppToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
return false;
}
return true;
@@ -116,8 +159,8 @@ public final class DragAndDropPermissions implements Parcelable {
public void release() {
try {
mDragAndDropPermissions.release();
- mTransientToken = null;
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -142,11 +185,9 @@ public final class DragAndDropPermissions implements Parcelable {
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeStrongInterface(mDragAndDropPermissions);
- destination.writeStrongBinder(mTransientToken);
}
private DragAndDropPermissions(Parcel in) {
mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
- mTransientToken = in.readStrongBinder();
}
}
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
index cc7525b48aa2..56b438350d06 100644
--- a/core/java/android/view/RoundedCorner.java
+++ b/core/java/android/view/RoundedCorner.java
@@ -163,7 +163,7 @@ public final class RoundedCorner implements Parcelable {
* @hide
*/
public boolean isEmpty() {
- return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+ return mRadius == 0 || mCenter.x <= 0 || mCenter.y <= 0;
}
private String getPositionString(@Position int position) {
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 569c287901c7..623d9692ac80 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -181,16 +181,16 @@ public class RoundedCorners implements Parcelable {
boolean hasRoundedCorner;
switch (position) {
case POSITION_TOP_LEFT:
- hasRoundedCorner = radius > insetTop || radius > insetLeft;
+ hasRoundedCorner = radius > insetTop && radius > insetLeft;
break;
case POSITION_TOP_RIGHT:
- hasRoundedCorner = radius > insetTop || radius > insetRight;
+ hasRoundedCorner = radius > insetTop && radius > insetRight;
break;
case POSITION_BOTTOM_RIGHT:
- hasRoundedCorner = radius > insetBottom || radius > insetRight;
+ hasRoundedCorner = radius > insetBottom && radius > insetRight;
break;
case POSITION_BOTTOM_LEFT:
- hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+ hasRoundedCorner = radius > insetBottom && radius > insetLeft;
break;
default:
throw new IllegalArgumentException(
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ca8967794c81..238ce85622b5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2904,9 +2904,6 @@ public class Editor {
} finally {
mTextView.endBatchEdit();
mUndoInputFilter.freezeLastEdit();
- if (permissions != null) {
- permissions.release();
- }
}
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index d59a415469b6..2464b4af23eb 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -26,6 +26,7 @@ import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.LruCache;
+import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -62,7 +63,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
// Pause between each spell check to keep the UI smooth
private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
- private static final int MIN_SENTENCE_LENGTH = 50;
+ // The maximum length of sentence.
+ private static final int MAX_SENTENCE_LENGTH = WORD_ITERATOR_INTERVAL;
private static final int USE_SPAN_RANGE = -1;
@@ -89,7 +91,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
// Shared by all SpellParsers. Cannot be shared with TextView since it may be used
// concurrently due to the asynchronous nature of onGetSuggestions.
- private WordIterator mWordIterator;
+ private SentenceIteratorWrapper mSentenceIterator;
@Nullable
private TextServicesManager mTextServicesManager;
@@ -151,8 +153,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
resetSession();
if (locale != null) {
- // Change SpellParsers' wordIterator locale
- mWordIterator = new WordIterator(locale);
+ // Change SpellParsers' sentenceIterator locale
+ mSentenceIterator = new SentenceIteratorWrapper(
+ BreakIterator.getSentenceInstance(locale));
}
// This class is the listener for locale change: warn other locale-aware objects
@@ -306,22 +309,30 @@ public class SpellChecker implements SpellCheckerSessionListener {
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
- // Do not check this word if the user is currently editing it
- final boolean isEditing;
+ // Check the span if any of following conditions is met:
+ // - the user is not currently editing it
+ // - or `forceCheckWhenEditingWord` is true.
+ final boolean isNotEditing;
// Defer spell check when typing a word ending with a punctuation like an apostrophe
// which could end up being a mid-word punctuation.
if (selectionStart == end + 1
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
- isEditing = false;
- } else {
+ isNotEditing = false;
+ } else if (selectionEnd <= start || selectionStart > end) {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
- isEditing = selectionEnd <= start || selectionStart > end;
+ isNotEditing = true;
+ } else {
+ // When cursor is at the end of spell check span, allow spell check if the
+ // character before cursor is a separator.
+ isNotEditing = selectionStart == end
+ && selectionStart > 0
+ && isSeparator(Character.codePointBefore(editable, selectionStart));
}
- if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
+ if (start >= 0 && end > start && (forceCheckWhenEditingWord || isNotEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
@@ -346,6 +357,19 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
}
+ private static boolean isSeparator(int codepoint) {
+ final int type = Character.getType(codepoint);
+ return ((1 << type) & ((1 << Character.SPACE_SEPARATOR)
+ | (1 << Character.LINE_SEPARATOR)
+ | (1 << Character.PARAGRAPH_SEPARATOR)
+ | (1 << Character.DASH_PUNCTUATION)
+ | (1 << Character.END_PUNCTUATION)
+ | (1 << Character.FINAL_QUOTE_PUNCTUATION)
+ | (1 << Character.INITIAL_QUOTE_PUNCTUATION)
+ | (1 << Character.START_PUNCTUATION)
+ | (1 << Character.OTHER_PUNCTUATION))) != 0;
+ }
+
private SpellCheckSpan onGetSuggestionsInternal(
SuggestionsInfo suggestionsInfo, int offset, int length) {
if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) {
@@ -534,6 +558,60 @@ public class SpellChecker implements SpellCheckerSessionListener {
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
}
+ /**
+ * A wrapper of sentence iterator which only processes the specified window of the given text.
+ */
+ private static class SentenceIteratorWrapper {
+ private BreakIterator mSentenceIterator;
+ private int mStartOffset;
+ private int mEndOffset;
+
+ SentenceIteratorWrapper(BreakIterator sentenceIterator) {
+ mSentenceIterator = sentenceIterator;
+ }
+
+ /**
+ * Set the char sequence and the text window to process.
+ */
+ public void setCharSequence(CharSequence sequence, int start, int end) {
+ mStartOffset = Math.max(0, start);
+ mEndOffset = Math.min(end, sequence.length());
+ mSentenceIterator.setText(sequence.subSequence(mStartOffset, mEndOffset).toString());
+ }
+
+ /**
+ * See {@link BreakIterator#preceding(int)}
+ */
+ public int preceding(int offset) {
+ if (offset < mStartOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.preceding(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#following(int)}
+ */
+ public int following(int offset) {
+ if (offset > mEndOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.following(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#isBoundary(int)}
+ */
+ public boolean isBoundary(int offset) {
+ if (offset < mStartOffset || offset > mEndOffset) {
+ return false;
+ }
+ return mSentenceIterator.isBoundary(offset - mStartOffset);
+ }
+ }
+
private class SpellParser {
private Object mRange = new Object();
@@ -582,27 +660,15 @@ public class SpellChecker implements SpellCheckerSessionListener {
public void parse() {
Editable editable = (Editable) mTextView.getText();
- // Iterate over the newly added text and schedule new SpellCheckSpans
- final int start = Math.max(
- 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
-
- final int end = editable.getSpanEnd(mRange);
-
- int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
- mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
-
- // Move back to the beginning of the current word, if any
- int wordStart = mWordIterator.preceding(start);
- int wordEnd;
- if (wordStart == BreakIterator.DONE) {
- wordEnd = mWordIterator.following(start);
- if (wordEnd != BreakIterator.DONE) {
- wordStart = mWordIterator.getBeginning(wordEnd);
- }
- } else {
- wordEnd = mWordIterator.getEnd(wordStart);
- }
- if (wordEnd == BreakIterator.DONE) {
+ final int textChangeStart = editable.getSpanStart(mRange);
+ final int textChangeEnd = editable.getSpanEnd(mRange);
+
+ Range<Integer> sentenceBoundary = detectSentenceBoundary(editable, textChangeStart,
+ textChangeEnd);
+ int sentenceStart = sentenceBoundary.getLower();
+ int sentenceEnd = sentenceBoundary.getUpper();
+
+ if (sentenceStart == sentenceEnd) {
if (DBG) {
Log.i(TAG, "No more spell check.");
}
@@ -612,29 +678,16 @@ public class SpellChecker implements SpellCheckerSessionListener {
boolean scheduleOtherSpellCheck = false;
- if (wordIteratorWindowEnd < end) {
+ if (sentenceEnd < textChangeEnd) {
if (DBG) {
Log.i(TAG, "schedule other spell check.");
}
// Several batches needed on that region. Cut after last previous word
scheduleOtherSpellCheck = true;
}
- int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
- boolean correct = spellCheckEnd != BreakIterator.DONE;
- if (correct) {
- spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
- correct = spellCheckEnd != BreakIterator.DONE;
- }
- if (!correct) {
- if (DBG) {
- Log.i(TAG, "Incorrect range span.");
- }
- stop();
- return;
- }
+ int spellCheckEnd = sentenceEnd;
do {
- // TODO: Find the start position of the sentence.
- int spellCheckStart = wordStart;
+ int spellCheckStart = sentenceStart;
boolean createSpellCheckSpan = true;
// Cancel or merge overlapped spell check spans
for (int i = 0; i < mLength; ++i) {
@@ -671,27 +724,23 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
// Stop spell checking when there are no characters in the range.
- if (spellCheckEnd < start) {
- break;
- }
if (spellCheckEnd <= spellCheckStart) {
Log.w(TAG, "Trying to spellcheck invalid region, from "
- + start + " to " + end);
+ + sentenceStart + " to " + spellCheckEnd);
break;
}
if (createSpellCheckSpan) {
addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
}
} while (false);
- wordStart = spellCheckEnd;
-
- if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
+ sentenceStart = spellCheckEnd;
+ if (scheduleOtherSpellCheck && sentenceStart != BreakIterator.DONE
+ && sentenceStart <= textChangeEnd) {
// Update range span: start new spell check from last wordStart
- setRangeSpan(editable, wordStart, end);
+ setRangeSpan(editable, sentenceStart, textChangeEnd);
} else {
removeRangeSpan(editable);
}
-
spellCheck(mForceCheckWhenEditingWord);
}
@@ -708,6 +757,94 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
}
+ private Range<Integer> detectSentenceBoundary(CharSequence sequence,
+ int textChangeStart, int textChangeEnd) {
+ // Only process a substring of the full text due to performance concern.
+ final int iteratorWindowStart = findSeparator(sequence,
+ Math.max(0, textChangeStart - MAX_SENTENCE_LENGTH),
+ Math.max(0, textChangeStart - 2 * MAX_SENTENCE_LENGTH));
+ final int iteratorWindowEnd = findSeparator(sequence,
+ Math.min(textChangeStart + 2 * MAX_SENTENCE_LENGTH, textChangeEnd),
+ Math.min(textChangeStart + 3 * MAX_SENTENCE_LENGTH, sequence.length()));
+ if (DBG) {
+ Log.d(TAG, "Set iterator window as [" + iteratorWindowStart + ", " + iteratorWindowEnd
+ + ").");
+ }
+ mSentenceIterator.setCharSequence(sequence, iteratorWindowStart, iteratorWindowEnd);
+
+ // Detect the offset of sentence begin/end on the substring.
+ int sentenceStart = mSentenceIterator.isBoundary(textChangeStart) ? textChangeStart
+ : mSentenceIterator.preceding(textChangeStart);
+ int sentenceEnd = mSentenceIterator.following(sentenceStart);
+ if (sentenceEnd == BreakIterator.DONE) {
+ sentenceEnd = iteratorWindowEnd;
+ }
+ if (DBG) {
+ if (sentenceStart != sentenceEnd) {
+ Log.d(TAG, "Sentence detected [" + sentenceStart + ", " + sentenceEnd + ").");
+ }
+ }
+
+ if (sentenceEnd - sentenceStart <= MAX_SENTENCE_LENGTH) {
+ // Add more sentences until the MAX_SENTENCE_LENGTH limitation is reached.
+ while (sentenceEnd < textChangeEnd) {
+ int nextEnd = mSentenceIterator.following(sentenceEnd);
+ if (nextEnd == BreakIterator.DONE
+ || nextEnd - sentenceStart > MAX_SENTENCE_LENGTH) {
+ break;
+ }
+ sentenceEnd = nextEnd;
+ }
+ } else {
+ // If the sentence containing `textChangeStart` is longer than MAX_SENTENCE_LENGTH,
+ // the sentence will be sliced into sub-sentences of about MAX_SENTENCE_LENGTH
+ // characters each. This is done by processing the unchecked part of that sentence :
+ // [textChangeStart, sentenceEnd)
+ //
+ // - If the `uncheckedLength` is bigger than MAX_SENTENCE_LENGTH, then check the
+ // [textChangeStart, textChangeStart + MAX_SENTENCE_LENGTH), and leave the rest
+ // part for the next check.
+ //
+ // - If the `uncheckedLength` is smaller than or equal to MAX_SENTENCE_LENGTH,
+ // then check [sentenceEnd - MAX_SENTENCE_LENGTH, sentenceEnd).
+ //
+ // The offset should be rounded up to word boundary.
+ int uncheckedLength = sentenceEnd - textChangeStart;
+ if (uncheckedLength > MAX_SENTENCE_LENGTH) {
+ sentenceEnd = findSeparator(sequence, sentenceStart + MAX_SENTENCE_LENGTH,
+ sentenceEnd);
+ sentenceStart = roundUpToWordStart(sequence, textChangeStart, sentenceStart);
+ } else {
+ sentenceStart = roundUpToWordStart(sequence, sentenceEnd - MAX_SENTENCE_LENGTH,
+ sentenceStart);
+ }
+ }
+ return new Range(sentenceStart, sentenceEnd);
+ }
+
+ private int roundUpToWordStart(CharSequence sequence, int position, int frontBoundary) {
+ if (isSeparator(sequence.charAt(position))) {
+ return position;
+ }
+ int separator = findSeparator(sequence, position, frontBoundary);
+ return separator != frontBoundary ? separator + 1 : frontBoundary;
+ }
+
+ /**
+ * Search the range [start, end) of sequence and returns the position of the first separator.
+ * If end is smaller than start, do a reverse search.
+ * Returns `end` if no separator is found.
+ */
+ private static int findSeparator(CharSequence sequence, int start, int end) {
+ final int step = start < end ? 1 : -1;
+ for (int i = start; i != end; i += step) {
+ if (isSeparator(sequence.charAt(i))) {
+ return i;
+ }
+ }
+ return end;
+ }
+
public static boolean haveWordBoundariesChanged(final Editable editable, final int start,
final int end, final int spanStart, final int spanEnd) {
final boolean haveWordBoundariesChanged;
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index d0c807d24b14..783d088b19b9 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -221,10 +221,6 @@ public final class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args));
}
- public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
- // no-op
- }
-
public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
dispatchMessage(
mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 8eb13bc03b4b..43490213c29a 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -62,6 +62,13 @@ public class RoundedCornerTest {
}
@Test
+ public void testIsEmpty_negativeCenter() {
+ RoundedCorner roundedCorner =
+ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT, 1, -2, -3);
+ assertThat(roundedCorner.isEmpty(), is(true));
+ }
+
+ @Test
public void testEquals() {
RoundedCorner roundedCorner = new RoundedCorner(
RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index cb54021d7a23..3708e151c372 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -119,7 +119,7 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
@Override
@@ -161,24 +161,27 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
/**
- * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by
- * {@code displayId}.
- *
- * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is
- * associated with
- * @param newConfig The propagated configuration
+ * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
+ * {@link DisplayAreaInfo#displayId}.
*/
- private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) {
+ private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ final Display display = mContext.getSystemService(DisplayManager.class)
+ .getDisplay(displayId);
+ if (display == null) {
+ throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
+ + "displayAreaInfo:" + displayAreaInfo);
+ }
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
- daContext = new DisplayAreaContext(mContext, displayId);
+ daContext = new DisplayAreaContext(mContext, display);
mDisplayAreaContexts.put(displayId, daContext);
}
- daContext.updateConfigurationChanges(newConfig);
+ daContext.updateConfigurationChanges(displayAreaInfo.configuration);
}
/**
@@ -228,10 +231,8 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
private final IBinder mToken = new Binder();
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
- public DisplayAreaContext(@NonNull Context context, int displayId) {
+ public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
super(null);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(displayId);
attachBaseContext(context.createTokenContext(mToken, display));
}
diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 340141b78aa5..c5100794899a 100644
--- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -42,9 +42,9 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable {
MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
/** Type of this VPN. */
- @VpnManager.VpnType public final int type;
+ public final int type;
- public VpnTransportInfo(@VpnManager.VpnType int type) {
+ public VpnTransportInfo(int type) {
this.type = type;
}
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
index 781bfcdbc75e..1ccf4175bac5 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
@@ -51,6 +51,7 @@ public class AppSwitchPreference extends SwitchPreference {
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
+ setSingleLineTitle(true);
super.onBindViewHolder(holder);
final View switchView = holder.findViewById(android.R.id.switch_widget);
if (switchView != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 8fd1910041c8..129aca4a689f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -386,7 +386,6 @@ public class BluetoothEventManager {
case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
- case BluetoothDevice.UNBOND_REASON_REMOVED:
errorMsg = R.string.bluetooth_pairing_error_message;
break;
default:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 6a4d650ebf31..4bff78f4af2c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -353,21 +353,6 @@ public class BluetoothEventManagerTest {
}
@Test
- public void showUnbondMessage_reasonRemoved_showCorrectedErrorCode() {
- mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
- mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
- mIntent.putExtra(BluetoothDevice.EXTRA_REASON, BluetoothDevice.UNBOND_REASON_REMOVED);
- when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
- when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
-
- mContext.sendBroadcast(mIntent);
-
- verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
- eq(R.string.bluetooth_pairing_error_message));
- }
-
- @Test
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 426e63181ff1..9f6087834a4e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1862,6 +1862,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Update application display metrics.
final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+ final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
@@ -1878,6 +1879,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
+ mDisplayInfo.roundedCorners = roundedCorners;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 999c5859ba1d..62e4a8547600 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,12 +16,15 @@
package com.android.server.wm;
+import static java.lang.Integer.toHexString;
+
import android.app.UriGrantsManager;
import android.content.ClipData;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
@@ -32,6 +35,9 @@ import java.util.ArrayList;
class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
implements IBinder.DeathRecipient {
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
+
private final WindowManagerGlobalLock mGlobalLock;
private final int mSourceUid;
private final String mTargetPackage;
@@ -43,7 +49,7 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
private IBinder mActivityToken = null;
private IBinder mPermissionOwnerToken = null;
- private IBinder mTransientToken = null;
+ private IBinder mAppToken = null;
DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -62,6 +68,10 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to activity: "
+ + toHexString(activityToken.hashCode()));
+ }
mActivityToken = activityToken;
// Will throw if Activity is not found.
@@ -84,14 +94,18 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
}
@Override
- public void takeTransient(IBinder transientToken) throws RemoteException {
+ public void takeTransient(IBinder appToken) throws RemoteException {
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to app process: "
+ + toHexString(appToken.hashCode()));
+ }
mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
.newUriPermissionOwner("drop");
- mTransientToken = transientToken;
- mTransientToken.linkToDeath(this, 0);
+ mAppToken = appToken;
+ mAppToken.linkToDeath(this, 0);
doTake(mPermissionOwnerToken);
}
@@ -112,11 +126,17 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
} finally {
mActivityToken = null;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing activity-bound permissions");
+ }
} else {
permissionOwner = mPermissionOwnerToken;
mPermissionOwnerToken = null;
- mTransientToken.unlinkToDeath(this, 0);
- mTransientToken = null;
+ mAppToken.unlinkToDeath(this, 0);
+ mAppToken = null;
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing process-bound permissions");
+ }
}
UriGrantsManagerInternal ugm = LocalServices.getService(UriGrantsManagerInternal.class);
@@ -139,6 +159,9 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
@Override
public void binderDied() {
+ if (DEBUG) {
+ Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+ }
try {
release();
} catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4dc6c7ce35cf..3084b144c9b9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14634,6 +14634,10 @@ public class TelephonyManager {
/**
* Enable/Disable E-UTRA-NR Dual Connectivity.
*
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @param nrDualConnectivityState expected NR dual connectivity state
* This can be passed following states
* <ol>
@@ -14648,6 +14652,9 @@ public class TelephonyManager {
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
@NrDualConnectivityState int nrDualConnectivityState) {
try {
@@ -14667,15 +14674,21 @@ public class TelephonyManager {
/**
* Is E-UTRA-NR Dual Connectivity enabled.
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @return true if dual connectivity is enabled else false. Enabled state does not mean dual
* connectivity is active. It means the device is allowed to connect to both primary and
* secondary cell.
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public boolean isNrDualConnectivityEnabled() {
try {
ITelephony telephony = getITelephony();
@@ -14927,11 +14940,23 @@ public class TelephonyManager {
public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
"CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+ /**
+ * Indicates whether {@link #setNrDualConnectivityState()} and
+ * {@link #isNrDualConnectivityEnabled()} ()} are available. See comments
+ * on respective methods for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
+ "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
+ CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
})
public @interface RadioInterfaceCapability {}