summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java2
-rw-r--r--cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java2
-rw-r--r--cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java2
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java39
-rw-r--r--core/java/android/content/AttributionSource.java20
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java4
-rw-r--r--packages/VpnDialogs/res/values/strings.xml28
-rw-r--r--packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java53
-rw-r--r--services/core/java/com/android/server/notification/SnoozeHelper.java29
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java88
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecordInputSink.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java41
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java19
20 files changed, 413 insertions, 40 deletions
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index 3b14be7327f7..24727c5f2448 100644
--- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -107,7 +107,7 @@ public class DumpCommand extends Command {
DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
int rotation = display.getRotation();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x,
size.y);
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index ab198b319e27..488292d68620 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -139,7 +139,7 @@ public class AccessibilityNodeInfoDumper {
serializer.attribute("", "id", Integer.toString(displayId));
int rotation = display.getRotation();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
for (int i = 0, n = windows.size(); i < n; ++i) {
dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation);
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b1b432bf79ab..a31deb084575 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -767,7 +767,7 @@ public class UiDevice {
if(root != null) {
Display display = getAutomatorBridge().getDefaultDisplay();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
display.getRotation(), size.x, size.y);
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 8aa27853b462..129dbc1fd355 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -30,6 +30,7 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -310,20 +311,27 @@ public class AppWidgetHostView extends FrameLayout {
super.onLayout(changed, left, top, right, bottom);
} catch (final RuntimeException e) {
Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
- removeViewInLayout(mView);
- View child = getErrorView();
- prepareView(child);
- addViewInLayout(child, 0, child.getLayoutParams());
- measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
- child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
- child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
- mView = child;
- mViewMode = VIEW_MODE_ERROR;
+ handleViewError();
}
}
/**
+ * Remove bad view and replace with error message view
+ */
+ private void handleViewError() {
+ removeViewInLayout(mView);
+ View child = getErrorView();
+ prepareView(child);
+ addViewInLayout(child, 0, child.getLayoutParams());
+ measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+ child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
+ child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
+ mView = child;
+ mViewMode = VIEW_MODE_ERROR;
+ }
+
+ /**
* Provide guidance about the size of this widget to the AppWidgetManager. The widths and
* heights should correspond to the full area the AppWidgetHostView is given. Padding added by
* the framework will be accounted for automatically. This information gets embedded into the
@@ -932,4 +940,15 @@ public class AppWidgetHostView extends FrameLayout {
reapplyLastRemoteViews();
}
}
+
+ @Override
+ protected void dispatchDraw(@NonNull Canvas canvas) {
+ try {
+ super.dispatchDraw(canvas);
+ } catch (Exception e) {
+ // Catch draw exceptions that may be caused by RemoteViews
+ Log.e(TAG, "Drawing view failed: " + e);
+ post(this::handleViewError);
+ }
+ }
}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2f61fee88e9f..ec56f9a7cf0c 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -30,6 +30,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.permission.PermissionManager;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.Immutable;
@@ -86,6 +87,8 @@ import java.util.Set;
*/
@Immutable
public final class AttributionSource implements Parcelable {
+ private static final String TAG = "AttributionSource";
+
private static final String DESCRIPTOR = "android.content.AttributionSource";
private static final Binder sDefaultToken = new Binder(DESCRIPTOR);
@@ -153,9 +156,20 @@ public final class AttributionSource implements Parcelable {
AttributionSource(@NonNull Parcel in) {
this(AttributionSourceState.CREATOR.createFromParcel(in));
- // Since we just unpacked this object as part of it transiting a Binder
- // call, this is the perfect time to enforce that its UID and PID can be trusted
- enforceCallingUidAndPid();
+ if (!Binder.isHandlingTransaction()) {
+ Log.e(TAG, "Unable to verify calling UID #" + mAttributionSourceState.uid + " PID #"
+ + mAttributionSourceState.pid + " when not handling Binder transaction; "
+ + "clearing.");
+ mAttributionSourceState.pid = -1;
+ mAttributionSourceState.uid = -1;
+ mAttributionSourceState.packageName = null;
+ mAttributionSourceState.attributionTag = null;
+ mAttributionSourceState.next = null;
+ } else {
+ // Since we just unpacked this object as part of it transiting a Binder
+ // call, this is the perfect time to enforce that its UID and PID can be trusted
+ enforceCallingUidAndPid();
+ }
}
/** @hide */
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 786af5f0823e..105f80adb2c7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import static android.content.ContentProvider.getUserIdFromUri;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.Animator;
@@ -149,6 +151,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
@@ -1375,7 +1378,7 @@ public class ChooserActivity extends ResolverActivity implements
ImageView previewThumbnailView = contentPreviewLayout.findViewById(
R.id.content_preview_thumbnail);
- if (previewThumbnail == null) {
+ if (!validForContentPreview(previewThumbnail)) {
previewThumbnailView.setVisibility(View.GONE);
} else {
mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
@@ -1403,6 +1406,10 @@ public class ChooserActivity extends ResolverActivity implements
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (!validForContentPreview(uri)) {
+ contentPreviewLayout.setVisibility(View.GONE);
+ return contentPreviewLayout;
+ }
imagePreview.findViewById(R.id.content_preview_image_1_large)
.setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
@@ -1412,7 +1419,7 @@ public class ChooserActivity extends ResolverActivity implements
List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
List<Uri> imageUris = new ArrayList<>();
for (Uri uri : uris) {
- if (isImageType(resolver.getType(uri))) {
+ if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) {
imageUris.add(uri);
}
}
@@ -1521,9 +1528,16 @@ public class ChooserActivity extends ResolverActivity implements
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (!validForContentPreview(uri)) {
+ contentPreviewLayout.setVisibility(View.GONE);
+ return contentPreviewLayout;
+ }
loadFileUriIntoView(uri, contentPreviewLayout);
} else {
List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ uris = uris.stream()
+ .filter(ChooserActivity::validForContentPreview)
+ .collect(Collectors.toList());
int uriCount = uris.size();
if (uriCount == 0) {
@@ -1577,6 +1591,24 @@ public class ChooserActivity extends ResolverActivity implements
}
}
+ /**
+ * Indicate if the incoming content URI should be allowed.
+ *
+ * @param uri the uri to test
+ * @return true if the URI is allowed for content preview
+ */
+ private static boolean validForContentPreview(Uri uri) throws SecurityException {
+ if (uri == null) {
+ return false;
+ }
+ int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+ if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) {
+ Log.e(TAG, "dropped invalid content URI belonging to user " + userId);
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
protected boolean isImageType(String mimeType) {
return mimeType != null && mimeType.startsWith("image/");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 73bb6cd9ba1c..0007bbde94a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@ import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
@@ -1554,7 +1555,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
PeopleNotificationIdentifier peopleNotificationIdentifier,
OnUserInteractionCallback onUserInteractionCallback,
Optional<BubblesManager> bubblesManagerOptional,
- NotificationGutsManager gutsManager) {
+ NotificationGutsManager gutsManager,
+ IStatusBarService statusBarService) {
mEntry = entry;
mAppName = appName;
if (mMenuRow == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index c9fcdac8e45f..b09ea1c6282a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -25,6 +25,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -84,6 +85,7 @@ public class ExpandableNotificationRowController implements NodeController {
private final boolean mAllowLongPress;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private final Optional<BubblesManager> mBubblesManagerOptional;
+ private final IStatusBarService mStatusBarService;
@Inject
public ExpandableNotificationRowController(
@@ -109,7 +111,8 @@ public class ExpandableNotificationRowController implements NodeController {
FalsingManager falsingManager,
FalsingCollector falsingCollector,
PeopleNotificationIdentifier peopleNotificationIdentifier,
- Optional<BubblesManager> bubblesManagerOptional) {
+ Optional<BubblesManager> bubblesManagerOptional,
+ IStatusBarService statusBarService) {
mView = view;
mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController;
@@ -134,6 +137,7 @@ public class ExpandableNotificationRowController implements NodeController {
mFalsingCollector = falsingCollector;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
+ mStatusBarService = statusBarService;
}
/**
@@ -160,7 +164,8 @@ public class ExpandableNotificationRowController implements NodeController {
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
mBubblesManagerOptional,
- mNotificationGutsManager
+ mNotificationGutsManager,
+ mStatusBarService
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 4f54e4feb21d..8e3e488f53f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -21,10 +21,13 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.RemoteException;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,6 +43,7 @@ import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.RemoteInputController;
@@ -125,6 +129,8 @@ public class NotificationContentView extends FrameLayout {
private RemoteInputController mRemoteInputController;
private Runnable mExpandedVisibleListener;
private PeopleNotificationIdentifier mPeopleIdentifier;
+ private IStatusBarService mStatusBarService;
+
/**
* List of listeners for when content views become inactive (i.e. not the showing view).
*/
@@ -178,6 +184,7 @@ public class NotificationContentView extends FrameLayout {
mHybridGroupManager = new HybridGroupManager(getContext());
mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
mSmartReplyController = Dependency.get(SmartReplyController.class);
+ mStatusBarService = Dependency.get(IStatusBarService.class);
initView();
}
@@ -1978,4 +1985,36 @@ public class NotificationContentView extends FrameLayout {
}
return Notification.COLOR_INVALID;
}
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ try {
+ super.dispatchDraw(canvas);
+ } catch (Exception e) {
+ // Catch draw exceptions that may be caused by RemoteViews
+ Log.e(TAG, "Drawing view failed: " + e);
+ cancelNotification(e);
+ }
+ }
+
+ private void cancelNotification(Exception exception) {
+ try {
+ setVisibility(GONE);
+ final StatusBarNotification sbn = mNotificationEntry.getSbn();
+ if (mStatusBarService != null) {
+ // report notification inflation errors back up
+ // to notification delegates
+ mStatusBarService.onNotificationError(
+ sbn.getPackageName(),
+ sbn.getTag(),
+ sbn.getId(),
+ sbn.getUid(),
+ sbn.getInitialPid(),
+ exception.getMessage(),
+ sbn.getUser().getIdentifier());
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "cancelNotification failed: " + ex);
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index cea49b71f009..41f83d141e35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -265,7 +265,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
new FalsingManagerFake(),
new FalsingCollectorFake(),
mPeopleNotificationIdentifier,
- Optional.of(mock(BubblesManager.class))
+ Optional.of(mock(BubblesManager.class)),
+ mock(IStatusBarService.class)
));
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0bb66fc14553..d6fc41aa7fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -43,6 +43,7 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.widget.RemoteViews;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -472,7 +473,8 @@ public class NotificationTestHelper {
mPeopleNotificationIdentifier,
mock(OnUserInteractionCallback.class),
Optional.of(mock(BubblesManager.class)),
- mock(NotificationGutsManager.class));
+ mock(NotificationGutsManager.class),
+ mock(IStatusBarService.class));
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index f971a0916837..a85b8e4ff553 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -100,4 +100,32 @@
without any consequences. [CHAR LIMIT=20] -->
<string name="dismiss">Dismiss</string>
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ first 30 characters of the label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the label is very long, then, this
+ will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN
+ connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis.
+ -->
+ <string name="sanitized_vpn_label_with_ellipsis">
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… (
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
+
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but
+ the length is not very long, the dialog will show "VpnLabelWith&lt;br&gt;HtmlTag
+ (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer
+ to sanitized_vpn_label.
+ -->
+ <string name="sanitized_vpn_label">
+ <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> (
+ <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
</resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index fb2367843fc1..2b3202e0a982 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -40,12 +40,18 @@ public class ConfirmDialog extends AlertActivity
implements DialogInterface.OnClickListener, ImageGetter {
private static final String TAG = "VpnConfirm";
+ // Usually the label represents the app name, 150 code points might be enough to display the app
+ // name, and 150 code points won't cover the warning message from VpnDialog.
+ static final int MAX_VPN_LABEL_LENGTH = 150;
+
@VpnManager.VpnType private final int mVpnType;
private String mPackage;
private VpnManager mVm;
+ private View mView;
+
public ConfirmDialog() {
this(VpnManager.TYPE_VPN_SERVICE);
}
@@ -54,6 +60,42 @@ public class ConfirmDialog extends AlertActivity
mVpnType = vpnType;
}
+ /**
+ * This function will use the string resource to combine the VPN label and the package name.
+ *
+ * If the VPN label violates the length restriction, the first 30 code points of VPN label and
+ * the package name will be returned. Or return the VPN label and the package name directly if
+ * the VPN label doesn't violate the length restriction.
+ *
+ * The result will be something like,
+ * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app)
+ * if the VPN label violates the length restriction.
+ * or
+ * - VpnLabelWith&lt;br&gt;HtmlTag (com.vpn.app)
+ * if the VPN label doesn't violate the length restriction.
+ *
+ */
+ private String getSimplifiedLabel(String vpnLabel, String packageName) {
+ if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) {
+ return getString(R.string.sanitized_vpn_label_with_ellipsis,
+ vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)),
+ packageName);
+ }
+
+ return getString(R.string.sanitized_vpn_label, vpnLabel, packageName);
+ }
+
+ protected String getSanitizedVpnLabel(String vpnLabel, String packageName) {
+ final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel);
+ final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0,
+ sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH;
+ if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) {
+ return getSimplifiedLabel(sanitizedVpnLabel, packageName);
+ }
+
+ return sanitizedVpnLabel;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -75,15 +117,16 @@ public class ConfirmDialog extends AlertActivity
finish();
return;
}
- View view = View.inflate(this, R.layout.confirm, null);
- ((TextView) view.findViewById(R.id.warning)).setText(
- Html.fromHtml(getString(R.string.warning, getVpnLabel()),
- this, null /* tagHandler */));
+ mView = View.inflate(this, R.layout.confirm, null);
+ ((TextView) mView.findViewById(R.id.warning)).setText(
+ Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel(
+ getVpnLabel().toString(), mPackage)),
+ this /* imageGetter */, null /* tagHandler */));
mAlertParams.mTitle = getText(R.string.prompt);
mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
- mAlertParams.mView = view;
+ mAlertParams.mView = mView;
setupAlert();
getWindow().setCloseOnTouchOutside(false);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 2e08a9cbf844..f3b92eaf6473 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -64,6 +64,9 @@ public class SnoozeHelper {
static final int CONCURRENT_SNOOZE_LIMIT = 500;
+ // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+ static final int MAX_STRING_LENGTH = 1000;
+
protected static final String XML_TAG_NAME = "snoozed-notifications";
private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -152,7 +155,7 @@ public class SnoozeHelper {
ArrayMap<String, Long> snoozed =
mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg));
if (snoozed != null) {
- time = snoozed.get(key);
+ time = snoozed.get(getTrimmedString(key));
}
}
if (time == null) {
@@ -166,7 +169,7 @@ public class SnoozeHelper {
ArrayMap<String, String> snoozed =
mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg));
if (snoozed != null) {
- return snoozed.get(key);
+ return snoozed.get(getTrimmedString(key));
}
}
return null;
@@ -251,7 +254,8 @@ public class SnoozeHelper {
scheduleRepost(pkg, key, userId, duration);
Long activateAt = System.currentTimeMillis() + duration;
synchronized (mLock) {
- storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
+ storeRecordLocked(pkg, getTrimmedString(key), userId, mPersistedSnoozedNotifications,
+ activateAt);
}
}
@@ -262,8 +266,10 @@ public class SnoozeHelper {
int userId = record.getUser().getIdentifier();
if (contextId != null) {
synchronized (mLock) {
- storeRecordLocked(record.getSbn().getPackageName(), record.getKey(),
- userId, mPersistedSnoozedNotificationsWithContext, contextId);
+ storeRecordLocked(record.getSbn().getPackageName(),
+ getTrimmedString(record.getKey()),
+ userId, mPersistedSnoozedNotificationsWithContext,
+ getTrimmedString(contextId));
}
}
snooze(record);
@@ -280,6 +286,13 @@ public class SnoozeHelper {
}
}
+ private String getTrimmedString(String key) {
+ if (key != null && key.length() > MAX_STRING_LENGTH) {
+ return key.substring(0, MAX_STRING_LENGTH);
+ }
+ return key;
+ }
+
private <T> void storeRecordLocked(String pkg, String key, Integer userId,
ArrayMap<String, ArrayMap<String, T>> targets, T object) {
@@ -384,12 +397,14 @@ public class SnoozeHelper {
}
protected void repost(String key, int userId, boolean muteOnReturn) {
+ final String trimmedKey = getTrimmedString(key);
+
NotificationRecord record;
synchronized (mLock) {
final String pkg = mPackages.remove(key);
mUsers.remove(key);
- removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications);
- removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
+ removeRecordLocked(pkg, trimmedKey, userId, mPersistedSnoozedNotifications);
+ removeRecordLocked(pkg, trimmedKey, userId, mPersistedSnoozedNotificationsWithContext);
ArrayMap<String, NotificationRecord> records =
mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (records == null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 3506fd9be15d..e40075371910 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -405,6 +405,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@NonNull List<ShortcutInfo> changedShortcuts) {
Preconditions.checkArgument(newShortcut.isEnabled(),
"pushDynamicShortcuts() cannot publish disabled shortcuts");
+ ensureShortcutCountBeforePush();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
@@ -412,7 +413,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
boolean deleted = false;
- if (oldShortcut == null) {
+ if (oldShortcut == null || !oldShortcut.isDynamic()) {
final ShortcutService service = mShortcutUser.mService;
final int maxShortcuts = service.getMaxActivityShortcuts();
@@ -422,7 +423,6 @@ class ShortcutPackage extends ShortcutPackageItem {
if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) {
// Max has reached. Delete the shortcut with lowest rank.
-
// Sort by isManifestShortcut() and getRank().
Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator);
@@ -437,7 +437,8 @@ class ShortcutPackage extends ShortcutPackageItem {
changedShortcuts.add(shortcut);
deleted = deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true) != null;
}
- } else {
+ }
+ if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
@@ -463,6 +464,32 @@ class ShortcutPackage extends ShortcutPackageItem {
return deleted;
}
+ private void ensureShortcutCountBeforePush() {
+ final ShortcutService service = mShortcutUser.mService;
+ // Ensure the total number of shortcuts doesn't exceed the hard limit per app.
+ final int maxShortcutPerApp = service.getMaxAppShortcuts();
+ synchronized (mLock) {
+ final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
+ !si.isPinned()).collect(Collectors.toList());
+ if (appShortcuts.size() >= maxShortcutPerApp) {
+ // Max has reached. Removes shortcuts until they fall within the hard cap.
+ // Sort by isManifestShortcut(), isDynamic() and getLastChangedTimestamp().
+ Collections.sort(appShortcuts, mShortcutTypeRankAndTimeComparator);
+
+ while (appShortcuts.size() >= maxShortcutPerApp) {
+ final ShortcutInfo shortcut = appShortcuts.remove(appShortcuts.size() - 1);
+ if (shortcut.isDeclaredInManifest()) {
+ // All shortcuts are manifest shortcuts and cannot be removed.
+ throw new IllegalArgumentException(getPackageName() + " has published "
+ + appShortcuts.size() + " manifest shortcuts across different"
+ + " activities.");
+ }
+ forceDeleteShortcutInner(shortcut.getId());
+ }
+ }
+ }
+ }
+
/**
* Remove all shortcuts that aren't pinned, cached nor dynamic.
*
@@ -1369,6 +1396,61 @@ class ShortcutPackage extends ShortcutPackageItem {
};
/**
+ * To sort by isManifestShortcut(), isDynamic(), getRank() and
+ * getLastChangedTimestamp(). i.e. manifest shortcuts come before non-manifest shortcuts,
+ * dynamic shortcuts come before floating shortcuts, then sort by last changed timestamp.
+ *
+ * This is used to decide which shortcuts to remove when the total number of shortcuts retained
+ * for the app exceeds the limit defined in {@link ShortcutService#getMaxAppShortcuts()}.
+ *
+ * (Note the number of manifest shortcuts is always <= the max number, because if there are
+ * more, ShortcutParser would ignore the rest.)
+ */
+ final Comparator<ShortcutInfo> mShortcutTypeRankAndTimeComparator = (ShortcutInfo a,
+ ShortcutInfo b) -> {
+ if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
+ return -1;
+ }
+ if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
+ return 1;
+ }
+ if (a.isDynamic() && b.isDynamic()) {
+ return Integer.compare(a.getRank(), b.getRank());
+ }
+ if (a.isDynamic()) {
+ return -1;
+ }
+ if (b.isDynamic()) {
+ return 1;
+ }
+ if (a.isCached() && b.isCached()) {
+ // if both shortcuts are cached, prioritize shortcuts cached by people tile,
+ if (a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
+ && !b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
+ return -1;
+ } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
+ && b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
+ return 1;
+ }
+ // followed by bubbles.
+ if (a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
+ && !b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
+ return -1;
+ } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
+ && b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
+ return 1;
+ }
+ }
+ if (a.isCached()) {
+ return -1;
+ }
+ if (b.isCached()) {
+ return 1;
+ }
+ return Long.compare(b.getLastChangedTimestamp(), a.getLastChangedTimestamp());
+ };
+
+ /**
* Build a list of shortcuts for each target activity and return as a map. The result won't
* contain "floating" shortcuts because they don't belong on any activities.
*/
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 62d6717e847a..940eb345c06d 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -180,6 +180,9 @@ public class ShortcutService extends IShortcutService.Stub {
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
+ static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100;
+
+ @VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@VisibleForTesting
@@ -254,6 +257,11 @@ public class ShortcutService extends IShortcutService.Stub {
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
+ * Key name for the max shortcuts can be retained in system ram per app. (int)
+ */
+ String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+ /**
* Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -325,11 +333,16 @@ public class ShortcutService extends IShortcutService.Stub {
new SparseArray<>();
/**
- * Max number of dynamic + manifest shortcuts that each application can have at a time.
+ * Max number of dynamic + manifest shortcuts that each activity can have at a time.
*/
private int mMaxShortcuts;
/**
+ * Max number of shortcuts that can exists in system ram for each application.
+ */
+ private int mMaxShortcutsPerApp;
+
+ /**
* Max number of updating API calls that each application can make during the interval.
*/
int mMaxUpdatesPerInterval;
@@ -790,6 +803,9 @@ public class ShortcutService extends IShortcutService.Stub {
mMaxShortcuts = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
+ mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+ ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1758,6 +1774,13 @@ public class ShortcutService extends IShortcutService.Stub {
}
/**
+ * Return the max number of shortcuts can be retaiend in system ram for each application.
+ */
+ int getMaxAppShortcuts() {
+ return mMaxShortcutsPerApp;
+ }
+
+ /**
* - Sends a notification to LauncherApps
* - Write to file
*/
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e4c63fd220a7..1e63dd51f336 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1296,6 +1296,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
true /* ignoreVisibility */);
+ if (inPictureInPictureMode && findMainWindow() == null) {
+ // Prevent malicious app entering PiP without valid WindowState, which can in turn
+ // result a non-touchable PiP window since the InputConsumer for PiP requires it.
+ EventLog.writeEvent(0x534e4554, "265293293", -1, "");
+ removeImmediately();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 95b5cec9a144..95a6e8b8b88f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -86,6 +86,10 @@ class ActivityRecordInputSink {
mInputWindowHandle.layoutParamsFlags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
changed |= notTouchable;
}
+ if (mInputWindowHandle.displayId != mActivityRecord.getDisplayId()) {
+ mInputWindowHandle.displayId = mActivityRecord.getDisplayId();
+ changed = true;
+ }
return changed;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 8bead5774548..883613f6a2b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -69,6 +69,7 @@ import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Collections;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -248,6 +249,37 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
+ public void testLongTagPersistedNotification() throws Exception {
+ String longTag = String.join("", Collections.nCopies(66000, "A"));
+ NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 0);
+
+ // We store the full key in temp storage.
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser, 4);
+
+ mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+ // We trim the key in persistent storage.
+ verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+ }
+
+ @Test
public void testSnoozeForTime() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
@@ -595,13 +627,20 @@ public class SnoozeHelperTest extends UiServiceTestCase {
public void testClearData() {
// snooze 2 from same package
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
- NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2,
+ "two" + String.join("", Collections.nCopies(66000, "2")), UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
mSnoozeHelper.snooze(r2, 1000);
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
+ assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r.getUser().getIdentifier(), r.getSbn().getPackageName(),
+ r.getSbn().getKey()));
+ assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r2.getUser().getIdentifier(), r2.getSbn().getPackageName(),
+ r2.getSbn().getKey()));
// clear data
mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index e332d3ff2b4d..808d032f5d66 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -517,6 +517,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Sets the address. See {@link PhoneAccount#getAddress}.
+ * <p>
+ * Note: The entire URI value is limited to 256 characters. This check is
+ * enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if URI is over 256.
*
* @param value The address of the phone account.
* @return The builder.
@@ -550,6 +555,10 @@ public final class PhoneAccount implements Parcelable {
/**
* Sets the icon. See {@link PhoneAccount#getIcon}.
+ * <p>
+ * Note: An {@link IllegalArgumentException} if the Icon cannot be written to memory.
+ * This check is enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}
*
* @param icon The icon to set.
*/
@@ -583,6 +592,10 @@ public final class PhoneAccount implements Parcelable {
/**
* Specifies an additional URI scheme supported by the {@link PhoneAccount}.
*
+ * <p>
+ * Each URI scheme is limited to 256 characters. Adding a scheme over 256 characters will
+ * cause an {@link IllegalArgumentException} to be thrown when the account is registered.
+ *
* @param uriScheme The URI scheme.
* @return The builder.
*/
@@ -596,6 +609,12 @@ public final class PhoneAccount implements Parcelable {
/**
* Specifies the URI schemes supported by the {@link PhoneAccount}.
*
+ * <p>
+ * A max of 10 URI schemes can be added per account. Additionally, each URI scheme is
+ * limited to 256 characters. Adding more than 10 URI schemes or 256 characters on any
+ * scheme will cause an {@link IllegalArgumentException} to be thrown when the account
+ * is registered.
+ *
* @param uriSchemes The URI schemes.
* @return The builder.
*/