diff --git a/Android.bp b/Android.bp index fc64efd93234..ad0e6ca2c3d4 100644 --- a/Android.bp +++ b/Android.bp @@ -255,6 +255,7 @@ filegroup { ":framework_native_aidl", ":gatekeeper_aidl", ":gsiservice_aidl", + ":guiconstants_aidl", ":incidentcompanion_aidl", ":installd_aidl", ":keystore_aidl", diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index e15f0f37fc62..0e98bf0e8e00 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -146,6 +146,8 @@ void restrictApp(@NonNull String packageName, int userId, void setActiveAdminApps(Set adminPkgs, int userId); + void setAdminProtectedPackages(Set packageNames, int userId); + void onAdminDataAvailable(); void clearCarrierPrivilegedApps(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 2f5f555817ec..146d68fdd653 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -687,6 +687,10 @@ public void run() { } } catch (XmlPullParserException | IOException e) { Slog.wtf(TAG, "Error jobstore xml.", e); + } catch (Exception e) { + // Crashing at this point would result in a boot loop, so live with a general + // Exception for system stability's sake. + Slog.wtf(TAG, "Unexpected exception", e); } finally { if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. mPersistInfo.countAllJobsLoaded = numJobs; @@ -817,6 +821,15 @@ private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser) } catch (NumberFormatException e) { Slog.d(TAG, "Error reading constraints, skipping."); return null; + } catch (XmlPullParserException e) { + Slog.d(TAG, "Error Parser Exception.", e); + return null; + } catch (IOException e) { + Slog.d(TAG, "Error I/O Exception.", e); + return null; + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Constraints contained invalid data", e); + return null; } parser.next(); // Consume @@ -912,8 +925,14 @@ private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser) return null; } - PersistableBundle extras = PersistableBundle.restoreFromXml(parser); - jobBuilder.setExtras(extras); + final PersistableBundle extras; + try { + extras = PersistableBundle.restoreFromXml(parser); + jobBuilder.setExtras(extras); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Persisted extras contained invalid data", e); + return null; + } parser.nextTag(); // Consume final JobInfo builtJob; @@ -959,7 +978,8 @@ private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberF return new JobInfo.Builder(jobId, cname); } - private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) { + private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) + throws XmlPullParserException, IOException { String val; final String netCapabilities = parser.getAttributeValue(null, "net-capabilities"); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 36ccaf9c6fb8..403e8b5547bd 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -233,6 +233,10 @@ static class Lock {} @GuardedBy("mActiveAdminApps") private final SparseArray> mActiveAdminApps = new SparseArray<>(); + /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */ + @GuardedBy("mAdminProtectedPackages") + private final SparseArray> mAdminProtectedPackages = new SparseArray<>(); + /** * Set of system apps that are headless (don't have any declared activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. @@ -1019,6 +1023,9 @@ public void onUserRemoved(int userId) { synchronized (mActiveAdminApps) { mActiveAdminApps.remove(userId); } + synchronized (mAdminProtectedPackages) { + mAdminProtectedPackages.remove(userId); + } } } @@ -1108,6 +1115,10 @@ private int getAppMinBucket(String packageName, int appId, int userId) { return STANDBY_BUCKET_EXEMPTED; } + if (isAdminProtectedPackages(packageName, userId)) { + return STANDBY_BUCKET_EXEMPTED; + } + if (isActiveNetworkScorer(packageName)) { return STANDBY_BUCKET_EXEMPTED; } @@ -1510,6 +1521,17 @@ boolean isActiveDeviceAdmin(String packageName, int userId) { } } + private boolean isAdminProtectedPackages(String packageName, int userId) { + synchronized (mAdminProtectedPackages) { + if (mAdminProtectedPackages.contains(UserHandle.USER_ALL) + && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) { + return true; + } + return mAdminProtectedPackages.contains(userId) + && mAdminProtectedPackages.get(userId).contains(packageName); + } + } + @Override public void addActiveDeviceAdmin(String adminPkg, int userId) { synchronized (mActiveAdminApps) { @@ -1533,6 +1555,17 @@ public void setActiveAdminApps(Set adminPkgs, int userId) { } } + @Override + public void setAdminProtectedPackages(Set packageNames, int userId) { + synchronized (mAdminProtectedPackages) { + if (packageNames == null || packageNames.isEmpty()) { + mAdminProtectedPackages.remove(userId); + } else { + mAdminProtectedPackages.put(userId, packageNames); + } + } + } + @Override public void onAdminDataAvailable() { mAdminDataAvailableLatch.countDown(); @@ -1555,6 +1588,13 @@ Set getActiveAdminAppsForTest(int userId) { } } + @VisibleForTesting + Set getAdminProtectedPackagesForTest(int userId) { + synchronized (mAdminProtectedPackages) { + return mAdminProtectedPackages.get(userId); + } + } + /** * Returns {@code true} if the supplied package is the device provisioning app. Otherwise, * returns {@code false}. diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 13bf197aa9dc..421301adbff0 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -500,9 +500,13 @@ status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel* switch (code) { case SHELL_COMMAND_TRANSACTION: { - int in = data.readFileDescriptor(); - int out = data.readFileDescriptor(); - int err = data.readFileDescriptor(); + unique_fd in, out, err; + if (status_t status = data.readUniqueFileDescriptor(&in); status != OK) return status; + + if (status_t status = data.readUniqueFileDescriptor(&out); status != OK) return status; + + if (status_t status = data.readUniqueFileDescriptor(&err); status != OK) return status; + int argc = data.readInt32(); Vector args; for (int i = 0; i < argc && data.dataAvail() > 0; i++) { @@ -512,15 +516,15 @@ status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel* sp resultReceiver = IResultReceiver::asInterface(data.readStrongBinder()); - FILE* fin = fdopen(in, "r"); - FILE* fout = fdopen(out, "w"); - FILE* ferr = fdopen(err, "w"); + FILE* fin = fdopen(in.release(), "r"); + FILE* fout = fdopen(out.release(), "w"); + FILE* ferr = fdopen(err.release(), "w"); if (fin == NULL || fout == NULL || ferr == NULL) { resultReceiver->send(NO_MEMORY); } else { - err = command(fin, fout, ferr, args); - resultReceiver->send(err); + status_t result = command(fin, fout, ferr, args); + resultReceiver->send(result); } if (fin != NULL) { diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 6f952f637506..8ec668a6c5b5 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -163,12 +163,15 @@ StatsService::StatsService(const sp& handlerLooper, shared_ptr([this] { readLogs(); }); } } StatsService::~StatsService() { + if (mEventQueue != nullptr) { + stopReadingLogs(); + mLogsReaderThread->join(); + } } /* Runs on a dedicated thread to process pushed events. */ @@ -177,6 +180,13 @@ void StatsService::readLogs() { while (1) { // Block until an event is available. auto event = mEventQueue->waitPop(); + + // Below flag will be set when statsd is exiting and log event will be pushed to break + // out of waitPop. + if (mIsStopRequested) { + break; + } + // Pass it to StatsLogProcess to all configs/metrics // At this point, the LogEventQueue is not blocked, so that the socketListener // can read events from the socket and write to buffer to avoid data drop. @@ -1335,6 +1345,15 @@ void StatsService::statsCompanionServiceDiedImpl() { mPullerManager->SetStatsCompanionService(nullptr); } +void StatsService::stopReadingLogs() { + mIsStopRequested = true; + // Push this event so that readLogs will process and break out of the loop + // after the stop is requested. + int64_t timeStamp; + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + mEventQueue->push(std::move(logEvent), &timeStamp); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index b49fa1d42e66..a12d82eedca9 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -338,6 +338,13 @@ class StatsService : public BnStatsd { */ void statsCompanionServiceDiedImpl(); + /* + * This method is used to stop log reader thread. + */ + void stopReadingLogs(); + + std::atomic mIsStopRequested = false; + /** * Tracks the uid <--> package name mapping. */ @@ -380,6 +387,7 @@ class StatsService : public BnStatsd { */ mutable mutex mShellSubscriberMutex; std::shared_ptr mEventQueue; + std::unique_ptr mLogsReaderThread; MultiConditionTrigger mBootCompleteTrigger; static const inline string kBootCompleteTag = "BOOT_COMPLETE"; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index ac00a042b79e..e52b9c177dc1 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1982,6 +1982,10 @@ private void sendServiceInfo() { IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); if (mInfo != null && connection != null) { + if (!mInfo.isWithinParcelableSize()) { + throw new IllegalStateException( + "Cannot update service info: size is larger than safe parcelable limits."); + } try { connection.setServiceInfo(mInfo); mInfo = null; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index a41fa6431d4d..f853dff01e8a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -39,6 +39,7 @@ import android.graphics.drawable.Drawable; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -1028,6 +1029,15 @@ public int describeContents() { return 0; } + /** @hide */ + public final boolean isWithinParcelableSize() { + final Parcel parcel = Parcel.obtain(); + writeToParcel(parcel, 0); + final boolean result = parcel.dataSize() <= IBinder.MAX_IPC_SIZE; + parcel.recycle(); + return result; + } + public void writeToParcel(Parcel parcel, int flagz) { parcel.writeInt(eventTypes); parcel.writeStringArray(packageNames); diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java index 9a18880a353b..965b6e0a02cd 100644 --- a/core/java/android/accounts/Account.java +++ b/core/java/android/accounts/Account.java @@ -30,6 +30,7 @@ import com.android.internal.annotations.GuardedBy; +import java.util.Objects; import java.util.Set; /** @@ -85,6 +86,12 @@ public Account(String name, String type, String accessId) { if (TextUtils.isEmpty(type)) { throw new IllegalArgumentException("the type must not be empty: " + type); } + if (name.length() > 200) { + throw new IllegalArgumentException("account name is longer than 200 characters"); + } + if (type.length() > 200) { + throw new IllegalArgumentException("account type is longer than 200 characters"); + } this.name = name; this.type = type; this.accessId = accessId; diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 8cdc6a71caf8..adfe171f1a8d 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -407,7 +407,7 @@ public void run(final AccountManagerFuture accountManagerFuture) { mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage, mCallingUid); intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityForResult(intent, REQUEST_ADD_ACCOUNT); + startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT); return; } } catch (OperationCanceledException e) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1059a6f2e868..811cd5b75275 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3933,6 +3933,9 @@ public void restartPackage(String packageName) { * processes to reclaim memory; the system will take care of restarting * these processes in the future as needed. * + *

Third party applications can only use this API to kill their own processes. + *

+ * * @param packageName The name of the package whose processes are to * be killed. */ diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4aedfeefb72b..b8413c5aafcc 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,6 +20,8 @@ import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; @@ -61,7 +63,7 @@ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) * Context.startActivity(Intent, Bundle)} and related methods. */ -public class ActivityOptions { +public class ActivityOptions extends ComponentOptions { private static final String TAG = "ActivityOptions"; /** @@ -963,13 +965,12 @@ public boolean getLaunchTaskBehind() { } private ActivityOptions() { + super(); } /** @hide */ public ActivityOptions(Bundle opts) { - // If the remote side sent us bad parcelables, they won't get the - // results they want, which is their loss. - opts.setDefusable(true); + super(opts); mPackageName = opts.getString(KEY_PACKAGE_NAME); try { @@ -1373,7 +1374,9 @@ public void setPendingIntentLaunchFlags(@android.content.Intent.Flags int flags) * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** @@ -1575,8 +1578,9 @@ public void update(ActivityOptions otherOptions) { * object; you must not modify it, but can supply it to the startActivity * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (mPackageName != null) { b.putString(KEY_PACKAGE_NAME, mPackageName); } diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 7180c01143a5..9a92515ee794 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -45,6 +45,14 @@ public final class AutomaticZenRule implements Parcelable { private long creationTime; private ZenPolicy mZenPolicy; private boolean mModified = false; + private String mPkg; + + /** + * The maximum string length for any string contained in this automatic zen rule. This pertains + * both to fields in the rule itself (such as its name) and items with sub-fields. + * @hide + */ + public static final int MAX_STRING_LENGTH = 1000; /** * Creates an automatic zen rule. @@ -92,10 +100,10 @@ public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner, @Nullable ComponentName configurationActivity, @NonNull Uri conditionId, @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) { - this.name = name; - this.owner = owner; - this.configurationActivity = configurationActivity; - this.conditionId = conditionId; + this.name = getTrimmedString(name); + this.owner = getTrimmedComponentName(owner); + this.configurationActivity = getTrimmedComponentName(configurationActivity); + this.conditionId = getTrimmedUri(conditionId); this.interruptionFilter = interruptionFilter; this.enabled = enabled; this.mZenPolicy = policy; @@ -114,15 +122,16 @@ public AutomaticZenRule(String name, ComponentName owner, ComponentName configur public AutomaticZenRule(Parcel source) { enabled = source.readInt() == ENABLED; if (source.readInt() == ENABLED) { - name = source.readString(); + name = getTrimmedString(source.readString()); } interruptionFilter = source.readInt(); - conditionId = source.readParcelable(null); - owner = source.readParcelable(null); - configurationActivity = source.readParcelable(null); + conditionId = getTrimmedUri(source.readParcelable(null)); + owner = getTrimmedComponentName(source.readParcelable(null)); + configurationActivity = getTrimmedComponentName(source.readParcelable(null)); creationTime = source.readLong(); mZenPolicy = source.readParcelable(null); mModified = source.readInt() == ENABLED; + mPkg = source.readString(); } /** @@ -194,7 +203,7 @@ public long getCreationTime() { * Sets the representation of the state that causes this rule to become active. */ public void setConditionId(Uri conditionId) { - this.conditionId = conditionId; + this.conditionId = getTrimmedUri(conditionId); } /** @@ -209,7 +218,7 @@ public void setInterruptionFilter(@InterruptionFilter int interruptionFilter) { * Sets the name of this rule. */ public void setName(String name) { - this.name = name; + this.name = getTrimmedString(name); } /** @@ -241,7 +250,21 @@ public void setZenPolicy(ZenPolicy zenPolicy) { * that are not backed by {@link android.service.notification.ConditionProviderService}. */ public void setConfigurationActivity(@Nullable ComponentName componentName) { - this.configurationActivity = componentName; + this.configurationActivity = getTrimmedComponentName(componentName); + } + + /** + * @hide + */ + public void setPackageName(String pkgName) { + mPkg = pkgName; + } + + /** + * @hide + */ + public String getPackageName() { + return mPkg; } @Override @@ -265,6 +288,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); dest.writeInt(mModified ? ENABLED : DISABLED); + dest.writeString(mPkg); } @Override @@ -273,6 +297,7 @@ public String toString() { .append("enabled=").append(enabled) .append(",name=").append(name) .append(",interruptionFilter=").append(interruptionFilter) + .append(",pkg=").append(mPkg) .append(",conditionId=").append(conditionId) .append(",owner=").append(owner) .append(",configActivity=").append(configurationActivity) @@ -294,13 +319,14 @@ public boolean equals(Object o) { && Objects.equals(other.owner, owner) && Objects.equals(other.mZenPolicy, mZenPolicy) && Objects.equals(other.configurationActivity, configurationActivity) + && Objects.equals(other.mPkg, mPkg) && other.creationTime == creationTime; } @Override public int hashCode() { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime); + configurationActivity, mZenPolicy, mModified, creationTime, mPkg); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR @@ -314,4 +340,35 @@ public AutomaticZenRule[] newArray(int size) { return new AutomaticZenRule[size]; } }; + + /** + * If the package or class name of the provided ComponentName are longer than MAX_STRING_LENGTH, + * return a trimmed version that truncates each of the package and class name at the max length. + */ + private static ComponentName getTrimmedComponentName(ComponentName cn) { + if (cn == null) return null; + return new ComponentName(getTrimmedString(cn.getPackageName()), + getTrimmedString(cn.getClassName())); + } + + /** + * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. + */ + private static String getTrimmedString(String input) { + if (input != null && input.length() > MAX_STRING_LENGTH) { + return input.substring(0, MAX_STRING_LENGTH); + } + return input; + } + + /** + * Returns a truncated copy of the Uri by trimming the string representation to the maximum + * string length. + */ + private static Uri getTrimmedUri(Uri input) { + if (input != null && input.toString().length() > MAX_STRING_LENGTH) { + return Uri.parse(getTrimmedString(input.toString())); + } + return input; + } } diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 161e2ad06ec0..8947fb44b07b 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -28,7 +28,7 @@ * {@hide} */ @SystemApi -public class BroadcastOptions { +public class BroadcastOptions extends ComponentOptions { private long mTemporaryAppWhitelistDuration; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; @@ -72,10 +72,12 @@ public static BroadcastOptions makeBasic() { } private BroadcastOptions() { + super(); } /** @hide */ public BroadcastOptions(Bundle opts) { + super(opts); mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, @@ -173,6 +175,24 @@ public boolean allowsBackgroundActivityStarts() { return mAllowBackgroundActivityStarts; } + /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + super.setPendingIntentBackgroundActivityLaunchAllowed(allowed); + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return super.isPendingIntentBackgroundActivityLaunchAllowed(); + } + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) @@ -181,8 +201,9 @@ public boolean allowsBackgroundActivityStarts() { * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (mTemporaryAppWhitelistDuration > 0) { b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); } diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java new file mode 100644 index 000000000000..34ee9138a364 --- /dev/null +++ b/core/java/android/app/ComponentOptions.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.Bundle; + +/** + * @hide + */ +public class ComponentOptions { + + /** + * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED. + * @hide + **/ + public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true; + + /** + * PendingIntent caller allows activity start even if PendingIntent creator is in background. + * This only works if the PendingIntent caller is allowed to start background activities, + * for example if it's in the foreground, or has BAL permission. + * @hide + */ + public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED = + "android.pendingIntent.backgroundActivityAllowed"; + + private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT; + + ComponentOptions() { + } + + ComponentOptions(Bundle opts) { + // If the remote side sent us bad parcelables, they won't get the + // results they want, which is their loss. + opts.setDefusable(true); + setPendingIntentBackgroundActivityLaunchAllowed( + opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + PENDING_INTENT_BAL_ALLOWED_DEFAULT)); + } + + /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + mPendingIntentBalAllowed = allowed; + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return mPendingIntentBalAllowed; + } + + /** + * @hide + */ + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + mPendingIntentBalAllowed); + return bundle; + } +} diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index a5f987adb1a7..863fe4d5f6b1 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -203,7 +203,7 @@ interface INotificationManager void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); List getZenRules(); - String addAutomaticZenRule(in AutomaticZenRule automaticZenRule); + String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); boolean removeAutomaticZenRule(String id); boolean removeAutomaticZenRules(String packageName); diff --git a/core/java/android/app/IUriGrantsManager.aidl b/core/java/android/app/IUriGrantsManager.aidl index 9e7f2fecfea0..b630d034dca9 100644 --- a/core/java/android/app/IUriGrantsManager.aidl +++ b/core/java/android/app/IUriGrantsManager.aidl @@ -39,4 +39,7 @@ interface IUriGrantsManager { void clearGrantedUriPermissions(in String packageName, int userId); ParceledListSlice getUriPermissions(in String packageName, boolean incoming, boolean persistedOnly); + + int checkGrantUriPermission_ignoreNonSystem( + int sourceUid, String targetPkg, in Uri uri, int modeFlags, int userId); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 132afabe82d1..6a59329d4d42 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1831,6 +1831,10 @@ public Action build() { } } + private void visitUris(@NonNull Consumer visitor) { + visitIconUri(visitor, getIcon()); + } + @Override public Action clone() { return new Action( @@ -2476,6 +2480,14 @@ public void cloneInto(Notification that, boolean heavy) { } } + private static void visitIconUri(@NonNull Consumer visitor, @Nullable Icon icon) { + if (icon == null) return; + final int iconType = icon.getType(); + if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { + visitor.accept(icon.getUri()); + } + } + /** * Note all {@link Uri} that are referenced internally, with the expectation * that Uri permission grants will need to be issued to ensure the recipient @@ -2484,6 +2496,10 @@ public void cloneInto(Notification that, boolean heavy) { * @hide */ public void visitUris(@NonNull Consumer visitor) { + if (publicVersion != null) { + publicVersion.visitUris(visitor); + } + visitor.accept(sound); if (tickerView != null) tickerView.visitUris(visitor); @@ -2491,7 +2507,18 @@ public void visitUris(@NonNull Consumer visitor) { if (bigContentView != null) bigContentView.visitUris(visitor); if (headsUpContentView != null) headsUpContentView.visitUris(visitor); + visitIconUri(visitor, mSmallIcon); + visitIconUri(visitor, mLargeIcon); + + if (actions != null) { + for (Action action : actions) { + action.visitUris(visitor); + } + } + if (extras != null) { + visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG)); + visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI)); if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); @@ -2506,13 +2533,24 @@ public void visitUris(@NonNull Consumer visitor) { } } + final RemoteInputHistoryItem[] history = getParcelableArrayFromBundle(extras, + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } + + // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since + // Notification Listeners might use directly (without the isStyle check). final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON); if (person != null && person.getIconUri() != null) { visitor.accept(person.getIconUri()); } - } - if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) { final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message message : MessagingStyle.Message @@ -2538,14 +2576,17 @@ public void visitUris(@NonNull Consumer visitor) { } } } + + visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON)); } - if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) { - final Icon icon = mBubbleMetadata.getIcon(); - final int iconType = icon.getType(); - if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) { - visitor.accept(icon.getUri()); - } + if (mBubbleMetadata != null) { + visitIconUri(visitor, mBubbleMetadata.getIcon()); + } + + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); } } @@ -3037,8 +3078,11 @@ public void setLatestEventInfo(Context context, * * @hide */ - public void setAllowlistToken(@Nullable IBinder token) { - mWhitelistToken = token; + public void clearAllowlistToken() { + mWhitelistToken = null; + if (publicVersion != null) { + publicVersion.clearAllowlistToken(); + } } /** @@ -10256,6 +10300,12 @@ private void setFlag(int mask, boolean value) { mFlags &= ~mask; } } + + private void visitUris(@NonNull Consumer visitor) { + for (Action action : mActions) { + action.visitUris(visitor); + } + } } /** diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index cf2f7690bc2c..cd46b39574d2 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -78,8 +78,13 @@ public final class NotificationChannel implements Parcelable { /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this * limit. + * @hide */ - private static final int MAX_TEXT_LENGTH = 1000; + public static final int MAX_TEXT_LENGTH = 1000; + /** + * @hide + */ + public static final int MAX_VIBRATION_LENGTH = 1000; private static final String TAG_CHANNEL = "channel"; private static final String ATT_NAME = "name"; @@ -236,17 +241,17 @@ public NotificationChannel(String id, CharSequence name, @Importance int importa */ protected NotificationChannel(Parcel in) { if (in.readByte() != 0) { - mId = in.readString(); + mId = getTrimmedString(in.readString()); } else { mId = null; } if (in.readByte() != 0) { - mName = in.readString(); + mName = getTrimmedString(in.readString()); } else { mName = null; } if (in.readByte() != 0) { - mDesc = in.readString(); + mDesc = getTrimmedString(in.readString()); } else { mDesc = null; } @@ -255,18 +260,22 @@ protected NotificationChannel(Parcel in) { mLockscreenVisibility = in.readInt(); if (in.readByte() != 0) { mSound = Uri.CREATOR.createFromParcel(in); + mSound = Uri.parse(getTrimmedString(mSound.toString())); } else { mSound = null; } mLights = in.readByte() != 0; mVibration = in.createLongArray(); + if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) { + mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH); + } mUserLockedFields = in.readInt(); mFgServiceShown = in.readByte() != 0; mVibrationEnabled = in.readByte() != 0; mShowBadge = in.readByte() != 0; mDeleted = in.readByte() != 0; if (in.readByte() != 0) { - mGroup = in.readString(); + mGroup = getTrimmedString(in.readString()); } else { mGroup = null; } @@ -276,8 +285,8 @@ protected NotificationChannel(Parcel in) { mAllowBubbles = in.readInt(); mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); - mParentId = in.readString(); - mConversationId = in.readString(); + mParentId = getTrimmedString(in.readString()); + mConversationId = getTrimmedString(in.readString()); mDemoted = in.readBoolean(); mImportantConvo = in.readBoolean(); } diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 403fb3e3727c..07802a220015 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -42,8 +42,9 @@ public final class NotificationChannelGroup implements Parcelable { /** * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at * this limit. + * @hide */ - private static final int MAX_TEXT_LENGTH = 1000; + public static final int MAX_TEXT_LENGTH = 1000; private static final String TAG_GROUP = "channelGroup"; private static final String ATT_NAME = "name"; @@ -89,13 +90,17 @@ public NotificationChannelGroup(String id, CharSequence name) { */ protected NotificationChannelGroup(Parcel in) { if (in.readByte() != 0) { - mId = in.readString(); + mId = getTrimmedString(in.readString()); } else { mId = null; } - mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); if (in.readByte() != 0) { - mDescription = in.readString(); + mName = getTrimmedString(in.readString()); + } else { + mName = ""; + } + if (in.readByte() != 0) { + mDescription = getTrimmedString(in.readString()); } else { mDescription = null; } @@ -119,7 +124,12 @@ public void writeToParcel(Parcel dest, int flags) { } else { dest.writeByte((byte) 0); } - TextUtils.writeToParcel(mName, dest, flags); + if (mName != null) { + dest.writeByte((byte) 1); + dest.writeString(mName.toString()); + } else { + dest.writeByte((byte) 0); + } if (mDescription != null) { dest.writeByte((byte) 1); dest.writeString(mDescription); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index eef9c022fda8..f9e19b5fa7de 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -465,6 +465,12 @@ public class NotificationManager { */ public static final int BUBBLE_PREFERENCE_SELECTED = 2; + /** + * Maximum length of the component name of a registered NotificationListenerService. + * @hide + */ + public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500; + @UnsupportedAppUsage private static INotificationManager sService; @@ -1072,10 +1078,12 @@ public Map getAutomaticZenRules() { List rules = service.getZenRules(); Map ruleMap = new HashMap<>(); for (ZenModeConfig.ZenRule rule : rules) { - ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, rule.conditionId, rule.zenPolicy, zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime)); + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); } return ruleMap; } catch (RemoteException e) { @@ -1116,7 +1124,7 @@ public AutomaticZenRule getAutomaticZenRule(String id) { public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { INotificationManager service = getService(); try { - return service.addAutomaticZenRule(automaticZenRule); + return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ecd0e65b8921..9f0675617d5e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -19,6 +19,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest.permission; +import android.accounts.Account; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.IntDef; @@ -152,6 +153,27 @@ public DevicePolicyManager(Context context, IDevicePolicyManager service) { this(context, service, false); } + /** + * Called when a managed profile has been provisioned. + * + * @throws SecurityException if the caller does not hold + * {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void finalizeWorkProfileProvisioning( + @NonNull UserHandle managedProfileUser, @Nullable Account migratedAccount) { + Objects.requireNonNull(managedProfileUser, "managedProfileUser can't be null"); + if (mService == null) { + throw new IllegalStateException("Could not find DevicePolicyManagerService"); + } + try { + mService.finalizeWorkProfileProvisioning(managedProfileUser, migratedAccount); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ @VisibleForTesting protected DevicePolicyManager(Context context, IDevicePolicyManager service, @@ -9639,6 +9661,15 @@ public int getPermissionPolicy(ComponentName admin) { * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted. * + * Control over the following permissions are restricted for managed profile owners: + *
    + *
  • Manifest.permission.READ_SMS
  • + *
+ *

+ * A managed profile owner may not grant these permissions (i.e. call this method with any of + * the permissions listed above and {@code grantState} of + * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them. + * * @param admin Which profile or device owner this request is associated with. * @param packageName The application to grant or revoke a permission to. * @param permission The permission to grant or revoke. @@ -9876,7 +9907,8 @@ public CharSequence getShortSupportMessage(@NonNull ComponentName admin) { /** * Called by a device admin to set the long support message. This will be displayed to the user - * in the device administators settings screen. + * in the device administrators settings screen. If the message is longer than 20000 characters + * it may be truncated. *

* If the long support message needs to be localized, it is the responsibility of the * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast @@ -11965,7 +11997,8 @@ public boolean startViewCalendarEventInManagedProfile(long eventId, long start, /** * Called by Device owner to disable user control over apps. User will not be able to clear - * app data or force-stop packages. + * app data or force-stop packages. Packages with user control disabled are exempted from + * App Standby Buckets. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param packages The package names for the apps. diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index d24694faff93..60cb563cca3d 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; @@ -76,6 +77,13 @@ public interface OnCrossProfileWidgetProvidersChangeListener { public abstract void addOnCrossProfileWidgetProvidersChangeListener( OnCrossProfileWidgetProvidersChangeListener listener); + /** + * @param userHandle the handle of the user whose profile owner is being fetched. + * @return the configured supervision app if it exists and is the device owner or policy owner. + */ + public abstract @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle); + /** * Checks if an app with given uid is an active device admin of its user and has the policy * specified. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 51ca730138e8..fc8c7a15c58c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,7 @@ package android.app.admin; +import android.accounts.Account; import android.app.admin.NetworkEvent; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -91,6 +92,8 @@ interface IDevicePolicyManager { int getCurrentFailedPasswordAttempts(int userHandle, boolean parent); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent); + void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount); + void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, boolean parent); int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle, boolean parent); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 3fef92b203b6..e2351ee89c42 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -28,6 +28,7 @@ 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.Rect; import android.os.Bundle; @@ -250,19 +251,26 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto 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 @@ -725,4 +733,15 @@ private OnClickHandler getHandler(OnClickHandler handler) { } }; } + + @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); + post(this::handleViewError); + } + } } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 009ec522e436..287331aa6d6e 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1109,7 +1109,9 @@ public void setBindAppWidgetPermission(String packageName, int userId, boolean p * @param intent The intent of the service which will be providing the data to the * RemoteViewsAdapter. * @param connection The callback interface to be notified when a connection is made or lost. - * @param flags Flags used for binding to the service + * @param flags Flags used for binding to the service. Currently only + * {@link Context#BIND_AUTO_CREATE} and + * {@link Context#BIND_FOREGROUND_SERVICE_WHILE_AWAKE} are supported. * * @see Context#getServiceDispatcher(ServiceConnection, Handler, int) * @hide diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 4c595a9cb140..b1c7d6eadbba 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1230,7 +1230,10 @@ public String getAlias() { if (alias == null) { return getName(); } - return alias; + return alias + .replace('\t', ' ') + .replace('\n', ' ') + .replace('\r', ' '); } catch (RemoteException e) { Log.e(TAG, "", e); } diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java index a086a308d0d9..da4ecdd8c1f2 100644 --- a/core/java/android/content/AbstractThreadedSyncAdapter.java +++ b/core/java/android/content/AbstractThreadedSyncAdapter.java @@ -21,6 +21,7 @@ import android.accounts.Account; import android.annotation.MainThread; import android.annotation.NonNull; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -171,8 +172,20 @@ private Account toSyncKey(Account account) { } private class ISyncAdapterImpl extends ISyncAdapter.Stub { + private boolean isCallerSystem() { + final long callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + android.util.EventLog.writeEvent(0x534e4554, "203229608", -1, ""); + return false; + } + return true; + } + @Override public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) { + if (!isCallerSystem()) { + return; + } Handler.getMain().sendMessage(obtainMessage( AbstractThreadedSyncAdapter::handleOnUnsyncableAccount, AbstractThreadedSyncAdapter.this, cb)); @@ -181,12 +194,16 @@ public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) { @Override public void startSync(ISyncContext syncContext, String authority, Account account, Bundle extras) { + if (!isCallerSystem()) { + return; + } if (ENABLE_LOG) { if (extras != null) { extras.size(); // Unparcel so its toString() will show the contents. } Log.d(TAG, "startSync() start " + authority + " " + account + " " + extras); } + try { final SyncContext syncContextClient = new SyncContext(syncContext); @@ -242,6 +259,9 @@ public void startSync(ISyncContext syncContext, String authority, Account accoun @Override public void cancelSync(ISyncContext syncContext) { + if (!isCallerSystem()) { + return; + } try { // synchronize to make sure that mSyncThreads doesn't change between when we // check it and when we use it diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ededd0d2ea30..24d59a0826c8 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -10750,7 +10750,7 @@ private void toUriFragment(StringBuilder uri, String scheme, String defAction, private void toUriInner(StringBuilder uri, String scheme, String defAction, String defPackage, int flags) { if (scheme != null) { - uri.append("scheme=").append(scheme).append(';'); + uri.append("scheme=").append(Uri.encode(scheme)).append(';'); } if (mAction != null && !mAction.equals(defAction)) { uri.append("action=").append(Uri.encode(mAction)).append(';'); diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index f40dc298d560..b34b72254743 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -16,7 +16,9 @@ package android.content; +import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.Handler; @@ -154,7 +156,7 @@ public void run() { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { - sendIntent(context, code, intent, onFinished, handler, null); + sendIntent(context, code, intent, onFinished, handler, null, null /* options */); } /** @@ -186,6 +188,42 @@ public void sendIntent(Context context, int code, Intent intent, public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler, String requiredPermission) throws SendIntentException { + sendIntent(context, code, intent, onFinished, handler, requiredPermission, + null /* options */); + } + + /** + * Perform the operation associated with this IntentSender, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * @param context The Context of the caller. This may be null if + * intent is also null. + * @param code Result code to supply back to the IntentSender's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * @param requiredPermission Name of permission that a recipient of the PendingIntent + * is required to hold. This is only valid for broadcast intents, and + * corresponds to the permission argument in + * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. + * If null, no permission is required. + * @param options Additional options the caller would like to provide to modify the sending + * behavior. May be built from an {@link ActivityOptions} to apply to an activity start. + * + * @throws SendIntentException Throws CanceledIntentException if the IntentSender + * is no longer allowing more intents to be sent through it. + * @hide + */ + public void sendIntent(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler, String requiredPermission, + @Nullable Bundle options) + throws SendIntentException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) @@ -195,7 +233,7 @@ public void sendIntent(Context context, int code, Intent intent, onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, - requiredPermission, null); + requiredPermission, options); if (res < 0) { throw new SendIntentException(); } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index fc20263fe00a..de76a68e1d92 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -51,4 +51,5 @@ interface IPackageInstallerSession { int getParentSessionId(); boolean isStaged(); + int getInstallFlags(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 191c4655c708..d56f19909dc7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1358,6 +1358,18 @@ public boolean isStaged() { } } + /** + * @return Session's {@link SessionParams#installFlags}. + * @hide + */ + public int getInstallFlags() { + try { + return mSession.getInstallFlags(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * @return the session ID of the multi-package session that this belongs to or * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session. diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 1b3c46f90851..bf1219ccd872 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -261,6 +261,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -436,8 +442,7 @@ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReas private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -539,6 +544,14 @@ private static Person[] clonePersons(Person[] persons) { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2090,7 +2103,8 @@ private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl); mFlags = source.readInt(); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index 2da93ca34019..707523a192f0 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -1007,7 +1007,7 @@ public void writeToParcel(Parcel dest, int flags) { sForInternedStringList.parcel(this.requestedPermissions, dest, flags); sForInternedStringList.parcel(this.implicitPermissions, dest, flags); sForStringSet.parcel(this.upgradeKeySets, dest, flags); - dest.writeMap(this.keySetMapping); + ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping); sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags); dest.writeTypedList(this.activities); dest.writeTypedList(this.receivers); @@ -1026,7 +1026,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeBoolean(this.use32BitAbi); dest.writeBoolean(this.visibleToInstantApps); dest.writeBoolean(this.forceQueryable); - dest.writeParcelableList(this.queriesIntents, flags); + dest.writeTypedList(this.queriesIntents, flags); sForInternedStringList.parcel(this.queriesPackages, dest, flags); sForInternedStringSet.parcel(this.queriesProviders, dest, flags); dest.writeString(this.appComponentFactory); @@ -1169,7 +1169,7 @@ public ParsingPackageImpl(Parcel in) { this.requestedPermissions = sForInternedStringList.unparcel(in); this.implicitPermissions = sForInternedStringList.unparcel(in); this.upgradeKeySets = sForStringSet.unparcel(in); - this.keySetMapping = in.readHashMap(boot); + this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in); this.protectedBroadcasts = sForInternedStringList.unparcel(in); this.activities = in.createTypedArrayList(ParsedActivity.CREATOR); @@ -1584,6 +1584,9 @@ private void addMimeGroupsFromComponent(ParsedComponent component) { for (int i = component.getIntents().size() - 1; i >= 0; i--) { IntentFilter filter = component.getIntents().get(i); for (int groupIndex = filter.countMimeGroups() - 1; groupIndex >= 0; groupIndex--) { + if (mimeGroups != null && mimeGroups.size() > 500) { + throw new IllegalStateException("Max limit on number of MIME Groups reached"); + } mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex)); } } diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index ab0ed51fb909..e4fd59c01eac 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; @@ -84,6 +85,7 @@ import android.os.Build; import android.os.Bundle; import android.os.FileUtils; +import android.os.Parcel; import android.os.RemoteException; import android.os.Trace; import android.os.ext.SdkExtensions; @@ -777,6 +779,13 @@ private ParseResult parseBaseApkTags(ParseInput input, ParsingPa ); } + if (ParsedPermissionUtils.declareDuplicatePermission(pkg)) { + return input.error( + INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Found duplicate permission with a different attribute value." + ); + } + convertNewPermissions(pkg); convertSplitPermissions(pkg); @@ -2834,6 +2843,68 @@ private static String nonResString(@StyleableRes int index, TypedArray sa) { return sa.getNonResourceString(index); } + /** + * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. + */ + public static void writeKeySetMapping(@NonNull Parcel dest, + @NonNull Map> keySetMapping) { + if (keySetMapping == null) { + dest.writeInt(-1); + return; + } + + final int N = keySetMapping.size(); + dest.writeInt(N); + + for (String key : keySetMapping.keySet()) { + dest.writeString(key); + ArraySet keys = keySetMapping.get(key); + if (keys == null) { + dest.writeInt(-1); + continue; + } + + final int M = keys.size(); + dest.writeInt(M); + for (int j = 0; j < M; j++) { + dest.writeSerializable(keys.valueAt(j)); + } + } + } + + /** + * Reads a keyset mapping from the given parcel at the given data position. May return + * {@code null} if the serialized mapping was {@code null}. + */ + @NonNull + public static ArrayMap> readKeySetMapping(@NonNull Parcel in) { + final int N = in.readInt(); + if (N == -1) { + return null; + } + + ArrayMap> keySetMapping = new ArrayMap<>(); + for (int i = 0; i < N; ++i) { + String key = in.readString(); + final int M = in.readInt(); + if (M == -1) { + keySetMapping.put(key, null); + continue; + } + + ArraySet keys = new ArraySet<>(M); + for (int j = 0; j < M; ++j) { + PublicKey pk = (PublicKey) in.readSerializable(); + keys.add(pk); + } + + keySetMapping.put(key, keys); + } + + return keySetMapping; + } + + /** * Callback interface for retrieving information that may be needed while parsing * a package. diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index 1884a1e27832..fa7cfceb1a4c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -22,6 +22,8 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.EventLog; import android.util.Slog; import com.android.internal.R; @@ -32,6 +34,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.List; +import java.util.Objects; /** @hide */ public class ParsedPermissionUtils { @@ -207,4 +211,48 @@ public static ParseResult parsePermissionGroup(ParsingPac return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup, input); } + + /** + * Determines if a duplicate permission is malformed .i.e. defines different protection level + * or group. + */ + private static boolean isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2) { + // Since a permission tree is also added as a permission with normal protection + // level, we need to skip if the parsedPermission is a permission tree. + if (p1 == null || p2 == null || p1.isTree() || p2.isTree()) { + return false; + } + + if (p1.getProtectionLevel() != p2.getProtectionLevel()) { + return true; + } + if (!Objects.equals(p1.getGroup(), p2.getGroup())) { + return true; + } + + return false; + } + + /** + * @return {@code true} if the package declares malformed duplicate permissions. + */ + public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) { + final List permissions = pkg.getPermissions(); + final int size = permissions.size(); + if (size > 0) { + final ArrayMap checkDuplicatePerm = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + final ParsedPermission parsedPermission = permissions.get(i); + final String name = parsedPermission.getName(); + final ParsedPermission perm = checkDuplicatePerm.get(name); + if (isMalformedDuplicate(parsedPermission, perm)) { + // Fix for b/213323615 + EventLog.writeEvent(0x534e4554, "213323615"); + return true; + } + checkDuplicatePerm.put(name, parsedPermission); + } + } + return false; + } } diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index b978ae559390..35c8dad09d60 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -511,17 +511,31 @@ public static void cursorFillWindow(final Cursor cursor, */ public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { sb.append('\''); - if (sqlString.indexOf('\'') != -1) { - int length = sqlString.length(); - for (int i = 0; i < length; i++) { - char c = sqlString.charAt(i); - if (c == '\'') { - sb.append('\''); + int length = sqlString.length(); + for (int i = 0; i < length; i++) { + char c = sqlString.charAt(i); + if (Character.isHighSurrogate(c)) { + if (i == length - 1) { + continue; + } + if (Character.isLowSurrogate(sqlString.charAt(i + 1))) { + // add them both + sb.append(c); + sb.append(sqlString.charAt(i + 1)); + continue; + } else { + // this is a lone surrogate, skip it + continue; } - sb.append(c); } - } else - sb.append(sqlString); + if (Character.isLowSurrogate(c)) { + continue; + } + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } sb.append('\''); } diff --git a/core/java/android/debug/AdbManager.java b/core/java/android/debug/AdbManager.java index 7714dd80f910..243f80187185 100644 --- a/core/java/android/debug/AdbManager.java +++ b/core/java/android/debug/AdbManager.java @@ -38,6 +38,7 @@ public class AdbManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING) public static final String WIRELESS_DEBUG_STATE_CHANGED_ACTION = "com.android.server.adb.WIRELESS_DEBUG_STATUS"; @@ -46,6 +47,7 @@ public class AdbManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING) public static final String WIRELESS_DEBUG_PAIRED_DEVICES_ACTION = "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES"; @@ -59,6 +61,7 @@ public class AdbManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING) public static final String WIRELESS_DEBUG_PAIRING_RESULT_ACTION = "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT"; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index cfacd5d97bcb..0fa4ca8a574b 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -61,9 +61,6 @@ public final class DisplayManager { * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra. *

* This broadcast is only sent to registered receivers and can only be sent by the system. - *

- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission is required to - * receive this broadcast. *

* @hide */ diff --git a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java index df13ade2bf5e..bd25b8f2ad88 100644 --- a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java +++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java @@ -16,9 +16,9 @@ package android.hardware.location; +import android.os.BadParcelableException; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; /** * Geofence Hardware Request used for internal location services communication. @@ -139,11 +139,8 @@ public String toString() { @Override public GeofenceHardwareRequestParcelable createFromParcel(Parcel parcel) { int geofenceType = parcel.readInt(); - if(geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { - Log.e( - "GeofenceHardwareRequest", - String.format("Invalid Geofence type: %d", geofenceType)); - return null; + if (geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { + throw new BadParcelableException("Invalid Geofence type: " + geofenceType); } GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence( diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java index 66269cb772f8..b25f47b11532 100644 --- a/core/java/android/hardware/usb/UsbConfiguration.java +++ b/core/java/android/hardware/usb/UsbConfiguration.java @@ -172,7 +172,8 @@ public UsbConfiguration createFromParcel(Parcel in) { String name = in.readString(); int attributes = in.readInt(); int maxPower = in.readInt(); - Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader()); + Parcelable[] interfaces = in.readParcelableArray( + UsbInterface.class.getClassLoader(), UsbInterface.class); UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower); configuration.setInterfaces(interfaces); return configuration; diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 53a5785f7c76..23f4c6301ec1 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -107,6 +107,34 @@ boolean isOpen() { } } + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer, length); + } + } + + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer); + } + } + /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 473df712e3f9..c5573214048c 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -113,11 +113,13 @@ public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) * Releases all resources related to this request. */ public void close() { - if (mNativeContext != 0) { - mEndpoint = null; - mConnection = null; - native_close(); - mCloseGuard.close(); + synchronized (mLock) { + if (mNativeContext != 0) { + mEndpoint = null; + mConnection = null; + native_close(); + mCloseGuard.close(); + } } } @@ -191,10 +193,32 @@ public void setClientData(Object data) { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer, length); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; - if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } @@ -243,6 +267,28 @@ public boolean queue(ByteBuffer buffer, int length) { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); @@ -260,7 +306,7 @@ public boolean queue(@Nullable ByteBuffer buffer) { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { - if (mConnection.getContext().getApplicationInfo().targetSdkVersion + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, @@ -363,11 +409,12 @@ public boolean queue(@Nullable ByteBuffer buffer) { * @return true if cancelling succeeded */ public boolean cancel() { - if (mConnection == null) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { return false; } - return mConnection.cancelRequest(this); + return connection.cancelRequest(this); } /** @@ -382,7 +429,8 @@ public boolean cancel() { * @return true if cancelling succeeded. */ /* package */ boolean cancelIfOpen() { - if (mNativeContext == 0 || (mConnection != null && !mConnection.isOpen())) { + UsbDeviceConnection connection = mConnection; + if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { Log.w(TAG, "Detected attempt to cancel a request on a connection which isn't open"); return false; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 1cb4fe8cf4e7..84fc4f78edc4 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1194,13 +1194,16 @@ private HierarchicalUri(String scheme, Part authority, PathPart path, } static Uri readFrom(Parcel parcel) { - return new HierarchicalUri( - parcel.readString8(), - Part.readFrom(parcel), - PathPart.readFrom(parcel), - Part.readFrom(parcel), - Part.readFrom(parcel) - ); + final String scheme = parcel.readString8(); + final Part authority = Part.readFrom(parcel); + // In RFC3986 the path should be determined based on whether there is a scheme or + // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). + final boolean hasSchemeOrAuthority = + (scheme != null && scheme.length() > 0) || !authority.isEmpty(); + final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); + final Part query = Part.readFrom(parcel); + final Part fragment = Part.readFrom(parcel); + return new HierarchicalUri(scheme, authority, path, query, fragment); } public int describeContents() { @@ -2259,6 +2262,11 @@ static PathPart readFrom(Parcel parcel) { } } + static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) { + final PathPart path = readFrom(parcel); + return hasSchemeOrAuthority ? makeAbsolute(path) : path; + } + /** * Creates a path from the encoded string. * diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e5bab6fc9230..5a4b22b8a91b 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -443,6 +443,7 @@ public static Parcel obtain() { */ public final void recycle() { if (DEBUG_RECYCLE) mStack = null; + mClassCookies = null; freeBuffer(); final Parcel[] pool; diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index 7a837e167fb0..7c7e2137aa17 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.FastXmlSerializer; @@ -45,6 +46,8 @@ */ public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, XmlUtils.WriteMapCallback { + private static final String TAG = "PersistableBundle"; + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; public static final PersistableBundle EMPTY; @@ -107,7 +110,11 @@ public PersistableBundle(PersistableBundle b) { * @hide */ public PersistableBundle(Bundle b) { - this(b.getMap()); + this(b, true); + } + + private PersistableBundle(Bundle b, boolean throwException) { + this(b.getMap(), throwException); } /** @@ -116,7 +123,7 @@ public PersistableBundle(Bundle b) { * @param map a Map containing only those items that can be persisted. * @throws IllegalArgumentException if any element of #map cannot be persisted. */ - private PersistableBundle(ArrayMap map) { + private PersistableBundle(ArrayMap map, boolean throwException) { super(); mFlags = FLAG_DEFUSABLE; @@ -125,16 +132,23 @@ private PersistableBundle(ArrayMap map) { // Now verify each item throwing an exception if there is a violation. final int N = mMap.size(); - for (int i=0; i= 0; --i) { Object value = mMap.valueAt(i); if (value instanceof ArrayMap) { // Fix up any Maps by replacing them with PersistableBundles. - mMap.setValueAt(i, new PersistableBundle((ArrayMap) value)); + mMap.setValueAt(i, + new PersistableBundle((ArrayMap) value, throwException)); } else if (value instanceof Bundle) { - mMap.setValueAt(i, new PersistableBundle(((Bundle) value))); + mMap.setValueAt(i, new PersistableBundle((Bundle) value, throwException)); } else if (!isValidType(value)) { - throw new IllegalArgumentException("Bad value in PersistableBundle key=" - + mMap.keyAt(i) + " value=" + value); + final String errorMsg = "Bad value in PersistableBundle key=" + + mMap.keyAt(i) + " value=" + value; + if (throwException) { + throw new IllegalArgumentException(errorMsg); + } else { + Slog.wtfStack(TAG, errorMsg); + mMap.removeAt(i); + } } } } @@ -249,6 +263,15 @@ public void writeUnknownObject(Object v, String name, XmlSerializer out) /** @hide */ public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { unparcel(); + // Explicitly drop invalid types an attacker may have added before persisting. + for (int i = mMap.size() - 1; i >= 0; --i) { + final Object value = mMap.valueAt(i); + if (!isValidType(value)) { + Slog.e(TAG, "Dropping bad data before persisting: " + + mMap.keyAt(i) + "=" + value); + mMap.removeAt(i); + } + } XmlUtils.writeMapXml(mMap, out, this); } @@ -297,9 +320,12 @@ public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOExcept while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { if (event == XmlPullParser.START_TAG) { + // Don't throw an exception when restoring from XML since an attacker could try to + // input invalid data in the persisted file. return new PersistableBundle((ArrayMap) XmlUtils.readThisArrayMapXml(in, startTag, tagName, - new MyReadMapCallback())); + new MyReadMapCallback()), + /* throwException */ false); } } return EMPTY; diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 6adba63f42ce..485d43066b0f 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -129,7 +129,7 @@ public WorkSource(int uid, @NonNull String packageName) { mNames = in.createStringArray(); int numChains = in.readInt(); - if (numChains > 0) { + if (numChains >= 0) { mChains = new ArrayList<>(numChains); in.readParcelableList(mChains, WorkChain.class.getClassLoader()); } else { diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java index 7ed733cb4f4c..9d648a6995fb 100644 --- a/core/java/android/service/gatekeeper/GateKeeperResponse.java +++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java @@ -105,7 +105,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mTimeout); } else if (mResponseCode == RESPONSE_OK) { dest.writeInt(mShouldReEnroll ? 1 : 0); - if (mPayload != null) { + if (mPayload != null && mPayload.length > 0) { dest.writeInt(mPayload.length); dest.writeByteArray(mPayload); } else { diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index cf57e2590c9c..0057d3e872ed 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -89,6 +89,12 @@ public final class Condition implements Parcelable { public final int flags; public final int icon; + /** + * The maximum string length for any string contained in this condition. + * @hide + */ + public static final int MAX_STRING_LENGTH = 1000; + /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule @@ -103,16 +109,19 @@ public Condition(Uri id, String summary, String line1, String line2, int icon, if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); - this.id = id; - this.summary = summary; - this.line1 = line1; - this.line2 = line2; + this.id = getTrimmedUri(id); + this.summary = getTrimmedString(summary); + this.line1 = getTrimmedString(line1); + this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { + // This constructor passes all fields directly into the constructor that takes all the + // fields as arguments; that constructor will trim each of the input strings to + // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), source.readString(), @@ -239,4 +248,25 @@ public Condition[] newArray(int size) { return new Condition[size]; } }; + + /** + * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. + */ + private static String getTrimmedString(String input) { + if (input != null && input.length() > MAX_STRING_LENGTH) { + return input.substring(0, MAX_STRING_LENGTH); + } + return input; + } + + /** + * Returns a truncated copy of the Uri by trimming the string representation to the maximum + * string length. + */ + private static Uri getTrimmedUri(Uri input) { + if (input != null && input.toString().length() > MAX_STRING_LENGTH) { + return Uri.parse(getTrimmedString(input.toString())); + } + return input; + } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 0827fef60252..9c6e0ef4cec3 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -46,6 +46,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -159,6 +160,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; + private static final String RULE_ATT_PKG = "pkg"; private static final String RULE_ATT_COMPONENT = "component"; private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity"; private static final String RULE_ATT_ZEN = "zen"; @@ -669,11 +671,11 @@ public static ZenRule readRuleXml(XmlPullParser parser) { rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY); - rt.pkg = (rt.component != null) - ? rt.component.getPackageName() - : (rt.configurationActivity != null) - ? rt.configurationActivity.getPackageName() - : null; + rt.pkg = XmlUtils.readStringAttribute(parser, RULE_ATT_PKG); + if (rt.pkg == null) { + // backfill from component, if present. configActivity is not safe to backfill from + rt.pkg = rt.component != null ? rt.component.getPackageName() : null; + } rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0); rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER); rt.condition = readConditionXml(parser); @@ -695,6 +697,9 @@ public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOExcept out.attribute(null, RULE_ATT_NAME, rule.name); } out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode)); + if (rule.pkg != null) { + out.attribute(null, RULE_ATT_PKG, rule.pkg); + } if (rule.component != null) { out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 6f941121771e..b956d1918f7e 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -16,12 +16,14 @@ package android.service.voice; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; import android.hardware.soundtrigger.KeyphraseMetadata; @@ -232,8 +234,10 @@ public class AlwaysOnHotwordDetector { private final Callback mExternalCallback; private final Object mLock = new Object(); private final Handler mHandler; + private final Context mContext; private int mAvailability = STATE_NOT_READY; + private boolean mIsGrantedHotwordPermission; /** * A ModelParamRange is a representation of supported parameter range for a @@ -408,23 +412,37 @@ public static abstract class Callback { public abstract void onRecognitionResumed(); } + private static boolean hasHotwordPermission(Context context) { + return context.checkSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean hasRecordAudioPermission(Context context) { + return context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED; + } + /** + * @param context The context to check permission * @param text The keyphrase text to get the detector for. * @param locale The java locale for the detector. * @param callback A non-null Callback for receiving the recognition events. + * @param keyphraseEnrollmentInfo The Enrollment info of key phrase * @param modelManagementService A service that allows management of sound models. * @hide */ - public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, + public AlwaysOnHotwordDetector(Context context, String text, Locale locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionManagerService modelManagementService) { mText = text; + mContext = context; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; mExternalCallback = callback; mHandler = new MyHandler(); mInternalCallback = new SoundTriggerListener(mHandler); mModelManagementService = modelManagementService; + mIsGrantedHotwordPermission = hasHotwordPermission(mContext); new RefreshAvailabiltyTask().execute(); } @@ -477,6 +495,11 @@ private int getSupportedRecognitionModesLocked() { @AudioCapabilities public int getSupportedAudioCapabilities() { if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()"); + + if (!mIsGrantedHotwordPermission) { + return 0; + } + synchronized (mLock) { return getSupportedAudioCapabilitiesLocked(); } @@ -515,6 +538,12 @@ private int getSupportedAudioCapabilitiesLocked() { */ public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); + + if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) { + throw new IllegalStateException("Must have the RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD " + + "permissions to access the detector."); + } + synchronized (mLock) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("startRecognition called on an invalid detector"); @@ -545,6 +574,12 @@ public boolean startRecognition(@RecognitionFlags int recognitionFlags) { */ public boolean stopRecognition() { if (DBG) Slog.d(TAG, "stopRecognition()"); + + if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) { + throw new IllegalStateException("Must have the RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD " + + "permissions to access the detector."); + } + synchronized (mLock) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("stopRecognition called on an invalid detector"); @@ -582,6 +617,10 @@ public int setParameter(@ModelParams int modelParam, int value) { Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); } + if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) { + return SoundTrigger.STATUS_INVALID_OPERATION; + } + synchronized (mLock) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("setParameter called on an invalid detector"); @@ -609,6 +648,10 @@ public int getParameter(@ModelParams int modelParam) { Slog.d(TAG, "getParameter(" + modelParam + ")"); } + if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) { + return MODEL_PARAM_THRESHOLD_FACTOR; + } + synchronized (mLock) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("getParameter called on an invalid detector"); @@ -634,6 +677,10 @@ public ModelParamRange queryParameter(@ModelParams int modelParam) { Slog.d(TAG, "queryParameter(" + modelParam + ")"); } + if (!mIsGrantedHotwordPermission || !hasRecordAudioPermission(mContext)) { + return null; + } + synchronized (mLock) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("queryParameter called on an invalid detector"); @@ -742,8 +789,8 @@ void invalidate() { */ void onSoundModelsChanged() { synchronized (mLock) { - if (mAvailability == STATE_INVALID - || mAvailability == STATE_HARDWARE_UNAVAILABLE) { + if (mAvailability == STATE_INVALID || mAvailability == STATE_HARDWARE_UNAVAILABLE + || !hasRecordAudioPermission(mContext)) { Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); return; } @@ -962,6 +1009,10 @@ public Void doInBackground(Void... params) { * @return The initial availability without checking the enrollment status. */ private int internalGetInitialAvailability() { + if (!mIsGrantedHotwordPermission) { + return STATE_HARDWARE_UNAVAILABLE; + } + synchronized (mLock) { // This detector has already been invalidated. if (mAvailability == STATE_INVALID) { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 45d3465fdae8..a56d40790dbf 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -318,7 +318,7 @@ public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( synchronized (mLock) { // Allow only one concurrent recognition via the APIs. safelyShutdownHotwordDetector(); - mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, + mHotwordDetector = new AlwaysOnHotwordDetector(this, keyphrase, locale, callback, mKeyphraseEnrollmentInfo, mSystemService); } return mHotwordDetector; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 346fe293d7ae..f0c20bec7d7b 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -75,6 +75,11 @@ public class ApkSignatureSchemeV2Verifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; + /** + * The maximum number of signers supported by the v2 APK signature scheme. + */ + private static final int MAX_V2_SIGNERS = 10; + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * @@ -183,6 +188,11 @@ private static VerifiedSigner verify( } while (signers.hasRemaining()) { signerCount++; + if (signerCount > MAX_V2_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS + + " signers"); + } try { ByteBuffer signer = getLengthPrefixedSlice(signers); X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index 45254908c5c9..a6aca330d323 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -78,6 +78,11 @@ class StrictJarVerifier { "SHA1", }; + /** + * The maximum number of signers supported by the JAR signature scheme. + */ + private static final int MAX_JAR_SIGNERS = 10; + private final String jarName; private final StrictJarManifest manifest; private final HashMap metaEntries; @@ -293,10 +298,16 @@ synchronized boolean readCertificates() { return false; } + int signerCount = 0; Iterator it = metaEntries.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { + if (++signerCount > MAX_JAR_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS + + " signers"); + } verifyCertificate(key); it.remove(); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 0410c9024dcb..819e89b67b38 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -336,11 +336,12 @@ interface IWindowSession { * an input channel where the client can receive input. */ void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window, - in IBinder hostInputToken, int flags, int type, out InputChannel outInputChannel); + in IBinder hostInputToken, int flags, int privateFlags, int type, + out InputChannel outInputChannel); /** * Update the flags on an input channel associated with a particular surface. */ void updateInputChannel(in IBinder channelToken, int displayId, in SurfaceControl surface, - int flags, in Region region); + int flags, int privateFlags, in Region region); } diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 71d26b8880f7..b3cf78c5157f 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -82,6 +82,9 @@ public final class InputWindowHandle { // Input event dispatching is paused. public boolean paused; + // Window is trusted overlay. + public boolean trustedOverlay; + // Id of process and user that owns the window. public int ownerPid; public int ownerUid; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 87b2f4b46df7..0dc7f5ad26fb 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -42,6 +42,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.gui.DropInputMode; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -49,7 +50,6 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Trace; import android.util.ArrayMap; import android.util.Log; import android.util.SparseIntArray; @@ -139,7 +139,10 @@ private static native void nativeSetBackgroundBlurRadius(long transactionObj, lo int blurRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, int layerStack); - + private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject, + boolean isTrustedOverlay); + private static native void nativeSetDropInputMode( + long transactionObj, long nativeObject, int flags); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); private static native boolean nativeClearAnimationFrameStats(); @@ -3037,6 +3040,28 @@ public Transaction setFrameRate(@NonNull SurfaceControl sc, return this; } + /** + * Sets the trusted overlay state on this SurfaceControl and it is inherited to all the + * children. The caller must hold the ACCESS_SURFACE_FLINGER permission. + * @hide + */ + public Transaction setTrustedOverlay(SurfaceControl sc, boolean isTrustedOverlay) { + checkPreconditions(sc); + nativeSetTrustedOverlay(mNativeObject, sc.mNativeObject, isTrustedOverlay); + return this; + } + + /** + * Sets the input event drop mode on this SurfaceControl and its children. The caller must + * hold the ACCESS_SURFACE_FLINGER permission. See {@code InputEventDropMode}. + * @hide + */ + public Transaction setDropInputMode(SurfaceControl sc, @DropInputMode int mode) { + checkPreconditions(sc); + nativeSetDropInputMode(mNativeObject, sc.mNativeObject, mode); + return this; + } + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 98155de99b6f..eca0045ad436 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1216,14 +1216,6 @@ public static class LayoutParams extends ViewGroup.LayoutParams implements Parce */ public static final int TYPE_STATUS_BAR_ADDITIONAL = FIRST_SYSTEM_WINDOW + 41; - /** - * Similar to TYPE_APPLICATION_OVERLAY, but trusted to overlay other windows since it is - * is coming from the system. - * @hide - */ - // TODO(b/155781676): Remove and replace call points with trustedOverlay when that is ready. - public static final int TYPE_TRUSTED_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 42; - /** * End of types of system windows. */ @@ -2044,6 +2036,11 @@ public static boolean isSystemAlertWindowType(@WindowType int type) { */ public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000; + /** + * Flag to indicate that the window is a trusted overlay. + * @hide + */ + public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000; /** * An internal annotation for flags that can be specified to {@link #softInputMode}. * @@ -2952,6 +2949,20 @@ public void setFitInsetsIgnoringVisibility(boolean ignore) { privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED; } + /** + * Specifies that the window should be considered a trusted system overlay. Trusted system + * overlays are ignored when considering whether windows are obscured during input + * dispatch. Requires the {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} + * permission. + * + * {@see android.view.MotionEvent#FLAG_WINDOW_IS_OBSCURED} + * {@see android.view.MotionEvent#FLAG_WINDOW_IS_PARTIALLY_OBSCURED} + * @hide + */ + public void setTrustedOverlay() { + privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY; + } + /** * @return the insets types that this window is avoiding overlapping. */ diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index f0006d988163..13f26f5a7fb6 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -115,7 +115,8 @@ protected void setTouchRegion(IBinder window, @Nullable Region region) { if (state.mInputChannelToken != null) { try { mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, - state.mSurfaceControl, state.mParams.flags, state.mInputRegion); + state.mSurfaceControl, state.mParams.flags, state.mParams.privateFlags, + state.mInputRegion); } catch (RemoteException e) { Log.e(TAG, "Failed to update surface input channel: ", e); } @@ -144,7 +145,7 @@ public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attr WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) { try { mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, - attrs.type, outInputChannel); + attrs.privateFlags, attrs.type, outInputChannel); } catch (RemoteException e) { Log.e(TAG, "Failed to grant input to surface: ", e); } @@ -274,7 +275,7 @@ public int relayout(IWindow window, int seq, WindowManager.LayoutParams inAttrs, && state.mInputChannelToken != null) { try { mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc, - attrs.flags, state.mInputRegion); + attrs.flags, attrs.privateFlags, state.mInputRegion); } catch (RemoteException e) { Log.e(TAG, "Failed to update surface input channel: ", e); } @@ -445,12 +446,13 @@ public void reportSystemGestureExclusionChanged(android.view.IWindow window, @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, - IBinder hostInputToken, int flags, int type, InputChannel outInputChannel) { + IBinder hostInputToken, int flags, int privateFlags, int type, + InputChannel outInputChannel) { } @Override public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, Region region) { + int flags, int privateFlags, Region region) { } @Override diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5f8b13b5b548..7b2ebde70e5f 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2957,7 +2957,7 @@ public Map> getShortcutInputMethodsAnd @UnsupportedAppUsage public int getInputMethodWindowVisibleHeight() { try { - return mService.getInputMethodWindowVisibleHeight(); + return mService.getInputMethodWindowVisibleHeight(mClient); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a9b2c4df255f..6693b937a6e5 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -562,6 +562,12 @@ public void visitUris(@NonNull Consumer visitor) { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { @@ -1674,6 +1680,11 @@ public boolean prefersAsyncApply() { public int getActionTag() { return VIEW_GROUP_ACTION_ADD_TAG; } + + @Override + public final void visitUris(@NonNull Consumer visitor) { + mNestedViews.visitUris(visitor); + } } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 796a557f45af..8ac2ca49e9eb 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; @@ -148,6 +150,7 @@ 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 - @@ -1300,7 +1303,7 @@ private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater 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); @@ -1327,6 +1330,10 @@ private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater 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; + } mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); } else { ContentResolver resolver = getContentResolver(); @@ -1334,7 +1341,7 @@ private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater List uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); List imageUris = new ArrayList<>(); for (Uri uri : uris) { - if (isImageType(resolver.getType(uri))) { + if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) { imageUris.add(uri); } } @@ -1441,9 +1448,16 @@ private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater 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 uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + uris = uris.stream() + .filter(ChooserActivity::validForContentPreview) + .collect(Collectors.toList()); int uriCount = uris.size(); if (uriCount == 0) { @@ -1497,6 +1511,24 @@ private void loadFileUriIntoView(final Uri uri, final View parent) { } } + /** + * 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/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java index ce2d229d41b3..33209e110123 100644 --- a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java +++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java @@ -16,6 +16,8 @@ package com.android.internal.app; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -27,6 +29,7 @@ import android.util.Log; import android.view.View; import android.widget.TextView; + import com.android.internal.R; /** @@ -48,6 +51,7 @@ public class HarmfulAppWarningActivity extends AlertActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); final Intent intent = getIntent(); mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 1270185e1ea5..998000c19cd0 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -19,6 +19,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.Nullable; import android.annotation.StringRes; @@ -69,7 +70,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.Window; import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Button; @@ -101,7 +104,6 @@ import java.util.Objects; import java.util.Set; - /** * This activity is displayed when the system attempts to start an Intent for * which there is more than one matching activity, allowing the user to decide @@ -822,6 +824,8 @@ protected void onRestart() { @Override protected void onStart() { super.onStart(); + + this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); if (shouldShowTabs()) { mWorkProfileStateReceiver = createWorkProfileStateReceiver(); registerWorkProfileStateReceiver(); @@ -849,6 +853,12 @@ private void registerWorkProfileStateReceiver() { @Override protected void onStop() { super.onStop(); + + final Window window = this.getWindow(); + final WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + window.setAttributes(attrs); + if (mRegistered) { mPersonalPackageMonitor.unregister(); if (mWorkPackageMonitor != null) { @@ -1223,9 +1233,6 @@ protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { } if (target != null) { - if (intent != null && isLaunchingTargetInOtherProfile()) { - prepareIntentForCrossProfileLaunch(intent); - } safelyStartActivity(target); // Rely on the ActivityManager to pop up a dialog regarding app suspension @@ -1238,15 +1245,6 @@ protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { return true; } - private void prepareIntentForCrossProfileLaunch(Intent intent) { - intent.fixUris(UserHandle.myUserId()); - } - - private boolean isLaunchingTargetInOtherProfile() { - return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - != UserHandle.myUserId(); - } - @VisibleForTesting public void safelyStartActivity(TargetInfo cti) { // We're dispatching intents that might be coming from legacy apps, so diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index fe0e7d012262..cbbfbdd88a6c 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -178,6 +178,7 @@ public boolean startAsCaller(ResolverActivity activity, Bundle options, int user if (ENABLE_CHOOSER_DELEGATE) { return activity.startAsCallerImpl(mResolvedIntent, options, false, userId); } else { + TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId); activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId); return true; } @@ -185,6 +186,7 @@ public boolean startAsCaller(ResolverActivity activity, Bundle options, int user @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { + TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); activity.startActivityAsUser(mResolvedIntent, options, user); return false; } diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java index 900e18d468bb..9d057b34363e 100644 --- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java +++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java @@ -230,6 +230,7 @@ public boolean startAsCaller(ResolverActivity activity, Bundle options, int user } intent.setComponent(mChooserTarget.getComponentName()); intent.putExtras(mChooserTarget.getIntentExtras()); + TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId); // Important: we will ignore the target security checks in ActivityManager // if and only if the ChooserTarget's target package is the same package diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java index f56ab17cb059..7bb7ddc65c6d 100644 --- a/core/java/com/android/internal/app/chooser/TargetInfo.java +++ b/core/java/com/android/internal/app/chooser/TargetInfo.java @@ -130,4 +130,15 @@ public interface TargetInfo { * @return true if this target should be pinned to the front by the request of the user */ boolean isPinned(); + + /** + * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called + * before launching the intent as another user. + */ + static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) { + final int currentUserId = UserHandle.myUserId(); + if (targetUserId != currentUserId) { + intent.fixUris(currentUserId); + } + } } diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java index 9f15d8991fa7..84391c169941 100644 --- a/core/java/com/android/internal/infra/AndroidFuture.java +++ b/core/java/com/android/internal/infra/AndroidFuture.java @@ -16,23 +16,21 @@ package com.android.internal.infra; -import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; - import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; -import android.os.Message; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.util.ExceptionUtils; +import android.util.EventLog; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.util.function.pooled.PooledLambda; +import java.lang.reflect.Constructor; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -75,14 +73,16 @@ public class AndroidFuture extends CompletableFuture implements Parcelable private static final boolean DEBUG = false; private static final String LOG_TAG = AndroidFuture.class.getSimpleName(); + private static final Executor DIRECT_EXECUTOR = Runnable::run; private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + private static @Nullable Handler sMainHandler; private final @NonNull Object mLock = new Object(); @GuardedBy("mLock") private @Nullable BiConsumer mListener; @GuardedBy("mLock") private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR; - private @NonNull Handler mTimeoutHandler = Handler.getMain(); + private @NonNull Handler mTimeoutHandler = getMainHandler(); private final @Nullable IAndroidFuture mRemoteOrigin; public AndroidFuture() { @@ -96,7 +96,7 @@ public AndroidFuture() { // Done if (in.readBoolean()) { // Failed - completeExceptionally(unparcelException(in)); + completeExceptionally(readThrowable(in)); } else { // Success complete((T) in.readValue(null)); @@ -108,6 +108,15 @@ public AndroidFuture() { } } + @NonNull + private static Handler getMainHandler() { + // This isn't thread-safe but we are okay with it. + if (sMainHandler == null) { + sMainHandler = new Handler(Looper.getMainLooper()); + } + return sMainHandler; + } + /** * Create a completed future with the given value. * @@ -236,9 +245,7 @@ private void callListenerAsync(BiConsumer listener if (mListenerExecutor == DIRECT_EXECUTOR) { callListener(listener, res, err); } else { - mListenerExecutor.execute(PooledLambda - .obtainRunnable(AndroidFuture::callListener, listener, res, err) - .recycleOnUse()); + mListenerExecutor.execute(() -> callListener(listener, res, err)); } } @@ -260,7 +267,8 @@ static void callListener( } else { // listener exception-case threw // give up on listener but preserve the original exception when throwing up - throw ExceptionUtils.appendCause(t, err); + t.addSuppressed(err); + throw t; } } } catch (Throwable t2) { @@ -272,9 +280,7 @@ static void callListener( /** @inheritDoc */ //@Override //TODO uncomment once java 9 APIs are exposed to frameworks public AndroidFuture orTimeout(long timeout, @NonNull TimeUnit unit) { - Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this); - msg.obj = this; - mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout)); + mTimeoutHandler.postDelayed(this::triggerTimeout, this, unit.toMillis(timeout)); return this; } @@ -507,7 +513,7 @@ public void writeToParcel(Parcel dest, int flags) { result = get(); } catch (Throwable t) { dest.writeBoolean(true); - parcelException(dest, unwrapExecutionException(t)); + writeThrowable(dest, unwrapExecutionException(t)); return; } dest.writeBoolean(false); @@ -545,45 +551,75 @@ Throwable unwrapExecutionException(Throwable t) { * Alternative to {@link Parcel#writeException} that stores the stack trace, in a * way consistent with the binder IPC exception propagation behavior. */ - private static void parcelException(Parcel p, @Nullable Throwable t) { - p.writeBoolean(t == null); - if (t == null) { + private static void writeThrowable(@NonNull Parcel parcel, @Nullable Throwable throwable) { + boolean hasThrowable = throwable != null; + parcel.writeBoolean(hasThrowable); + if (!hasThrowable) { + return; + } + + boolean isFrameworkParcelable = throwable instanceof Parcelable + && throwable.getClass().getClassLoader() == Parcelable.class.getClassLoader(); + parcel.writeBoolean(isFrameworkParcelable); + if (isFrameworkParcelable) { + parcel.writeParcelable((Parcelable) throwable, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return; } - p.writeInt(Parcel.getExceptionCode(t)); - p.writeString(t.getClass().getName()); - p.writeString(t.getMessage()); - p.writeStackTrace(t); - parcelException(p, t.getCause()); + parcel.writeString(throwable.getClass().getName()); + parcel.writeString(throwable.getMessage()); + StackTraceElement[] stackTrace = throwable.getStackTrace(); + StringBuilder stackTraceBuilder = new StringBuilder(); + int truncatedStackTraceLength = Math.min(stackTrace != null ? stackTrace.length : 0, 5); + for (int i = 0; i < truncatedStackTraceLength; i++) { + if (i > 0) { + stackTraceBuilder.append('\n'); + } + stackTraceBuilder.append("\tat ").append(stackTrace[i]); + } + parcel.writeString(stackTraceBuilder.toString()); + writeThrowable(parcel, throwable.getCause()); } /** - * @see #parcelException + * @see #writeThrowable */ - private static @Nullable Throwable unparcelException(Parcel p) { - if (p.readBoolean()) { + private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) { + final boolean hasThrowable = parcel.readBoolean(); + if (!hasThrowable) { return null; } - int exCode = p.readInt(); - String cls = p.readString(); - String msg = p.readString(); - String stackTrace = p.readInt() > 0 ? p.readString() : "\t"; - msg += "\n" + stackTrace; - - Exception ex = p.createExceptionOrNull(exCode, msg); - if (ex == null) { - ex = new RuntimeException(cls + ": " + msg); + boolean isFrameworkParcelable = parcel.readBoolean(); + if (isFrameworkParcelable) { + return parcel.readParcelable(Parcelable.class.getClassLoader()); } - ex.setStackTrace(EMPTY_STACK_TRACE); - Throwable cause = unparcelException(p); + String className = parcel.readString(); + String message = parcel.readString(); + String stackTrace = parcel.readString(); + String messageWithStackTrace = message + '\n' + stackTrace; + Throwable throwable; + try { + Class clazz = Class.forName(className, true, Parcelable.class.getClassLoader()); + if (Throwable.class.isAssignableFrom(clazz)) { + Constructor constructor = clazz.getConstructor(String.class); + throwable = (Throwable) constructor.newInstance(messageWithStackTrace); + } else { + android.util.EventLog.writeEvent(0x534e4554, "186530450", -1, ""); + throwable = new RuntimeException(className + ": " + messageWithStackTrace); + } + } catch (Throwable t) { + throwable = new RuntimeException(className + ": " + messageWithStackTrace); + throwable.addSuppressed(t); + } + throwable.setStackTrace(EMPTY_STACK_TRACE); + Throwable cause = readThrowable(parcel); if (cause != null) { - ex.initCause(ex); + throwable.initCause(cause); } - - return ex; + return throwable; } @Override diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java index 167d128a76f8..89869389cb59 100644 --- a/core/java/com/android/internal/infra/ServiceConnector.java +++ b/core/java/com/android/internal/infra/ServiceConnector.java @@ -26,14 +26,11 @@ import android.os.Handler; import android.os.IBinder; import android.os.IInterface; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; -import android.text.TextUtils; -import android.util.DebugUtils; import android.util.Log; -import com.android.internal.util.function.pooled.PooledLambda; - import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; @@ -47,7 +44,6 @@ import java.util.function.BiConsumer; import java.util.function.Function; - /** * Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon * a certain timeout. @@ -220,6 +216,7 @@ class Impl extends ArrayDeque> private final @NonNull Queue> mQueue = this; private final @NonNull List> mUnfinishedJobs = new ArrayList<>(); + private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper()); private final @NonNull ServiceConnection mServiceConnection = this; private final @NonNull Runnable mTimeoutDisconnect = this; @@ -250,9 +247,8 @@ class Impl extends ArrayDeque> * {@link IInterface}. * Typically this is {@code IMyInterface.Stub::asInterface} */ - public Impl(@NonNull Context context, @NonNull Intent intent, - @Context.BindServiceFlags int bindingFlags, @UserIdInt int userId, - @Nullable Function binderAsInterface) { + public Impl(@NonNull Context context, @NonNull Intent intent, int bindingFlags, + @UserIdInt int userId, @Nullable Function binderAsInterface) { mContext = context; mIntent = intent; mBindingFlags = bindingFlags; @@ -264,7 +260,7 @@ public Impl(@NonNull Context context, @NonNull Intent intent, * {@link Handler} on which {@link Job}s will be called */ protected Handler getJobHandler() { - return Handler.getMain(); + return mMainHandler; } /** @@ -391,8 +387,7 @@ private void enqueue(@NonNull CompletionAwareJob task) { private boolean enqueue(@NonNull Job job) { cancelTimeout(); - return getJobHandler().sendMessage(PooledLambda.obtainMessage( - ServiceConnector.Impl::enqueueJobThread, this, job)); + return getJobHandler().post(() -> enqueueJobThread(job)); } void enqueueJobThread(@NonNull Job job) { @@ -422,7 +417,7 @@ private void cancelTimeout() { if (DEBUG) { logTrace(); } - Handler.getMain().removeCallbacks(mTimeoutDisconnect); + mMainHandler.removeCallbacks(mTimeoutDisconnect); } void completeExceptionally(@NonNull Job job, @NonNull Throwable ex) { @@ -486,7 +481,7 @@ private void scheduleUnbindTimeout() { } long timeout = getAutoDisconnectTimeoutMs(); if (timeout > 0) { - Handler.getMain().postDelayed(mTimeoutDisconnect, timeout); + mMainHandler.postDelayed(mTimeoutDisconnect, timeout); } else if (DEBUG) { Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this); } @@ -502,7 +497,7 @@ public void unbind() { logTrace(); } mUnbinding = true; - getJobHandler().sendMessage(PooledLambda.obtainMessage(Impl::unbindJobThread, this)); + getJobHandler().post(this::unbindJobThread); } void unbindJobThread() { @@ -659,10 +654,7 @@ private String stateToString() { } private void logTrace() { - Log.i(LOG_TAG, - TextUtils.join(" -> ", - DebugUtils.callersWithin(ServiceConnector.class, /* offset= */ 1)) - + "(" + this + ")"); + Log.i(LOG_TAG, "See stacktrace", new Throwable()); } /** diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java index 4ce6f609ef73..fdf0e9046eef 100644 --- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java +++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java @@ -17,6 +17,7 @@ package com.android.internal.notification; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; public final class NotificationAccessConfirmationActivityContract { @@ -25,13 +26,14 @@ public final class NotificationAccessConfirmationActivityContract { "com.android.settings.notification.NotificationAccessConfirmationActivity"); public static final String EXTRA_USER_ID = "user_id"; public static final String EXTRA_COMPONENT_NAME = "component_name"; - public static final String EXTRA_PACKAGE_TITLE = "package_title"; - public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) { + /** + * Creates a launcher intent for NotificationAccessConfirmationActivity. + */ + public static Intent launcherIntent(Context context, int userId, ComponentName component) { return new Intent() .setComponent(COMPONENT_NAME) .putExtra(EXTRA_USER_ID, userId) - .putExtra(EXTRA_COMPONENT_NAME, component) - .putExtra(EXTRA_PACKAGE_TITLE, packageTitle); + .putExtra(EXTRA_COMPONENT_NAME, component); } } diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index 8e454db4cb04..a8003a1169e9 100644 --- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -16,7 +16,7 @@ package com.android.internal.policy; interface IKeyguardStateCallback { - void onShowingStateChanged(boolean showing); + void onShowingStateChanged(boolean showing, int userId); void onSimSecureStateChanged(boolean simSecure); void onInputRestrictedStateChanged(boolean inputRestricted); void onTrustedChanged(boolean trusted); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index a1cbd3fcae79..629d92927237 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -67,7 +67,7 @@ interface IInputMethodManager { void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); // This is kept due to @UnsupportedAppUsage. // TODO(Bug 113914148): Consider removing this. - int getInputMethodWindowVisibleHeight(); + int getInputMethodWindowVisibleHeight(in IInputMethodClient client); void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index 79f62cb19db0..81569e0f7b7a 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -59,6 +59,7 @@ static struct { jfieldID hasFocus; jfieldID hasWallpaper; jfieldID paused; + jfieldID trustedOverlay; jfieldID ownerPid; jfieldID ownerUid; jfieldID inputFeatures; @@ -151,6 +152,7 @@ bool NativeInputWindowHandle::updateInfo() { gInputWindowHandleClassInfo.hasWallpaper); mInfo.paused = env->GetBooleanField(obj, gInputWindowHandleClassInfo.paused); + mInfo.trustedOverlay = env->GetBooleanField(obj, gInputWindowHandleClassInfo.trustedOverlay); mInfo.ownerPid = env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid); mInfo.ownerUid = env->GetIntField(obj, @@ -329,6 +331,8 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { GET_FIELD_ID(gInputWindowHandleClassInfo.paused, clazz, "paused", "Z"); + GET_FIELD_ID(gInputWindowHandleClassInfo.trustedOverlay, clazz, "trustedOverlay", "Z"); + GET_FIELD_ID(gInputWindowHandleClassInfo.ownerPid, clazz, "ownerPid", "I"); diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 9f4e3e516ada..87102d3359a7 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include @@ -48,9 +49,17 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi return NULL; } + sp map = deviceInfo.getKeyCharacterMap(); + if (map != nullptr) { + Parcel parcel; + map->writeToParcel(&parcel); + parcel.setDataPosition(0); + map = map->readFromParcel(&parcel); + } + ScopedLocalRef kcmObj(env, - android_view_KeyCharacterMap_create(env, deviceInfo.getId(), - deviceInfo.getKeyCharacterMap())); + android_view_KeyCharacterMap_create(env, deviceInfo.getId(), + map)); if (!kcmObj.get()) { return NULL; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ae36f8a7b30b..3c704ffb8de7 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -627,6 +627,14 @@ static void nativeSetShadowRadius(JNIEnv* env, jclass clazz, jlong transactionOb transaction->setShadowRadius(ctrl, shadowRadius); } +static void nativeSetTrustedOverlay(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jboolean isTrustedOverlay) { + auto transaction = reinterpret_cast(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast(nativeObject); + transaction->setTrustedOverlay(ctrl, isTrustedOverlay); +} + static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jfloat frameRate, jint compatibility) { auto transaction = reinterpret_cast(transactionObj); @@ -663,6 +671,13 @@ static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transac transaction->setFixedTransformHint(ctrl, transformHint); } +static void nativeSetDropInputMode(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint mode) { + auto transaction = reinterpret_cast(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast(nativeObject); + transaction->setDropInputMode(ctrl, static_cast(mode)); +} + static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) { const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds(); jlongArray array = env->NewLongArray(displayIds.size()); @@ -1487,6 +1502,7 @@ static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { + // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", (void*)nativeCreate }, {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", @@ -1666,7 +1682,13 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetGlobalShadowSettings }, {"nativeGetHandle", "(J)J", (void*)nativeGetHandle }, - {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint}, + {"nativeSetFixedTransformHint", "(JJI)V", + (void*)nativeSetFixedTransformHint}, + {"nativeSetTrustedOverlay", "(JJZ)V", + (void*)nativeSetTrustedOverlay }, + {"nativeSetDropInputMode", "(JJI)V", + (void*)nativeSetDropInputMode}, + // clang-format on }; int register_android_view_SurfaceControl(JNIEnv* env) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5a01420732e9..0fdc0851bcc7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -96,6 +96,7 @@ + @@ -2548,7 +2549,11 @@ android:protectionLevel="normal" /> + + "Weier" "Toestemming versoek" "Toestemming versoek\nvir rekening %s." + "Toestemming versoek deur %1$s\nvir rekening %2$s" "Jy gebruik hierdie program buite jou werkprofiel" "Jy gebruik tans hierdie program in jou werkprofiel" "Invoermetode" diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index d41868e8c478..670b64e010f0 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1413,6 +1413,7 @@ "ያስተባብሉ" "ፈቃድ ተጠይቋል" \n" ለ%s መለያ ፈቃድ ተጠይቋል" + "ለመለያ %2$s\nበ%1$s የተጠየቀ ፈቃድ።" "ከስራ መገለጫዎ ውጪ ሆነው መተግበሪያ እየተጠቀሙ ነው" "ይህን መተግበሪያ በእርስዎ የስራ መገለጫ ላይ እየተጠቀሙበት ነው" "ግቤት ስልት" diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 4a99ec8021e8..7e5460160a7d 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1493,6 +1493,7 @@ "رفض" "الإذن مطلوب" "الإذن مطلوب\nللحساب %s." + "طلب تطبيق %1$s الإذن بالدخول\nإلى حساب %2$s." "أنت تستخدم هذا التطبيق خارج ملفك الشخصي للعمل" "أنت تستخدم هذا التطبيق في ملفك الشخصي للعمل" "طريقة الإرسال" diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 2ca98b4c5f22..3573ff5d6eb9 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1413,6 +1413,7 @@ "প্ৰত্যাখ্যান কৰক" "অনুমতি বিচাৰি অনুৰোধ কৰা হৈছে" "%s একাউণ্টৰ বাবে\nঅনুমতি বিচাৰি অনুৰোধ কৰা হৈছে।" + "%1$s%2$s একাউণ্টটো এক্সেছৰ \nঅনুমতি বিচাৰি অনুৰোধ জনাইছে।" "আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপটো ব্যৱহাৰ কৰি আছে" "আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপটো ব্যৱহাৰ কৰি আছে" "ইনপুট পদ্ধতি" diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 36c293c491c6..52c57cdcd112 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1413,6 +1413,7 @@ "Rədd et" "İcazə tələb olunur" \n" hesabı üçün%s icazə sorğusu göndərildi." + "%1$s\ntərəfindən %2$s hesabı üçün icazə tələb edilib." "Bu tətbiqi iş profilinizdən kənarda istifadə edirsiniz" "Bu tətbiqi iş profilinizdə istifadə edirsiniz" "Daxiletmə metodu" diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index daec63a9d5a5..1399c8fb9f37 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1433,6 +1433,7 @@ "Odbij" "Zatražena je dozvola" "Zatražena je dozvola\nza nalog %s" + "%1$s traži dozvolu \nza nalog %2$s." "Koristite ovu aplikaciju izvan poslovnog profila" "Koristite ovu aplikaciju na poslovnom profilu" "Metod unosa" diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 220c3520a1db..bd35a675d814 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1453,6 +1453,7 @@ "Адмовіць" "Дазвол запытаны" "Запытаны дазвол\nдля ўліковага запісу %s" + "Праграма \"%1$s\" запытвае дазвол\nдля ўліковага запісу %2$s." "Вы выкарыстоўваеце гэту праграму па-за межамі свайго працоўнага профілю" "Вы выкарыстоўваеце гэту праграму ў сваім працоўным профілі" "Метад уводу" diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 8d3a23c99e3b..5a4dc99e3845 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1413,6 +1413,7 @@ "Отказване" "Иска се разрешение" "Иска се разрешение\nза профила %s." + "Поискано е разрешение от %1$s\nза профила %2$s." "Използвате това приложение извън служебния си потребителски профил" "Използвате това приложение в служебния си потребителски профил" "Метод на въвеждане" diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 9462be3f2f44..b91868e8f147 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1413,6 +1413,7 @@ "অস্বীকার করুন" "অনুমতির অনুরোধ করা হয়েছে" "%sঅ্যাকাউন্টের জন্য\nঅনুমতির অনুরোধ করা হয়েছে৷" + "%2$s অ্যাকাউন্টের জন্য %1$s\n থেকে অনুমতি চাওয়া হয়েছে।" "আপনি এই অ্যাপ্লিকেশানটি আপনার কর্মস্থলের প্রোফাইলের বাইরে ব্যবহার করছেন" "আপনি এই অ্যাপ্লিকেশানটি আপনার কর্মস্থলের প্রোফাইলে ব্যবহার করছেন" "ইনপুট পদ্ধতি" diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 7511455fc0ee..2c4f91ee339c 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1433,6 +1433,7 @@ "Odbij" "Upućen zahtjev za odobrenje" "Upućen zahtjev za dozvolu\nza račun %s." + "Odobrenje je zatražila aplikacija %1$s\nza račun %2$s." "Aplikaciju koristite van poslovnog profila" "Aplikaciju koristite u poslovnom profilu" "Način unosa" diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index ca45c00fb8b3..2b18f3f8a731 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1413,6 +1413,7 @@ "Denega" "Permís sol·licitat" "S\'ha sol·licitat permís\nper al compte %s." + "%1$s ha sol·licitat permís\nper accedir al compte %2$s." "Estàs utilitzant aquesta aplicació fora del perfil de treball." "Estàs utilitzant l\'aplicació al perfil de treball." "Mètode d\'introducció de text" diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index a5f421390d44..fdbf1f80719c 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1453,6 +1453,7 @@ "Odepřít" "Požadováno oprávnění" "Požadováno oprávnění\npro účet %s." + "Aplikace %1$s požádala o přístup\nk účtu %2$s." "Tuto aplikaci používáte mimo svůj pracovní profil." "Tuto aplikaci používáte v pracovním profilu" "Metoda zadávání dat" diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 09e2ea20cd92..70dfcb2fb354 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1413,6 +1413,7 @@ "Afvis" "Der er anmodet om tilladelse" "Der er anmodet om tilladelse\nfor kontoen %s." + "%1$s har anmodet om tilladelse\nfor kontoen %2$s." "Du bruger denne app uden for din arbejdsprofil" "Du bruger denne app i din arbejdsprofil" "Inputmetode" diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 627a3ab6a4fd..dfe712e9f42a 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1413,6 +1413,7 @@ "Ablehnen" "Berechtigung angefordert" "Berechtigung angefordert\nfür Konto %s" + "Berechtigung wurde angefordert von %1$s\nfür das Konto %2$s." "Du verwendest diese App außerhalb deines Arbeitsprofils" "Du verwendest diese App in deinem Arbeitsprofil." "Eingabemethode" diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 9435d34b562e..a8ac541b9840 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1413,6 +1413,7 @@ "Άρνηση" "Απαιτείται άδεια" "Ζητήθηκε άδεια\nγια τον λογαριασμό %s." + "Ζητήθηκε άδεια από την εφαρμογή %1$s\nγια πρόσβαση στον λογαριασμό %2$s." "Χρησιμοποιείτε αυτήν την εφαρμογή εκτός του προφίλ εργασίας σας" "Χρησιμοποιείτε αυτήν την εφαρμογή στο προφίλ εργασίας" "Μέθοδος εισόδου" diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index c748cc80cb23..a0a64c9c1999 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1413,6 +1413,7 @@ "Deny" "Permission requested" "Permission requested\nfor account %s." + "Permission requested by %1$s\nfor account %2$s." "You\'re using this app outside of your work profile" "You\'re using this app in your work profile" "Input Method" diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index d6faf46ee74a..3a82c601090f 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1413,6 +1413,7 @@ "Deny" "Permission requested" "Permission requested\nfor account %s." + "Permission requested by %1$s\nfor account %2$s." "You\'re using this app outside of your work profile" "You\'re using this app in your work profile" "Input Method" diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 695ddb0e4e4e..bdeee0a9809f 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1413,6 +1413,7 @@ "Deny" "Permission requested" "Permission requested\nfor account %s." + "Permission requested by %1$s\nfor account %2$s." "You\'re using this app outside of your work profile" "You\'re using this app in your work profile" "Input Method" diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 596d4b658769..d1302d304a72 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1413,6 +1413,7 @@ "Deny" "Permission requested" "Permission requested\nfor account %s." + "Permission requested by %1$s\nfor account %2$s." "You\'re using this app outside of your work profile" "You\'re using this app in your work profile" "Input Method" diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 2cd891f57281..920808b9b492 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1413,6 +1413,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎Deny‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎Permission requested‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎Permission requested‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎for account ‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎Permission requested by ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎for account ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎You\'re using this app outside of your work profile‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎You\'re using this app in your work profile‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‎Input method‎‏‎‎‏‎" diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 5b216612430e..8ad378d3ecd8 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1413,6 +1413,7 @@ "Denegar" "Permiso solicitado" "Permiso solicitado\npara la cuenta %s" + "%1$s solicitó permiso\npara acceder a la cuenta %2$s." "Estás utilizando esta aplicación fuera del perfil de trabajo." "Estás utilizando esta aplicación en tu perfil de trabajo." "Método de entrada" diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 13f1b5613169..4f6dc6414a97 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1413,6 +1413,7 @@ "Denegar" "Permiso solicitado" "Permiso solicitado\npara la cuenta %s" + "Permiso solicitado por %1$s\npara acceder a la cuenta%2$s." "Estás usando esta aplicación fuera del perfil de trabajo" "Estás usando esta aplicación en tu perfil de trabajo" "Método de introducción de texto" diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index ea6fabe0556f..e58422ab4fee 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1413,6 +1413,7 @@ "Keela" "Taotletud luba" "Luba on taotletud\nkontole %s." + "Rakendus %1$s nõuab luba\nkontole %2$s juurdepääsemiseks." "Kasutate rakendust väljaspool tööprofiili" "Kasutate seda rakendust oma tööprofiilil" "Sisestusmeetod" diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 99012924a3e2..7bd4bfc56949 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1413,6 +1413,7 @@ "Ukatu" "Baimena eskatu da" "Baimena eskatu da \n%s konturako." + "%1$s aplikazioak %2$s kontua atzitzeko baimena\neskatu du." "Laneko profiletik kanpo ari zara aplikazioa erabiltzen" "Laneko profilean ari zara aplikazioa erabiltzen" "Idazketa-metodoa" diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 3dd8173ae4e1..ff775bcab8eb 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1413,6 +1413,7 @@ "مجاز نبودن" "مجوز درخواست شد" "مجوز\nبرای حساب %s درخواست شد." + "%1$s برای دسترسی به حساب %2$s\nدرخواست اجازه کرد." "شما از این برنامه در خارج از نمایه کاری‌تان استفاده می‌کنید" "از این برنامه در نمایه کاری‌تان استفاده می‌کنید" "روش ورودی" diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 527d5a6a8087..87cc6af3b34c 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1413,6 +1413,7 @@ "Kiellä" "Lupa pyydetty" "Pyydetään lupaa\ntilille %s." + "%1$s pyytänyt pääsyä\ntilille %2$s." "Käytät sovellusta muulla kuin työprofiililla" "Käytät sovellusta työprofiililla" "Syöttötapa" diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 8002080a1005..f46e648232e2 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1413,6 +1413,7 @@ "Refuser" "Autorisation demandée" "Autorisation demandée\npour le compte \"%s\"" + "Autorisation demandée par %1$s\npour le compte %2$s." "Vous utilisez cette application en dehors de votre profil professionnel" "Vous utilisez cette application dans votre profil professionnel" "Mode de saisie" diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 7d777542e6f4..6355e621e8e9 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1413,6 +1413,7 @@ "Refuser" "Autorisation demandée" "Autorisation demandée\npour le compte \"%s\"" + "Autorisation demandée par %1$s\npour le compte %2$s." "Vous utilisez cette application en dehors de votre profil professionnel." "Vous utilisez cette application dans votre profil professionnel." "Mode de saisie" diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 7cc6f6934901..86915dd2bb09 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1413,6 +1413,7 @@ "Rexeitar" "Permiso solicitado" "Permiso solicitado\npara a conta %s." + "%1$s solicitou permiso\npara acceder á conta %2$s." "Estás usando esta aplicación fóra do teu perfil de traballo" "Estás usando esta aplicación no teu perfil de traballo" "Método de introdución de texto" diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 758b2a014633..d1bea9bc507b 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1413,6 +1413,7 @@ "નકારો" "પરવાનગીની વિનંતી કરી" "એકાઉન્ટ %s માટે\nપરવાનગીની વિનંતી કરી." + "%2$s એકાઉન્ટ માટે\n%1$s દ્વારા પરવાનગીની વિનંતી કરવામાં આવી." "તમે તમારી કાર્ય પ્રોફાઇલની બહાર આ એપ્લિકેશનનો ઉપયોગ કરી રહ્યાં છો" "તમે તમારી કાર્ય પ્રોફાઇલમાં આ એપ્લિકેશનનો ઉપયોગ કરી રહ્યાં છો" "ઇનપુટ પદ્ધતિ" diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 7fdbb8a60989..481a9deb9ed1 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1413,6 +1413,7 @@ "अस्वीकारें" "अनुमति अनुरोधित" "%s खाते के लिए अनुमति\nका अनुरोध किया गया." + "%1$s ने %2$s खाते को ऐक्सेस\nकरने की अनुमति का अनुरोध किया है." "आप इस ऐप्स का उपयोग अपनी वर्क प्रोफ़ाइल से बाहर कर रहे हैं" "आप इस ऐप्स का उपयोग अपनी वर्क प्रोफ़ाइल में कर रहे हैं" "इनपुट विधि" diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 5d4905fb5680..18327375a156 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1433,6 +1433,7 @@ "Odbij" "Zatražena je dozvola" "Zatražena je dozvola\nza račun %s." + "Aplikacija %1$s zatražila je dopuštenje\nza račun %2$s." "Ovu aplikaciju upotrebljavate izvan svog radnog profila" "Upotrebljavate tu aplikaciju u radnom profilu" "Način unosa" diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 4492eec7d8be..f9c55767e1ca 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1413,6 +1413,7 @@ "Elutasítás" "Az engedélykérés megtörtént" "Az engedélykérés megtörtént\na(z) %s fiók számára." + "A(z) %1$s alkalmazás által kért engedély\na következő fiók számára: %2$s." "Ezt az alkalmazást munkaprofilján kívül használja" "Munkaprofiljában már használja az alkalmazást" "Beviteli mód" diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 2288e68d7b1b..986c3e7259ad 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1413,6 +1413,7 @@ "Մերժել" "Թույլտվության հարցում է արված" "Թույլտվության հարցում է արված\n%s հաշվի համար:" + "Հայցվում է թույլտվություն %1$s հավելվածի կողմից\n%2$s հաշվի համար" "Դուք օգտագործում եք այս հավելվածը ձեր աշխատանքային պրոֆիլից դուրս" "Դուք օգտագործում եք այս հավելվածը ձեր աշխատանքային պրոֆիլում" "Ներածման եղանակը" diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 12d479d95e8e..43238dc2c92b 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1413,6 +1413,7 @@ "Tolak" "Izin dimintakan" "Izin dimintakan\nuntuk akun %s." + "%1$s meminta izin\nuntuk akun %2$s." "Anda menggunakan aplikasi ini di luar profil kerja" "Anda menggunakan aplikasi ini di profil kerja" "Metode masukan" diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 1ca4e619b66c..dde1424f3379 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1413,6 +1413,7 @@ "Hafna" "Beðið um heimild" "Beðið um heimild\nfyrir reikninginn %s." + "Beiðni um heimild frá %1$s\nfyrir reikninginn %2$s." "Þú ert að nota þetta forrit utan vinnusniðsins" "Þú ert að nota þetta forrit á vinnusniðinu þínu" "Innsláttaraðferð" diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 054344ebabe5..83a7ee88d2ac 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1413,6 +1413,7 @@ "Rifiuta" "Autorizzazione richiesta" "Autorizzazione richiesta\nper l\'account %s." + "Autorizzazione richiesta da %1$s\nper l\'account %2$s." "Stai utilizzando l\'app al di fuori del tuo profilo di lavoro" "Stai utilizzando l\'app nel tuo profilo di lavoro" "Metodo inserimento" diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 757041c4d397..c2f78a4921d6 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1453,6 +1453,7 @@ "עדיף שלא" "בקשת הרשאה" "נדרשת הרשאה\nלחשבון %s." + "התבקשה הרשאה על ידי %1$s\nלחשבון %2$s." "אתה משתמש באפליקציה זו מחוץ לפרופיל העבודה שלך" "אתה משתמש באפליקציה זו בפרופיל העבודה שלך" "שיטת קלט" diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 4a8179142c5b..526d9226d6ee 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1413,6 +1413,7 @@ "拒否" "権限がリクエストされました" "次のアカウントにアクセスする権限が\nリクエストされました: %s" + "アカウント %2$s へのアクセス権限が\n%1$s からリクエストされました。" "仕事用プロファイルの外部でこのアプリを使用しています" "仕事用プロファイルでこのアプリを使用しています" "入力方法" diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 7365b05ee513..8102f353a83d 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1413,6 +1413,7 @@ "აკრძალვა" "მოთხოვნილია ნებართვა" "მოთხოვნილია ნებრათვა \nანგარიშისთვის: %s" + "ნებართვა მოთხოვნილია %1$s-ის მიერ\nანგარიშისთვის %2$s." "იყენებთ ამ აპს თქვენს სამუშაო პროფილს მიღმა" "ამ აპს თქვენს სამუშაო პროფილში იყენებთ" "შეყვანის მეთოდი" diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 18476482fa42..e4c354a6b7b6 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1413,6 +1413,7 @@ "Бас тарту" "Рұқсат өтінілді" "Рұқсат \nесептік жазба үшін %s өтінілді." + "%2$s аккаунты үшін %1$s\nқолданбасы арқылы рұқсат сұралды." "Осы қолданбаны жұмыс профиліңізден тыс пайдаланып жатырсыз" "Осы қолданбаны жұмыс профиліңізде пайдаланып жатырсыз" "Енгізу әдісі" diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 95b253f25d78..692f8b0b4d3d 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1413,6 +1413,7 @@ "បដិសេធ" "បាន​ស្នើ​សិទ្ធិ" "បាន​ស្នើ​សិទ្ធិ\nសម្រាប់​គណនី %s ។" + "ការអនុញ្ញាត​ដែលស្នើដោយ %1$s\nសម្រាប់គណនី %2$s។" "អ្នក​កំពុង​ប្រើ​កម្មវិធី​នេះ​នៅ​ខាងក្រៅ​ប្រវត្តិរូប​​ការងារ​របស់​អ្នក" "អ្នក​កំពុង​ប្រើ​កម្មវិធី​នេះ​ក្នុង​ប្រវត្តិរូប​ការងារ​របស់​អ្នក" "វិធីសាស្ត្រ​បញ្ចូល" diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 37d20495cbde..019e6777d9fe 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1413,6 +1413,7 @@ "ನಿರಾಕರಿಸಿ" "ಅನುಮತಿ ವಿನಂತಿಸಲಾಗಿದೆ" "%s ಖಾತೆಗಾಗಿ\n ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ." + "%2$s ಖಾತೆಗಾಗಿ \n %1$s ನಿಂದ ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ." "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನ ಹೊರಗೆ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" "ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ" "ಇನ್‌ಪುಟ್ ವಿಧಾನ" diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 7dcbaa00c476..6b390a0c6a78 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1413,6 +1413,7 @@ "거부" "권한 요청됨" "%s 계정에 대해\n권한 요청" + "%1$s에서 %2$s 계정에 대한\n권한을 요청했습니다" "직장 프로필 외부에서 이 앱을 사용 중입니다." "직장 프로필에서 이 앱을 사용 중입니다." "입력 방법" diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 6420e45fe02e..1eb107152bfe 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -1413,6 +1413,7 @@ "Уруксат берилбейт" "Уруксат талап кылуу" "Кийинки эсепке\nуруксат талап кылынууда: %s." + "%1$s колдонмосу\n%2$s аккаунтуна кирүүгө уруксат сурады." "Бул колдонмо жумуш профилиңиздин сыртында колдонулуп жатат" "Бул колдонмону жумуш профилиңизде пайдаланып жатасыз" "Киргизүү ыкмасы" diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 47bd5a8da60e..57acf482dd1d 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1413,6 +1413,7 @@ "ປະ​ຕິ​ເສດ" "ຕ້ອງການການອະນຸຍາດ" "ຮ້ອງຂໍການກຳນົດສິດ\nສຳລັບບັນຊີ %s ແລ້ວ." + "ຮ້ອງຂໍການອະນຸຍາດໂດຍ %1$s\nສຳລັບບັນຊີ %2$s." "​ທ່ານ​ກຳ​ລັງ​ໃຊ້​ແອັບຯ​ນີ້ນອກ​ໂປຣ​ໄຟລ໌​ບ່ອນ​ເຮັດ​ວຽກ​ຂອງ​ທ່ານ" "​ທ່ານ​ກຳ​ລັງ​ໃຊ້​ແອັບຯ​ນີ້​ໃນ​ໂປຣ​ໄຟລ໌​​ບ່ອນ​ເຮັດ​ວຽກ​ຂອງ​ທ່ານ" "ວິທີການປ້ອນຂໍ້ມູນ" diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 6a4958219732..cb69d1ab99d0 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1453,6 +1453,7 @@ "Atmesti" "Pateikta užklausa dėl leidimo" "Pateikta leidimo užklausa\ndėl %s paskyros" + "Programai „%1$s“ reikalingas leidimas\n, susijęs su paskyra %2$s." "Šią programą naudojate ne darbo profilyje" "Šią programą naudojate darbo profilyje" "Įvesties būdas" diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 8e67568e1718..d5caa65e5187 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1433,6 +1433,7 @@ "Noraidīt" "Atļauja ir pieprasīta." "Atļauja kontam %s\nir pieprasīta." + "Lietotne %1$s pieprasīja atļauju piekļūt \nkontam %2$s." "Šo lietotni izmantojat ārpus sava darba profila" "Jūs izmantojat šo lietotni no sava darba profila." "Ievades metode" diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 92d8e36fbe6b..32ba34c6a89d 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1413,6 +1413,7 @@ "Одбиј" "Побарана е дозвола" "Побарана е дозвола\nза сметка %s." + "%1$s побара дозвола\nза сметката %2$s." "Ја користите апликацијата надвор од работниот профил" "Ја користите апликацијата во работниот профил" "Метод на внес" diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 4a62f7286e63..ab631e9a5f4f 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1413,6 +1413,7 @@ "നിരസിക്കുക" "അനുമതി ആവശ്യമാണ്" "%s എന്ന അക്കൗണ്ടിനായി\nഅനുമതി അഭ്യർത്ഥിച്ചു." + "%2$s അക്കൗണ്ട് ആക്സസ് ചെയ്യാൻ \n%1$s അനുമതി അഭ്യർത്ഥിച്ചു." "നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈലിന് പുറത്ത് ഈ അപ്ലിക്കേഷൻ ഉപയോഗിക്കുന്നു" "നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈലിൽ ഈ അപ്ലിക്കേഷൻ ഉപയോഗിക്കുന്നു" "ടൈപ്പുചെയ്യൽ രീതി" diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index ed77e86a2ad1..768b8cd3a30f 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1413,6 +1413,7 @@ "Татгалзах" "Зөвшөөрөл хүсэв" "%s бүртгэл зөвшөөрөл \n хүссэн" + "%1$s\n нь %2$s бүртгэлд зөвшөөрөл хүссэн." "Та энэ апп-г өөрийн ажлын профайлаас гадуур ашиглаж байна" "Та энэ апп-г өөрийн ажлын профайл дотор ашиглаж байна" "Оруулах арга" diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 46057eea940e..80c490b78712 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1413,6 +1413,7 @@ "नकार द्या" "परवानगीची विनंती केली" "%s खात्यासाठी\nपरवानगीची विनंती केली." + "%1$s ने तुमचे \n%2$s खाते ॲक्सेस करण्यासाठी परवानगीची विनंती केली आहे." "तुम्ही हा अ‍ॅप आपल्‍या कार्य प्रोफाईलच्या बाहेर वापरत आहात" "तुम्ही हा अ‍ॅप आपल्या कार्य प्रोफाईलमध्‍ये वापरत आहात" "इनपुट पद्धत" diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index f8efcfd5216a..cf91addc0b9d 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1413,6 +1413,7 @@ "Nafi" "Kebenaran diminta" "Kebenaran diminta\nuntuk akaun %s." + "Kebenaran diminta oleh %1$s\nuntuk akaun %2$s." "Anda menggunakan apl ini di luar profil kerja anda" "Anda menggunakan apl ini dalam profil kerja anda" "Kaedah input" diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index c0e3fb9a30fb..675c1837dd75 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1413,6 +1413,7 @@ "ငြင်းပယ်ရန်" "ခွင့်ပြုချက် တောင်းခံထားခြင်း" "အကောင့် %s အတွက် \n ခွင့်ပြုချက် တောင်းခံထားပြီး" + "%2$s အကောင့်အတွက်\n%1$s က ခွင့်ပြုချက် တောင်းခံထားသည်။" "သင်သည် ဒီအက်ပ်ကို သင့်အလုပ်ပရိုဖိုင် ပြင်ပတွင် အသုံးပြုနေ၏" "သင်သည် ဒီအက်ပ်ကို သင်၏ အလုပ် ပရိုဖိုင် ထဲမှာ အသုံးပြုနေသည်" "ထည့်သွင်းရန်နည်းလမ်း" diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 4a89f37af147..7b99ec98ec51 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1413,6 +1413,7 @@ "Avslå" "Tillatelse forespurt" "Tillatelse forespurt\nfor kontoen %s." + "Tillatelse forespurt av %1$s\nfor kontoen %2$s." "Du bruker denne appen utenfor arbeidsprofilen" "Du bruker denne appen i jobbprofilen din" "Inndatametode" diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 343d0b22e4d4..ba55cc3b7521 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1413,6 +1413,7 @@ "अस्वीकार गर्नुहोस्" "अनुरोध गरिएको अनुमति" \n"खाता %sको लागि अनुरोध गरिएको अनुमति।" + "%1$s ले %2$s खाता चलाउने\nअनुमति मागेको छ।" "तपाईं तपाईंको कार्य प्रोफाइल बाहिर यो एप प्रयोग गरिरहनु भएको छ" "तपाईं आफ्नो कार्य प्रोफाइलमा यो एप प्रयोग गरिरहनु भएको छ" "इनपुट विधि" diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 1a8ce28e3561..0937031d99bd 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1411,8 +1411,8 @@ "Verzoek om toegang" "Toestaan" "Weigeren" - "Rechten gevraagd" - "Rechten gevraagd\nvoor account %s." + "Toestemming gevraagd" + "Toestemming gevraagd\nvoor account %s." "Je gebruikt deze app buiten je werkprofiel" "U gebruikt deze app in je werkprofiel" "Invoermethode" diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 386d9e05124d..9af7cf3a69be 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1413,6 +1413,7 @@ "ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ" "ଅନୁମତି ଅନୁରୋଧ କରାଯାଇଛି" "%s ଆକାଉଣ୍ଟ ପାଇଁ ଅନୁମତି\n ଅନୁରୋଧ କରାଯାଇଛି।" + "%2$s ଆକାଉଣ୍ଟକୁ ଆକ୍ସେସ୍ ପାଇଁ\n%1$s ଦ୍ୱାରା ଅନୁମତି ନିମନ୍ତେ ଅନୁରୋଧ କରାଯାଇଛି।" "ଆପଣ ନିଜର ୱର୍କ ପ୍ରୋଫାଇଲ୍‌ ବାହାରେ ଏହି ଆପ୍‌ର ପ୍ରୟୋଗ କରୁଛନ୍ତି" "ଆପଣ ନିଜ ୱର୍କ ପ୍ରୋଫାଇଲ୍‌ରେ ଏହି ଆପ୍‌ର ବ୍ୟବହାର କରୁଛନ୍ତି" "ଇନପୁଟ୍ ପଦ୍ଧତି" diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 77aceb7264f4..caded7e6a008 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1413,6 +1413,7 @@ "ਅਸਵੀਕਾਰ ਕਰੋ" "ਅਨੁਮਤੀ ਦੀ ਬੇਨਤੀ ਕੀਤੀ" "%s ਖਾਤੇ ਲਈ ਅਨੁਮਤੀ ਦੀ ਬੇਨਤੀ ਕੀਤੀ\n।" + "%1$s ਨੇ %2$s ਖਾਤੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ\nਦੀ ਇਜਾਜ਼ਤ ਲਈ ਬੇਨਤੀ ਕੀਤੀ।" "ਤੁਸੀਂ ਇਹ ਐਪ ਆਪਣੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਦੇ ਬਾਹਰ ਵਰਤ ਰਹੇ ਹੋ" "ਤੁਸੀਂ ਇਹ ਐਪ ਆਪਣੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿੱਚ ਵਰਤ ਰਹੇ ਹੋ" "ਇਨਪੁੱਟ ਵਿਧੀ" diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 9b6edd21b6ac..1a84dba1e880 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1453,8 +1453,8 @@ "Odmów" "Prośba o pozwolenie" "Prośba o pozwolenie\ndotyczące konta %s" - "Używasz tej aplikacji poza profilem służbowym" - "Używasz tej aplikacji w swoim profilu służbowym" + "Używasz tej aplikacji poza profilem do pracy" + "Używasz tej aplikacji w swoim profilu do pracy" "Sposób wprowadzania tekstu" "Synchronizacja" "Ułatwienia dostępu" diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index ec34e4e6f8c8..35ccbdb06e5a 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1413,6 +1413,7 @@ "Negar" "Permissão solicitada" "Permissão solicitada\npara a conta %s." + "Permissão solicitada pelo app %1$s\npara a conta %2$s." "Este app está sendo usado fora de seu perfil de trabalho" "Você está usando este app em seu perfil de trabalho" "Método de entrada" diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 6de3cdffefae..387d9a79db94 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1413,6 +1413,7 @@ "Recusar" "Permissão solicitada" "Permissão solicitada\npara a conta %s." + "Autorização solicitada pela app %1$s\npara a conta %2$s." "Está a utilizar esta app fora do seu perfil de trabalho" "Está a utilizar esta app no seu perfil de trabalho" "Método de entrada" diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index ec34e4e6f8c8..35ccbdb06e5a 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1413,6 +1413,7 @@ "Negar" "Permissão solicitada" "Permissão solicitada\npara a conta %s." + "Permissão solicitada pelo app %1$s\npara a conta %2$s." "Este app está sendo usado fora de seu perfil de trabalho" "Você está usando este app em seu perfil de trabalho" "Método de entrada" diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index bffa4570ef2a..b77851b76cf6 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1433,6 +1433,7 @@ "Refuzați" "Permisiune solicitată" "Permisiune solicitată\npentru contul %s." + "Permisiune solicitată de %1$s\npentru contul %2$s." "Utilizați această aplicație în afara profilului de serviciu" "Utilizați această aplicație în profilul de serviciu" "Metodă de intrare" diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 20407b131907..2fab14de573d 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1453,6 +1453,7 @@ "Отклонить" "Разрешение запрошено" "Требуется разрешение\nдля аккаунта %s." + "Разрешение запрошено приложением \"%1$s\"\nдля аккаунта %2$s." "Это приложение используется в личном профиле" "Вы перешли в рабочий профиль" "Способ ввода" diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index dc8f723af411..825fdfa3af0b 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1413,6 +1413,7 @@ "ප්‍රතික්ෂේප කරන්න" "අවසර ඉල්ලා සිටී" "%s ගිණුම සඳහා\nඅවසර ඉල්ලන ලදි." + "%2$s ගිණුම සඳහා %1$s\n විසින් ඉල්ලූ අවසරය" "මෙම යෙදුම ඔබගේ කාර්යාල පැතිකඩින් පිට දී ඔබ භාවිතා කරයි" "මෙම යෙදුම ඔබගේ පුද්ගලික කොටසේ ඔබ භාවිතා කරයි" "ආදාන ක්‍රමය" diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 624d55492603..cfc5f394150e 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1453,6 +1453,7 @@ "Zamietnuť" "Vyžaduje sa povolenie" "Vyžaduje sa oprávnenie\npre účet %s" + "Povolenia, ktoré aplikácia %1$s požaduje\npre účet %2$s." "Túto aplikáciu používate mimo svojho pracovného profilu" "Túto aplikáciu používate vo svojom pracovnom profile" "Metóda vstupu" diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 32cfb18501d2..0c681ec9a49f 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1453,6 +1453,7 @@ "Zavrni" "Zahtevano je dovoljenje" "Zahtevano je dovoljenje\nza račun %s." + "Aplikacija %1$s je zahtevala dovoljenje\nza račun %2$s." "Aplikacijo uporabljate zunaj delovnega profila" "To aplikacijo uporabljate v delovnem profilu" "Način vnosa" diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 0c2b143bc808..6e0436ed8884 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1413,6 +1413,7 @@ "Moho" "Kërkohet leje" "Kërkohet leje\npër llogarinë %s." + "Kërkohet leja nga %1$s\npër llogarinë %2$s." "Po e përdor këtë aplikacion jashtë profilit tënd të punës" "Këtë aplikacion po e përdor në profilin tënd të punës" "Metoda e hyrjeve" diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index d2e78651ef22..d69ed48f09e5 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1433,6 +1433,7 @@ "Одбиј" "Затражена је дозвола" "Затражена је дозвола\nза налог %s" + "%1$s тражи дозволу \nза налог %2$s." "Користите ову апликацију изван пословног профила" "Користите ову апликацију на пословном профилу" "Метод уноса" diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 66bf9c2d8030..6688fe210b9b 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1413,6 +1413,7 @@ "Neka" "Begärd behörighet" "Begärd behörighet\nför kontot %s." + "Behörighet har begärts av %1$s\nför kontot %2$s." "Du använder den här appen i din jobbprofil" "Du använder den här appen i din jobbprofil" "Indatametod" diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 8aca7d259c30..ef35a6fe4420 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1413,6 +1413,7 @@ "Kataza" "Idhini imeitishwa" "Idhini imeombwa\nya akaunti%s." + "%1$s imeombwa ruhusa\nkwenye akaunti ya %2$s." "Unatumia programu hii nje ya wasifu wako wa kazini" "Unatumia programu hii kwenye wasifu wako wa kazini" "Mbinu ya uingizaji" diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 2eb628c7afb0..d6410beb93cd 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1413,6 +1413,7 @@ "நிராகரி" "அனுமதிக் கோரப்பட்டது" "%s கணக்கிற்கான அனுமதி\nகோரப்பட்டது." + "%1$s ஆப்ஸ்\n%2$s கணக்கிற்கான அனுமதியைக் கோருகிறது." "இந்தப் பயன்பாட்டைப் பணிக் கணக்கிற்கு வெளியே பயன்படுத்துகிறீர்கள்" "பணிக் கணக்கில் பயன்பாட்டைப் பயன்படுத்துகிறீர்கள்" "உள்ளீட்டு முறை" diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index f379214734c9..988169835979 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1413,8 +1413,8 @@ "తిరస్కరించండి" "అనుమతి అభ్యర్థించబడింది" "ఖాతా %s కోసం\nఅనుమతి అభ్యర్థించబడింది." - "మీరు మీ కార్యాలయ ప్రొఫైల్‌కు వెలుపల ఈ యాప్‌ను ఉపయోగిస్తున్నారు" - "మీరు మీ కార్యాలయ ప్రొఫైల్‌లో ఈ యాప్‌ను ఉపయోగిస్తున్నారు" + "మీరు మీ కార్యాలయ ప్రొఫైల్‌కు వెలుపల ఈ అనువర్తనాన్ని ఉపయోగిస్తున్నారు" + "మీరు మీ కార్యాలయ ప్రొఫైల్‌లో ఈ అనువర్తనాన్ని ఉపయోగిస్తున్నారు" "ఇన్‌పుట్ పద్ధతి" "సమకాలీకరణ" "యాక్సెస్ సామర్థ్యం" diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 4c6be820d4ff..08718ffd614f 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1413,6 +1413,7 @@ "ปฏิเสธ" "การอนุญาตที่ขอ" "การอนุญาตที่ขอ\nสำหรับบัญชี %s" + "สิทธิ์ที่ %1$s ขอ\nสำหรับบัญชี %2$s" "คุณกำลังใช้แอปนี้นอกโปรไฟล์งานของคุณ" "คุณกำลังใช้แอปนี้ในโปรไฟล์งานของคุณ" "วิธีป้อนข้อมูล" diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 0bd750f1aa33..0a7aa0e7ab14 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1413,6 +1413,7 @@ "Tanggihan" "Hiniling ang pahintulot" "Hiniling ang pahintulot\npara sa account na %s." + "Humiling ang %1$s ng pahintulot\npara sa account na %2$s." "Ginagamit mo ang app na ito sa labas ng iyong profile sa trabaho" "Ginagamit mo ang app na ito sa iyong profile sa trabaho" "Pamamaraan ng pag-input" diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 87d97599a9bb..a44882498142 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1413,6 +1413,7 @@ "Reddet" "İzin istendi" "%s hesabı için\nizin isteğinde bulunuldu." + "%1$s uygulaması, %2$s hesabı\niçin izin istedi" "Bu uygulamayı iş profilinizin dışında kullanıyorsunuz" "Bu uygulamayı iş profilinizde kullanıyorsunuz" "Giriş yöntemi" diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 067045aa0860..4605db387a44 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1453,6 +1453,7 @@ "Забор." "Потрібен дозвіл" "Запитано дозвіл\nдля облікового запису %s." + "Додаток %1$s запитує дозвіл\nна доступ до облікового запису %2$s." "Ви використовуєте цей додаток за межами робочого профілю" "Ви використовуєте цей додаток у своєму робочому профілі" "Метод введення" diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 04800531784e..06dc0751e76e 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1413,6 +1413,7 @@ "مسترد کریں" "اجازت طلب کی گئی" "اکاؤنٹ %s کیلئے\nاجازت طلب کی گئی۔" + "%2$s اکاؤنٹ کیلئے\n%1$s نے اجازت کی درخواست کی۔" "آپ اس ایپ کا استعمال اپنے دفتری پروفائل کے باہر کر رہے ہیں" "آپ اس ایپ کو اپنے دفتری پروفائل میں استعمال کر رہے ہیں" "اندراج کا طریقہ" diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index e3ed7829f1ec..f09aaa67c43a 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1413,6 +1413,7 @@ "Rad etish" "Ruxsat so‘raldi" "%s hisobi uchun\nruxsat so‘raldi" + "Ruxsat %1$s ilovasi tomonidan \n%2$s hisobi uchun soʻralgan." "Siz ushbu ilovadan ishchi profilingizdan tashqarida foydalanmoqdasiz" "Siz ushbu ilovadan ishchi profilingizda foydalanmoqdasiz" "Kiritish uslubi" diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 8e0cc2ead613..d19ecf567c54 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1413,6 +1413,7 @@ "Từ chối" "Đã yêu cầu quyền" "Đã yêu cầu quyền\ncho tài khoản %s." + "Quyền do %1$s\nyêu cầu cho tài khoản %2$s." "Bạn đang sử dụng ứng dụng này bên ngoài hồ sơ công việc của mình" "Bạn đang sử dụng ứng dụng này trong hồ sơ công việc của mình" "Phương thức nhập" diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index a645cd7db90a..74dde0ec4550 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1413,6 +1413,7 @@ "拒绝" "权限请求" "应用对帐号 %s\n 提出权限请求。" + "“%1$s”请求获得以下帐号的访问权限:\n%2$s。" "您目前是在工作资料之外使用此应用" "您目前是在工作资料内使用此应用" "输入法" diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 9eef703670ef..6cae6439ffd3 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1413,6 +1413,7 @@ "拒絕" "已要求權限" "%s 帳戶的\n權限要求。" + "「%1$s」要求帳戶 %2$s\n的權限" "您目前並未透過公司檔案使用這個應用程式" "您目前透過公司檔案使用這個應用程式" "輸入法" diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index c4f5dc8d3661..58af46e65f60 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1413,6 +1413,7 @@ "拒絕" "已要求權限" "帳戶 %s 已提出\n權限要求。" + "「%1$s」要求授予\n%2$s 帳戶的權限。" "你目前並非透過工作資料夾使用這個應用程式" "你目前透過工作設定檔使用這個應用程式" "輸入法" diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index bb5360edb0b8..c84b44720467 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1413,6 +1413,7 @@ "Yala" "Imvume Iceliwe" "Imvume Iceliwe \n ye-akhawunti %s" + "Imvume ecelwe yi-%1$s\nye-akhawunti ye-%2$s." "Usebenzisa lolu hlelo lokusebenza ngaphandle kwephrofayela yakho yomsebenzi" "Usebenzisa lolu hlelo lokusebenza kuphrofayela yakho yomsebenzi" "Indlela yokufakwayo" diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java new file mode 100644 index 000000000000..282fdad294eb --- /dev/null +++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import android.content.ComponentName; +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AutomaticZenRuleTest { + private static final String CLASS = "android.app.AutomaticZenRule"; + + @Test + public void testLongFields_inConstructor() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + // test both variants where there's an owner, and where there's a configuration activity + AutomaticZenRule rule1 = new AutomaticZenRule( + longString, // name + new ComponentName("pkg", longString), // owner + null, // configuration activity + longUri, // conditionId + null, // zen policy + 0, // interruption filter + true); // enabled + + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule1.getName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule1.getConditionId().toString().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule1.getOwner().getClassName().length()); + + AutomaticZenRule rule2 = new AutomaticZenRule( + longString, // name + null, // owner + new ComponentName(longString, "SomeClassName"), // configuration activity + longUri, // conditionId + null, // zen policy + 0, // interruption filter + false); // enabled + + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule2.getName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule2.getConditionId().toString().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule2.getConfigurationActivity().getPackageName().length()); + } + + @Test + public void testLongFields_inSetters() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + AutomaticZenRule rule = new AutomaticZenRule( + "sensible name", + new ComponentName("pkg", "ShortClass"), + null, + Uri.parse("uri://short"), + null, 0, true); + + rule.setName(longString); + rule.setConditionId(longUri); + rule.setConfigurationActivity(new ComponentName(longString, longString)); + + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule.getName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule.getConditionId().toString().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule.getConfigurationActivity().getPackageName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + rule.getConfigurationActivity().getClassName().length()); + } + + @Test + public void testLongInputsFromParcel() { + // Create a rule with long fields, set directly via reflection so that we can confirm that + // a rule with too-long fields that comes in via a parcel has its fields truncated directly. + AutomaticZenRule rule = new AutomaticZenRule( + "placeholder", + new ComponentName("place", "holder"), + null, + Uri.parse("uri://placeholder"), + null, 0, true); + + try { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + Field name = Class.forName(CLASS).getDeclaredField("name"); + name.setAccessible(true); + name.set(rule, longString); + Field conditionId = Class.forName(CLASS).getDeclaredField("conditionId"); + conditionId.setAccessible(true); + conditionId.set(rule, longUri); + Field owner = Class.forName(CLASS).getDeclaredField("owner"); + owner.setAccessible(true); + owner.set(rule, new ComponentName(longString, longString)); + Field configActivity = Class.forName(CLASS).getDeclaredField("configurationActivity"); + configActivity.setAccessible(true); + configActivity.set(rule, new ComponentName(longString, longString)); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (ClassNotFoundException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + + Parcel parcel = Parcel.obtain(); + rule.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + AutomaticZenRule fromParcel = new AutomaticZenRule(parcel); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, fromParcel.getName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + fromParcel.getConditionId().toString().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + fromParcel.getConfigurationActivity().getPackageName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + fromParcel.getConfigurationActivity().getClassName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + fromParcel.getOwner().getPackageName().length()); + assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, + fromParcel.getOwner().getClassName().length()); + } +} diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java new file mode 100644 index 000000000000..625c66a4c60e --- /dev/null +++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +import android.os.Parcel; +import android.test.AndroidTestCase; +import android.text.TextUtils; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NotificationChannelGroupTest { + private final String CLASS = "android.app.NotificationChannelGroup"; + + @Test + public void testLongStringFields() { + NotificationChannelGroup group = new NotificationChannelGroup("my_group_01", "groupName"); + + try { + String longString = Strings.repeat("A", 65536); + Field mName = Class.forName(CLASS).getDeclaredField("mName"); + mName.setAccessible(true); + mName.set(group, longString); + Field mId = Class.forName(CLASS).getDeclaredField("mId"); + mId.setAccessible(true); + mId.set(group, longString); + Field mDescription = Class.forName(CLASS).getDeclaredField("mDescription"); + mDescription.setAccessible(true); + mDescription.set(group, longString); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + Parcel parcel = Parcel.obtain(); + group.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannelGroup fromParcel = + NotificationChannelGroup.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getId().length()); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getName().length()); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, + fromParcel.getDescription().length()); + } + + @Test + public void testNullableFields() { + NotificationChannelGroup group = new NotificationChannelGroup("my_group_01", null); + + Parcel parcel = Parcel.obtain(); + group.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannelGroup fromParcel = + NotificationChannelGroup.CREATOR.createFromParcel(parcel); + assertEquals(group.getId(), fromParcel.getId()); + assertTrue(TextUtils.isEmpty(fromParcel.getName())); + } +} diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java new file mode 100644 index 000000000000..647bfe84231d --- /dev/null +++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static junit.framework.TestCase.assertEquals; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NotificationChannelTest { + private final String CLASS = "android.app.NotificationChannel"; + + @Test + public void testLongStringFields() { + NotificationChannel channel = new NotificationChannel("id", "name", 3); + + try { + String longString = Strings.repeat("A", 65536); + Field mName = Class.forName(CLASS).getDeclaredField("mName"); + mName.setAccessible(true); + mName.set(channel, longString); + Field mId = Class.forName(CLASS).getDeclaredField("mId"); + mId.setAccessible(true); + mId.set(channel, longString); + Field mDesc = Class.forName(CLASS).getDeclaredField("mDesc"); + mDesc.setAccessible(true); + mDesc.set(channel, longString); + Field mParentId = Class.forName(CLASS).getDeclaredField("mParentId"); + mParentId.setAccessible(true); + mParentId.set(channel, longString); + Field mGroup = Class.forName(CLASS).getDeclaredField("mGroup"); + mGroup.setAccessible(true); + mGroup.set(channel, longString); + Field mConversationId = Class.forName(CLASS).getDeclaredField("mConversationId"); + mConversationId.setAccessible(true); + mConversationId.set(channel, longString); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + Parcel parcel = Parcel.obtain(); + channel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getId().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getName().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getDescription().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getParentChannelId().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getGroup().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getConversationId().length()); + } + + @Test + public void testLongAlertFields() { + NotificationChannel channel = new NotificationChannel("id", "name", 3); + + channel.setSound(Uri.parse("content://" + Strings.repeat("A",65536)), + Notification.AUDIO_ATTRIBUTES_DEFAULT); + channel.setVibrationPattern(new long[65550/2]); + + Parcel parcel = Parcel.obtain(); + channel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannel.MAX_VIBRATION_LENGTH, + fromParcel.getVibrationPattern().length); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getSound().toString().length()); + } +} diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index f20220c4ab9b..670aefd21d36 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -48,6 +48,7 @@ public void testToStringWithPathOnly() { public void testParcelling() { parcelAndUnparcel(Uri.parse("foo:bob%20lee")); parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(Uri.fromParts("https", "www.google.com", null)); parcelAndUnparcel(new Uri.Builder() .scheme("http") .authority("crazybob.org") @@ -873,9 +874,62 @@ private static void assertUnparcelLegacyPart_fails(Class partClass) throws Excep Throwable targetException = expected.getTargetException(); // Check that the exception was thrown for the correct reason. assertEquals("Unknown representation: 0", targetException.getMessage()); + } finally { + parcel.recycle(); } } + private Uri buildUriFromRawParcel(boolean argumentsEncoded, + String scheme, + String authority, + String path, + String query, + String fragment) { + // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). + final int representation = argumentsEncoded ? 1 : 2; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeInt(3); // hierarchical + parcel.writeString8(scheme); + parcel.writeInt(representation); + parcel.writeString8(authority); + parcel.writeInt(representation); + parcel.writeString8(path); + parcel.writeInt(representation); + parcel.writeString8(query); + parcel.writeInt(representation); + parcel.writeString8(fragment); + parcel.setDataPosition(0); + return Uri.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + public void testUnparcelMalformedPath() { + // Regression tests for b/171966843. + + // Test cases with arguments encoded (covering testing `scheme` * `authority` options). + Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/@evil.com", uri0.toString()); + Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); + Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + assertEquals("http::/@evil.com", uri2.toString()); + Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + assertEquals("@evil.com", uri3.toString()); + + // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). + Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/%40evil.com", uriA.toString()); + Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + assertEquals("//google.com/%40evil.com", uriB.toString()); + Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + assertEquals("http::/%40evil.com", uriC.toString()); + Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java new file mode 100644 index 000000000000..42629ba41287 --- /dev/null +++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConditionTest { + private static final String CLASS = "android.service.notification.Condition"; + + @Test + public void testLongFields_inConstructors() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + // Confirm strings are truncated via short constructor + Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); + + assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); + + // Confirm strings are truncated via long constructor + Condition cond2 = new Condition(longUri, longString, longString, longString, + -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); + + assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); + } + + @Test + public void testLongFields_viaParcel() { + // Set fields via reflection to force them to be long, then parcel and unparcel to make sure + // it gets truncated upon unparcelling. + Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", + Condition.STATE_TRUE); + + try { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + Field id = Class.forName(CLASS).getDeclaredField("id"); + id.setAccessible(true); + id.set(cond, longUri); + Field summary = Class.forName(CLASS).getDeclaredField("summary"); + summary.setAccessible(true); + summary.set(cond, longString); + Field line1 = Class.forName(CLASS).getDeclaredField("line1"); + line1.setAccessible(true); + line1.set(cond, longString); + Field line2 = Class.forName(CLASS).getDeclaredField("line2"); + line2.setAccessible(true); + line2.set(cond, longString); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (ClassNotFoundException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + + Parcel parcel = Parcel.obtain(); + cond.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Condition fromParcel = new Condition(parcel); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); + } +} diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 8cb7e1b95245..83ff725b5b75 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -20,6 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -29,6 +33,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Parcel; @@ -50,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -499,4 +506,85 @@ public ActivityOptions createSharedElementActivityOptions( return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_nestedViews() { + final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); + + final RemoteViews inner = new RemoteViews(mPackage, 33); + final Uri imageUriI = Uri.parse("content://inner/image"); + final Icon icon1 = Icon.createWithContentUri("content://inner/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://inner/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://inner/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://inner/icon4"); + inner.setImageViewUri(R.id.image, imageUriI); + inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + outer.addView(R.id.layout, inner); + + Consumer visitor = (Consumer) spy(Consumer.class); + outer.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriI)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java index 2c0f40d528d2..cedc4d2fb8e6 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java @@ -126,8 +126,8 @@ public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyS if (mChunkLength > mChunkSizeMax) { throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax - + " Actual: " + mChunkLength); + "Chunk size exceeded max chunk size. Max: " + mChunkSizeMax + + " Actual: " + mChunkLength); } if (mChunkLength >= mChunkSizeThreshold) { @@ -139,18 +139,28 @@ public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyS } else if (opResult.resultCode != KeyStore.NO_ERROR) { throw KeyStore.getKeyStoreException(opResult.resultCode); } - if (opResult.inputConsumed <= 0) { - throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, - "Keystore consumed 0 of " + mChunkLength + " bytes provided."); - } else if (opResult.inputConsumed > mChunkLength) { + if (opResult.inputConsumed == 0) { + // Some KM implementations do not consume data in certain block modes unless a + // full block of data was presented. + if (inputLength > 0) { + // More input is available, but it wasn't included into the previous chunk + // because the chunk reached its maximum permitted size. + // Shouldn't have happened. + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH, + "Keystore consumed nothing from max-sized chunk: " + mChunkLength + + " bytes"); + } + } else if (opResult.inputConsumed > mChunkLength || opResult.inputConsumed < 0) { throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, - "Keystore consumed more input than provided. Provided: " - + mChunkLength + ", consumed: " + opResult.inputConsumed); + "Keystore consumed more input than provided (or inputConsumed was " + + "negative." + + " Provided: " + mChunkLength + + ", consumed: " + opResult.inputConsumed); } mChunkLength -= opResult.inputConsumed; if (mChunkLength > 0) { - // Partialy consumed, shift chunk contents + // Partially consumed, shift chunk contents ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength); } diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java index dc1e99fd6a4f..2b978f759a13 100644 --- a/location/java/android/location/GpsNavigationMessage.java +++ b/location/java/android/location/GpsNavigationMessage.java @@ -262,12 +262,8 @@ public GpsNavigationMessage createFromParcel(Parcel parcel) { parcel.readByteArray(data); navigationMessage.setData(data); - if (parcel.dataAvail() >= Integer.SIZE) { - int status = parcel.readInt(); - navigationMessage.setStatus((short) status); - } else { - navigationMessage.setStatus(STATUS_UNKNOWN); - } + int status = parcel.readInt(); + navigationMessage.setStatus((short) status); return navigationMessage; } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 9deeb8fbab16..918a9d8943dd 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -801,10 +801,10 @@ public static Uri getActualDefaultRingtoneUri(Context context, int type) { return ringtoneUri; } - + /** * Sets the {@link Uri} of the default sound for a given sound type. - * + * * @param context A context used for querying. * @param type The type whose default sound should be set. One of * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or @@ -825,6 +825,21 @@ public static void setActualDefaultRingtoneUri(Context context, int type, Uri ri if(!isInternalRingtoneUri(ringtoneUri)) { ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); } + + if (ringtoneUri != null) { + final String mimeType = resolver.getType(ringtoneUri); + if (mimeType == null) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: failure to find mimeType (no access from this context?)"); + return; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: associated mimeType:" + mimeType + " is not an audio type"); + return; + } + } + Settings.System.putStringForUser(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 5ac059be2010..ac15164c4174 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -66,8 +66,8 @@ public void onCreate(Bundle savedInstanceState) { final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0); setTitle(Html.fromHtml(getString( R.string.confirmation_title, - getCallingAppName(), - selectedDevice.getDisplayName()), 0)); + Html.escapeHtml(getCallingAppName()), + Html.escapeHtml(selectedDevice.getDisplayName())), 0)); mPairButton = findViewById(R.id.button_pair); mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice)); getService().mSelectedDevice = selectedDevice; @@ -76,7 +76,8 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.device_chooser); mPairButton = findViewById(R.id.button_pair); mPairButton.setVisibility(View.GONE); - setTitle(Html.fromHtml(getString(R.string.chooser_title, getCallingAppName()), 0)); + setTitle(Html.fromHtml(getString(R.string.chooser_title, + Html.escapeHtml(getCallingAppName())), 0)); mDeviceListView = findViewById(R.id.device_list); final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter; mDeviceListView.setAdapter(adapter); diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java index bd9e760acfda..c8bcabff1094 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.function.Predicate; /** * Class for managing services matching a given intent and requesting a given permission. @@ -51,12 +52,13 @@ public class ServiceListing { private final HashSet mEnabledServices = new HashSet<>(); private final List mServices = new ArrayList<>(); private final List mCallbacks = new ArrayList<>(); + private final Predicate mValidator; private boolean mListening; private ServiceListing(Context context, String tag, String setting, String intentAction, String permission, String noun, - boolean addDeviceLockedFlags) { + boolean addDeviceLockedFlags, Predicate validator) { mContentResolver = context.getContentResolver(); mContext = context; mTag = tag; @@ -65,6 +67,7 @@ private ServiceListing(Context context, String tag, mPermission = permission; mNoun = noun; mAddDeviceLockedFlags = addDeviceLockedFlags; + mValidator = validator; } public void addCallback(Callback callback) { @@ -137,7 +140,6 @@ public void reload() { final PackageManager pmWrapper = mContext.getPackageManager(); List installedServices = pmWrapper.queryIntentServicesAsUser( new Intent(mIntentAction), flags, user); - for (ResolveInfo resolveInfo : installedServices) { ServiceInfo info = resolveInfo.serviceInfo; @@ -148,6 +150,9 @@ public void reload() { + mPermission); continue; } + if (mValidator != null && !mValidator.test(info)) { + continue; + } mServices.add(info); } for (Callback callback : mCallbacks) { @@ -194,6 +199,7 @@ public static class Builder { private String mPermission; private String mNoun; private boolean mAddDeviceLockedFlags = false; + private Predicate mValidator; public Builder(Context context) { mContext = context; @@ -224,6 +230,11 @@ public Builder setNoun(String noun) { return this; } + public Builder setValidator(Predicate validator) { + mValidator = validator; + return this; + } + /** * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results @@ -236,7 +247,7 @@ public Builder setAddDeviceLockedFlags(boolean addDeviceLockedFlags) { public ServiceListing build() { return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun, - mAddDeviceLockedFlags); + mAddDeviceLockedFlags, mValidator); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 8968340b65f4..c5e66bef9653 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -1117,14 +1117,16 @@ public String getSavedNetworkSummary() { * Returns the display title for the AccessPoint, such as for an AccessPointPreference's title. */ public String getTitle() { - if (isPasspoint()) { + if (isPasspoint() && !TextUtils.isEmpty(mConfig.providerFriendlyName)) { return mConfig.providerFriendlyName; - } else if (isPasspointConfig()) { + } else if (isPasspointConfig() && !TextUtils.isEmpty(mProviderFriendlyName)) { return mProviderFriendlyName; - } else if (isOsuProvider()) { + } else if (isOsuProvider() && !TextUtils.isEmpty(mOsuProvider.getFriendlyName())) { return mOsuProvider.getFriendlyName(); - } else { + } else if (!TextUtils.isEmpty(getSsidStr())) { return getSsidStr(); + } else { + return ""; } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index bcabec858487..46ecbd45a860 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -147,6 +147,17 @@ public void testSsidIsSpannableString_returnFalse() { assertThat(ssid instanceof SpannableString).isFalse(); } + @Test + public void testCompareTo_GivesNull() { + WifiConfiguration spyConfig = spy(new WifiConfiguration()); + + when(spyConfig.isPasspoint()).thenReturn(true); + spyConfig.providerFriendlyName = null; + AccessPoint passpointAp = new AccessPoint(mContext, spyConfig); + + assertThat(passpointAp.getTitle()).isEqualTo(""); + } + @Test public void testCompareTo_GivesActiveBeforeInactive() { AccessPoint activeAp = new TestAccessPointBuilder(mContext).setActive(true).build(); diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index 3756c3bf7704..723160d68771 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -34,6 +34,7 @@ android_robolectric_test { srcs: ["src/**/*.java"], static_libs: [ "SettingsLib-robo-testutils", + "androidx.test.core", ], java_resource_dirs: ["config"], instrumentation_for: "SettingsLibShell", diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java index f7fd25b9fb7d..7ff0988c494d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java @@ -18,20 +18,35 @@ import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.provider.Settings; +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class ServiceListingTest { @@ -39,16 +54,97 @@ public class ServiceListingTest { private static final String TEST_INTENT = "com.example.intent"; private ServiceListing mServiceListing; + private Context mContext; + private PackageManager mPm; @Before public void setUp() { - mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application) + mPm = mock(PackageManager.class); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getPackageManager()).thenReturn(mPm); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setPermission("testPermission") + .build(); + } + + @Test + public void testValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setValidator(info -> { + if (info.packageName.equals("pkg")) { + return true; + } + return false; + }) + .setPermission("testPermission") + .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(1); + assertThat(captor.getValue().get(0)).isEqualTo(s1); + } + + @Test + public void testNoValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) .setTag("testTag") .setSetting(TEST_SETTING) .setNoun("testNoun") .setIntentAction(TEST_INTENT) .setPermission("testPermission") .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(2); } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index b95d34f2966b..d3c10574ea13 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1781,6 +1781,9 @@ private boolean mutateSystemSetting(String name, String value, int runAsUserId, cacheName = Settings.System.ALARM_ALERT_CACHE; } if (cacheName != null) { + if (!isValidAudioUri(name, value)) { + return false; + } final File cacheFile = new File( getRingtoneCacheDir(owningUserId), cacheName); cacheFile.delete(); @@ -1813,6 +1816,34 @@ owningUserId, name, value, null, false, getCallingPackage(), } } + private boolean isValidAudioUri(String name, String uri) { + if (uri != null) { + Uri audioUri = Uri.parse(uri); + if (Settings.AUTHORITY.equals( + ContentProvider.getAuthorityWithoutUserId(audioUri.getAuthority()))) { + // Don't accept setting the default uri to self-referential URIs like + // Settings.System.DEFAULT_RINGTONE_URI, which is an alias to the value of this + // setting. + return false; + } + final String mimeType = getContext().getContentResolver().getType(audioUri); + if (mimeType == null) { + Slog.e(LOG_TAG, + "mutateSystemSetting for setting: " + name + " URI: " + audioUri + + " ignored: failure to find mimeType (no access from this context?)"); + return false; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg") + || mimeType.equals("application/x-flac"))) { + Slog.e(LOG_TAG, + "mutateSystemSetting for setting: " + name + " URI: " + audioUri + + " ignored: associated mimeType: " + mimeType + " is not an audio type"); + return false; + } + } + return true; + } + private boolean hasWriteSecureSettingsPermission() { // Write secure settings is a more protected permission. If caller has it we are good. if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) @@ -2955,6 +2986,15 @@ public Setting getSettingLocked(int type, int userId, String name) { return settingsState.getSettingLocked(name); } + private boolean shouldExcludeSettingFromReset(Setting setting, String prefix) { + // If a prefix was specified, exclude settings whose names don't start with it. + if (prefix != null && !setting.getName().startsWith(prefix)) { + return true; + } + // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty. + return Secure.SECURE_FRP_MODE.equals(setting.getName()); + } + public void resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ @@ -2977,7 +3017,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo Setting setting = settingsState.getSettingLocked(name); if (packageName.equals(setting.getPackageName())) { if ((tag != null && !tag.equals(setting.getTag())) - || (prefix != null && !setting.getName().startsWith(prefix))) { + || shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -2997,7 +3037,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName(), INVALID_UID, userId)) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3017,7 +3057,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName(), INVALID_UID, userId)) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { @@ -3040,7 +3080,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 6678cf6f1033..c252db11eaf2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -47,6 +47,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; @@ -384,9 +385,11 @@ public void resetSettingDefaultValueLocked(String name) { Setting newSetting = new Setting(name, oldSetting.getValue(), null, oldSetting.getPackageName(), oldSetting.getTag(), false, oldSetting.getId()); + int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), 0, + oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); + checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); mSettings.put(name, newSetting); - updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue, - newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); + updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); scheduleWriteIfNeededLocked(); } } @@ -419,6 +422,13 @@ public boolean insertSettingLocked(String name, String value, String tag, Setting oldState = mSettings.get(name); String oldValue = (oldState != null) ? oldState.value : null; String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null; + String newDefaultValue = makeDefault ? value : oldDefaultValue; + + int newSize = getNewMemoryUsagePerPackageLocked(packageName, + oldValue == null ? name.length() : 0 /* deltaKeySize */, + oldValue, value, oldDefaultValue, newDefaultValue); + checkNewMemoryUsagePerPackageLocked(packageName, newSize); + Setting newState; if (oldState != null) { @@ -439,8 +449,7 @@ oldValue, tag, makeDefault, getUserIdFromKey(mKey), addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); - updateMemoryUsagePerPackageLocked(packageName, oldValue, value, - oldDefaultValue, newState.getDefaultValue()); + updateMemoryUsagePerPackageLocked(packageName, newSize); scheduleWriteIfNeededLocked(); @@ -558,13 +567,18 @@ public boolean deleteSettingLocked(String name) { } Setting oldState = mSettings.remove(name); + if (oldState == null) { + return false; + } + int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, + -name.length() /* deltaKeySize */, + oldState.value, null, oldState.defaultValue, null); FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); - updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, - null, oldState.defaultValue, null); + updateMemoryUsagePerPackageLocked(oldState.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); @@ -581,20 +595,23 @@ public boolean resetSettingLocked(String name) { } Setting setting = mSettings.get(name); + if (setting == null) { + return false; + } Setting oldSetting = new Setting(setting); String oldValue = setting.getValue(); String oldDefaultValue = setting.getDefaultValue(); + int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, 0, oldValue, + oldDefaultValue, oldDefaultValue, oldDefaultValue); + checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize); + if (!setting.reset()) { return false; } - String newValue = setting.getValue(); - String newDefaultValue = setting.getDefaultValue(); - - updateMemoryUsagePerPackageLocked(setting.packageName, oldValue, - newValue, oldDefaultValue, newDefaultValue); + updateMemoryUsagePerPackageLocked(setting.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting); @@ -702,38 +719,49 @@ public void dumpHistoricalOperations(PrintWriter pw) { } @GuardedBy("mLock") - private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, - String newValue, String oldDefaultValue, String newDefaultValue) { - if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { - return; - } + private boolean isExemptFromMemoryUsageCap(String packageName) { + return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED + || SYSTEM_PACKAGE_NAME.equals(packageName); + } - if (SYSTEM_PACKAGE_NAME.equals(packageName)) { + @GuardedBy("mLock") + private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize) + throws IllegalStateException { + if (isExemptFromMemoryUsageCap(packageName)) { return; } + if (newSize > mMaxBytesPerAppPackage) { + throw new IllegalStateException("You are adding too many system settings. " + + "You should stop using system settings for app specific data" + + " package: " + packageName); + } + } + @GuardedBy("mLock") + private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeySize, + String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) { + if (isExemptFromMemoryUsageCap(packageName)) { + return 0; + } + final Integer currentSize = mPackageToMemoryUsage.get(packageName); final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; final int newValueSize = (newValue != null) ? newValue.length() : 0; final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0; final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0; - final int deltaSize = newValueSize + newDefaultValueSize + final int deltaSize = deltaKeySize + newValueSize + newDefaultValueSize - oldValueSize - oldDefaultValueSize; + return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0); + } - Integer currentSize = mPackageToMemoryUsage.get(packageName); - final int newSize = Math.max((currentSize != null) - ? currentSize + deltaSize : deltaSize, 0); - - if (newSize > mMaxBytesPerAppPackage) { - throw new IllegalStateException("You are adding too many system settings. " - + "You should stop using system settings for app specific data" - + " package: " + packageName); + @GuardedBy("mLock") + private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) { + if (isExemptFromMemoryUsageCap(packageName)) { + return; } - if (DEBUG) { Slog.i(LOG_TAG, "Settings for package: " + packageName + " size: " + newSize + " bytes."); } - mPackageToMemoryUsage.put(packageName, newSize); } @@ -1549,4 +1577,11 @@ public static boolean isSystemPackage(Context context, String packageName, int u return false; } } + + @VisibleForTesting + public int getMemoryUsage(String packageName) { + synchronized (mLock) { + return mPackageToMemoryUsage.getOrDefault(packageName, 0); + } + } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index 8ff595b3bc53..0fa0f728d4ce 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -463,6 +463,31 @@ private void testResetModeTrustedDefaultsCommon(int type) throws Exception { } } + // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other + // settings are reset. But it should still be possible to explicitly set its value. + @Test + public void testSecureFrpModeSettingCannotBeReset() throws Exception { + final String name = Settings.Secure.SECURE_FRP_MODE; + final String origValue = getSetting(SETTING_TYPE_GLOBAL, name); + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false); + try { + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) { + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES); + resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS); + } + // The value should still be "1". It should not have been reset to null. + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + // It should still be possible to explicitly set the value to "0". + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false); + assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name)); + } finally { + setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false); + assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name)); + } + } + private void doTestQueryStringInBracketsViaProviderApiForType(int type) { // Make sure we have a clean slate. deleteStringViaProviderApi(type, FAKE_SETTING_NAME); diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 9f448af7f344..36b892def814 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -21,6 +21,8 @@ import org.xmlpull.v1.XmlSerializer; +import com.google.common.base.Strings; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -280,4 +282,132 @@ private SettingsState getSettingStateObject() { settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING); return settingsState; } + + public void testInsertSetting_memoryUsage() { + SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + // No exception should be thrown when there is no cap + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + settingsState.deleteSettingLocked(SETTING_NAME); + + settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + // System package doesn't have memory usage limit + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, SYSTEM_PACKAGE); + settingsState.deleteSettingLocked(SETTING_NAME); + + // Should not throw if usage is under the cap + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19975), + null, false, "p1"); + settingsState.deleteSettingLocked(SETTING_NAME); + try { + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + try { + settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull()); + try { + settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + } + + public void testMemoryUsagePerPackage() { + SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + + // Test inserting one key with default + final String testKey1 = SETTING_NAME; + final String testValue1 = Strings.repeat("A", 100); + settingsState.insertSettingLocked(testKey1, testValue1, null, true, TEST_PACKAGE); + int expectedMemUsage = testKey1.length() + testValue1.length() + + testValue1.length() /* size for default */; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test inserting another key + final String testKey2 = SETTING_NAME + "2"; + settingsState.insertSettingLocked(testKey2, testValue1, null, false, TEST_PACKAGE); + expectedMemUsage += testKey2.length() + testValue1.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test updating first key with new default + final String testValue2 = Strings.repeat("A", 300); + settingsState.insertSettingLocked(testKey1, testValue2, null, true, TEST_PACKAGE); + expectedMemUsage += (testValue2.length() - testValue1.length()) * 2; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test updating first key without new default + final String testValue3 = Strings.repeat("A", 50); + settingsState.insertSettingLocked(testKey1, testValue3, null, false, TEST_PACKAGE); + expectedMemUsage -= testValue2.length() - testValue3.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test updating second key + settingsState.insertSettingLocked(testKey2, testValue2, null, false, TEST_PACKAGE); + expectedMemUsage -= testValue1.length() - testValue2.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test resetting key + settingsState.resetSettingLocked(testKey1); + expectedMemUsage += testValue2.length() - testValue3.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test resetting default value + settingsState.resetSettingDefaultValueLocked(testKey1); + expectedMemUsage -= testValue2.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test deletion + settingsState.deleteSettingLocked(testKey2); + expectedMemUsage -= testValue2.length() + testKey2.length() /* key is deleted too */; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test another package with a different key + final String testPackage2 = TEST_PACKAGE + "2"; + final String testKey3 = SETTING_NAME + "3"; + settingsState.insertSettingLocked(testKey3, testValue1, null, true, testPackage2); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + final int expectedMemUsage2 = testKey3.length() + testValue1.length() * 2; + assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2)); + + // Test system package + settingsState.insertSettingLocked(testKey1, testValue1, null, true, SYSTEM_PACKAGE); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2)); + assertEquals(0, settingsState.getMemoryUsage(SYSTEM_PACKAGE)); + + // Test invalid value + try { + settingsState.insertSettingLocked(testKey1, Strings.repeat("A", 20001), null, false, + TEST_PACKAGE); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + + // Test invalid key + try { + settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", null, false, + TEST_PACKAGE); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE)); + } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6bee19745c87..c536b6cb94df 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -114,6 +114,7 @@ + diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index ad70a67421b9..628608e36160 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -1079,6 +1079,7 @@ "Maak toe" "Hervat" "Instellings" + "%1$s loop tans" "Onaktief, gaan program na" "Fout, probeer tans weer …" "Nie gekry nie" diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 2900060b84b6..0af1f4619b8b 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -1079,6 +1079,7 @@ "አሰናብት" "ከቆመበት ቀጥል" "ቅንብሮች" + "%1$s እያሄደ ነው" "ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ" "ስህተት፣ እንደገና በመሞከር ላይ…" "አልተገኘም" diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 3d38d5ed088c..4f1f5e6f27f1 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -1103,6 +1103,7 @@ "إغلاق" "استئناف التشغيل" "الإعدادات" + "\"%1$s\" قيد التشغيل" "غير نشط، تحقّق من التطبيق." "حدث خطأ، جارٍ إعادة المحاولة…" "لم يتم العثور عليه." diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index c8e5e3675489..7cb23613ec7f 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -1079,6 +1079,7 @@ "অগ্ৰাহ্য কৰক" "পুনৰ আৰম্ভ কৰক" "ছেটিংসমূহ" + "%1$s চলি আছে" "সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক" "আসোঁৱাহ, পুনৰ চেষ্টা কৰি আছে…" "বিচাৰি পোৱা নগ’ল" diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 701927f32714..cfdb8ef85541 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -1079,6 +1079,7 @@ "İmtina edin" "Davam edin" "Ayarlar" + "%1$s işləyir" "Aktiv deyil, tətbiqi yoxlayın" "Xəta, yenidən cəhd edilir…" "Tapılmadı" diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 6c01ceed9fff..1a1817783190 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1085,6 +1085,7 @@ "Odbaci" "Nastavi" "Podešavanja" + "Aplikacija %1$s je pokrenuta" "Neaktivno. Vidite aplikaciju" "Greška, pokušava se ponovo…" "Nije pronađeno" diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index bcc534790ed4..85e3dc9171f6 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -1091,6 +1091,7 @@ "Адхіліць" "Узнавіць" "Налады" + "%1$s працуе" "Неактыўна, праверце праграму" "Памылка, паўторная спроба…" "Не знойдзена" diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 6b8844d67d01..a60b2681e549 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -1079,6 +1079,7 @@ "Отхвърляне" "Възобновяване" "Настройки" + "%1$s се изпълнява" "Неактивно, проверете прилож." "Грешка. Извършва се нов опит…" "Не е намерено" diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index d68f46337f2b..7848ec133f58 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -1079,6 +1079,7 @@ "খারিজ করুন" "আবার চালু করুন" "সেটিংস" + "%1$s চলছে" "বন্ধ আছে, অ্যাপ চেক করুন" "সমস্যা, আবার চেষ্টা করা হচ্ছে…" "খুঁজে পাওয়া যায়নি" diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index d3ef3ba9eff6..3ab91c389a58 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -1085,6 +1085,7 @@ "Odbaci" "Nastavi" "Postavke" + "Aplikacija %1$s je pokrenuta" "Neaktivno, vidite aplikaciju" "Greška, ponovni pokušaj…" "Nije pronađeno" diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 251464d3cee8..7bc747a236fa 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -1079,6 +1079,7 @@ "Ignora" "Reprèn" "Configuració" + "%1$s s\'està executant" "Inactiu; comprova l\'aplicació" "Error. S\'està tornant a provar…" "No s\'ha trobat" diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index e4b32f9fe1a0..f61337f32aca 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -1091,6 +1091,7 @@ "Zavřít" "Pokračovat" "Nastavení" + "Aplikace %1$s je spuštěna" "Neaktivní, zkontrolujte aplikaci" "Chyba. Nový pokus…" "Nenalezeno" diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 66885745b479..59bc8408fdc1 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -1079,6 +1079,7 @@ "Luk" "Genoptag" "Indstillinger" + "%1$s kører" "Inaktiv. Tjek appen" "Fejl. Prøver igen…" "Ikke fundet" diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 3b37ca3c4add..6b242519cb12 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -1079,6 +1079,7 @@ "Ablehnen" "Fortsetzen" "Einstellungen" + "%1$s wird ausgeführt" "Inaktiv – sieh in der App nach" "Fehler. Neuer Versuch…" "Nicht gefunden" diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 89331973c532..fb1dda51af3d 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -1079,6 +1079,7 @@ "Παράβλεψη" "Συνέχιση" "Ρυθμίσεις" + "Η εφαρμογή %1$s εκτελείται" "Ανενεργό, έλεγχος εφαρμογής" "Προέκυψε σφάλμα. Επανάληψη…" "Δεν βρέθηκε." diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 0adf19447e8c..f5bbb71946a3 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -1079,6 +1079,7 @@ "Dismiss" "Resume" "Settings" + "%1$s is running" "Inactive, check app" "Error, retrying…" "Not found" diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 9265f7141050..0c083c785002 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -1079,6 +1079,7 @@ "Dismiss" "Resume" "Settings" + "%1$s is running" "Inactive, check app" "Error, retrying…" "Not found" diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 0adf19447e8c..f5bbb71946a3 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -1079,6 +1079,7 @@ "Dismiss" "Resume" "Settings" + "%1$s is running" "Inactive, check app" "Error, retrying…" "Not found" diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 0adf19447e8c..f5bbb71946a3 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -1079,6 +1079,7 @@ "Dismiss" "Resume" "Settings" + "%1$s is running" "Inactive, check app" "Error, retrying…" "Not found" diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 2120e835c308..a03048fb7dbe 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -1079,6 +1079,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎Dismiss‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎Resume‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎Settings‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ is running‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎Error, retrying…‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎Not found‎‏‎‎‏‎" diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 0a3df72bba05..1643a33b4962 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -1079,6 +1079,7 @@ "Descartar" "Reanudar" "Configuración" + "%1$s se está ejecutando" "Inactivo. Verifica la app" "Hubo un error. Reintentando…" "No se encontró" diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 8eb5bf50758b..e9807db232ae 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -1079,6 +1079,7 @@ "Cerrar" "Reanudar" "Ajustes" + "%1$s se está ejecutando" "Inactivo, comprobar aplicación" "Error; reintentando…" "No se ha encontrado" diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a41e69e9f91e..d7e385ab75ce 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -1079,6 +1079,7 @@ "Loobu" "Jätka" "Seaded" + "%1$s töötab" "Passiivne, vaadake rakendust" "Viga, proovitakse uuesti …" "Ei leitud" diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 8922c85fdbca..1e01faa89336 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -1079,6 +1079,7 @@ "Baztertu" "Berrekin" "Ezarpenak" + "%1$s abian da" "Inaktibo; egiaztatu aplikazioa" "Errorea. Berriro saiatzen…" "Ez da aurkitu" diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index c2872a8da59e..f8799168dff4 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -1079,6 +1079,7 @@ "رد کردن" "ازسرگیری" "تنظیمات" + "%1$s در حال اجرا است" "غیرفعال، برنامه را بررسی کنید" "خطا، درحال تلاش مجدد…" "پیدا نشد" diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 19794e0a4199..449f1f3dc141 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -1079,6 +1079,7 @@ "Ohita" "Jatka" "Asetukset" + "%1$s on käynnissä" "Epäaktiivinen, tarkista sovellus" "Virhe, yritetään uudelleen…" "Ei löydy" diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 0459ca945702..7098f780fede 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -1079,6 +1079,7 @@ "Fermer" "Reprendre" "Paramètres" + "%1$s en cours d\'exécution" "Délai expiré, vérifiez l\'appli" "Erreur, nouvelle tentative…" "Introuvable" diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 7b6ad6bd7b52..8499b2cf366f 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -1079,6 +1079,7 @@ "Fermer" "Reprendre" "Paramètres" + "%1$s est en cours d\'exécution" "Délai expiré, vérifier l\'appli" "Erreur. Nouvelle tentative…" "Introuvable" diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index a210c33297d9..3111bf4e192a 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -1079,6 +1079,7 @@ "Ignorar" "Retomar" "Configuración" + "%1$s estase executando" "Inactivo. Comproba a app" "Erro. Tentando de novo…" "Non se atopou" diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index db782167c7a4..46eee2db2f87 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1079,6 +1079,7 @@ "છોડી દો" "ફરી શરૂ કરો" "સેટિંગ" + "%1$s ચાલી રહી છે" "નિષ્ક્રિય, ઍપને ચેક કરો" "ભૂલ, ફરી પ્રયાસ કરી રહ્યા છીએ…" "મળ્યું નથી" diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 766a45cff538..07576bdcd3f2 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -1079,6 +1079,7 @@ "खारिज करें" "फिर से शुरू करें" "सेटिंग" + "%1$s चालू है" "काम नहीं कर रहा, ऐप जांचें" "कोई गड़बड़ी हुई, फिर से कोशिश की जा रही है…" "कंट्रोल नहीं है" diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index d22c319cc0e5..57613aadee2f 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -1085,6 +1085,7 @@ "Odbaci" "Nastavi" "Postavke" + "Aplikacija %1$s je pokrenuta" "Neaktivno, provjerite aplik." "Pogreška, pokušavamo ponovo…" "Nije pronađeno" diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 0a37bf8f75c5..fa2cf6adf25c 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -1079,6 +1079,7 @@ "Elvetés" "Folytatás" "Beállítások" + "A(z) %1$s jelenleg fut" "Inaktív, ellenőrizze az appot" "Hiba, újrapróbálkozás…" "Nem található" diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index cfd0c0bec4d1..f2887e2ba8ce 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -1079,6 +1079,7 @@ "Փակել" "Շարունակել" "Կարգավորումներ" + "%1$s հավելվածն աշխատում է" "Ակտիվ չէ, ստուգեք հավելվածը" "Սխալ. նորից ենք փորձում…" "Չի գտնվել" diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 85e58f387c7e..1309a123c5f9 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -1079,6 +1079,7 @@ "Tutup" "Lanjutkan" "Setelan" + "%1$s sedang berjalan" "Nonaktif, periksa aplikasi" "Error, mencoba lagi..." "Tidak ditemukan" diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 324abb508490..36bc1421b8fa 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -1079,6 +1079,7 @@ "Hunsa" "Halda áfram" "Stillingar" + "%1$s er í gangi" "Óvirkt, athugaðu forrit" "Villa, reynir aftur…" "Fannst ekki" diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 8dcb5bc20303..85bbc152eb38 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -1079,6 +1079,7 @@ "Ignora" "Riprendi" "Impostazioni" + "%1$s è in esecuzione" "Inattivo, controlla l\'app" "Errore. Nuovo tentativo…" "Controllo non trovato" diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 33a7621853db..8bb79b9b8a6e 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -1091,6 +1091,7 @@ "סגירה" "המשך" "הגדרות" + "אפליקציית %1$s פועלת" "לא פעיל, יש לבדוק את האפליקציה" "שגיאה, מתבצע ניסיון חוזר…" "לא נמצא" diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 91488fa7094f..da2454e34ce8 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -1079,6 +1079,7 @@ "閉じる" "再開" "設定" + "%1$s を実行しています" "無効: アプリをご確認ください" "エラー。再試行しています…" "見つかりませんでした" diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index a2e1b7f25198..bf4ffc3b0cda 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -1079,6 +1079,7 @@ "დახურვა" "გაგრძელება" "პარამეტრები" + "%1$s გაშვებულია" "არააქტიურია, გადაამოწმეთ აპი" "შეცდომა, ხელახალი მცდელობა…" "ვერ მოიძებნა" diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 331f25e1a616..e4556d4a16e6 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -1079,6 +1079,7 @@ "Жабу" "Жалғастыру" "Параметрлер" + "%1$s қосулы тұр" "Өшірулі. Қолданба тексеріңіз." "Қате, әрекет қайталануда…" "Табылмады" diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 1020948bf631..c39c9be9f0d4 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -1079,6 +1079,7 @@ "ច្រាន​ចោល" "បន្ត" "ការកំណត់" + "%1$s កំពុង​ដំណើរការ" "អសកម្ម ពិនិត្យមើល​កម្មវិធី" "បញ្ហា កំពុងព្យាយាម​ម្ដងទៀត…" "រកមិន​ឃើញទេ" diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index b9fc90576391..2ecc0a138b81 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -1079,6 +1079,7 @@ "ವಜಾಗೊಳಿಸಿ" "ಪುನರಾರಂಭಿಸಿ" "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" + "%1$s ರನ್ ಆಗುತ್ತಿದೆ" "ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ" "ದೋಷ, ಮರುಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ…" "ಕಂಡುಬಂದಿಲ್ಲ" diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index f5aadc85eab4..540fee6e651a 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -1079,6 +1079,7 @@ "닫기" "다시 시작" "설정" + "%1$s 실행 중" "비활성. 앱을 확인하세요." "오류 발생, 다시 시도 중…" "찾을 수 없음" diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index d1b6a09b4d67..4fa31fa2f561 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -1079,6 +1079,7 @@ "Жабуу" "Улантуу" "Жөндөөлөр" + "%1$s иштеп жатат" "Жигерсиз. Колдонмону текшериңиз" "Ката, дагы аракет жасалууда…" "Табылган жок" diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index daae298d0317..ef5f2e8d77fe 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -1079,6 +1079,7 @@ "ປິດໄວ້" "ສືບຕໍ່" "ການຕັ້ງຄ່າ" + "%1$s ກຳລັງເຮັດວຽກຢູ່" "ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ" "ຜິດພາດ, ກໍາລັງ​ລອງ​ໃໝ່…" "ບໍ່ພົບ" diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 5607584e7351..a4feb80d856d 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -1091,6 +1091,7 @@ "Atsisakyti" "Tęsti" "Nustatymai" + "„%1$s“ vykdoma" "Neaktyvu, patikrinkite progr." "Klaida, bandoma iš naujo…" "Nerasta" diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 7c64e26136bb..6d27691a82eb 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -1085,6 +1085,7 @@ "Nerādīt" "Atsākt" "Iestatījumi" + "Lietotne %1$s darbojas" "Neaktīva, pārbaudiet lietotni" "Radās kļūda. Mēģina vēlreiz…" "Netika atrasta" diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 4e7c06370b0f..5b91d4f753a6 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -1079,6 +1079,7 @@ "Отфрли" "Продолжи" "Поставки" + "%1$s работи" "Неактивна, провери апликација" "Грешка, повторен обид…" "Не е најдено" diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index dc07b609756d..6502e5666ea5 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1079,6 +1079,7 @@ "ഡിസ്‌മിസ് ചെയ്യുക" "പുനരാരംഭിക്കുക" "ക്രമീകരണം" + "%1$s റൺ ചെയ്യുന്നു" "നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ" "പിശക്, വീണ്ടും ശ്രമിക്കുന്നു…" "കണ്ടെത്തിയില്ല" diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 4f85c057fd7b..cf6ec15572e5 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -1079,6 +1079,7 @@ "Хаах" "Үргэлжлүүлэх" "Тохиргоо" + "%1$s ажиллаж байна" "Идэвхгүй байна, аппыг шалгана уу" "Алдаа, дахин оролдож байна…" "Олдсонгүй" diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 1a608dae038f..17d874d4d2b8 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -1079,6 +1079,7 @@ "डिसमिस करा" "पुन्हा सुरू करा" "सेटिंग्ज" + "%1$s रन होत आहे" "निष्क्रिय, ॲप तपासा" "एरर, पुन्हा प्रयत्न करत आहे…" "आढळले नाही" diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index de2fcd817b7b..1eb74f5a85c8 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1079,6 +1079,7 @@ "Tolak" "Sambung semula" "Tetapan" + "%1$s sedang dijalankan" "Tidak aktif, semak apl" "Ralat, mencuba semula…" "Tidak ditemukan" diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 07400bb4f180..bdda228802cd 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -1079,6 +1079,7 @@ "ပယ်ရန်" "ဆက်လုပ်ရန်" "ဆက်တင်များ" + "%1$s ပွင့်နေပါသည်" "ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ" "မှားသွားသည်၊ ပြန်စမ်းနေသည်…" "မတွေ့ပါ" diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index ead1fd5ab488..0561d6e45596 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -1079,6 +1079,7 @@ "Lukk" "Gjenoppta" "Innstillinger" + "%1$s kjører" "Inaktiv. Sjekk appen" "Feil. Prøver igjen …" "Ikke funnet" diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 4af7294beecf..f75a41b420cc 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -1079,6 +1079,7 @@ "हटाउनुहोस्" "सुचारु गर्नुहोस्" "सेटिङ" + "%1$s चलिरहेको छ" "निष्क्रिय छ, एप जाँच गर्नु…" "त्रुटि भयो, फेरि प्रयास गर्दै…" "फेला परेन" diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index bf2d757dd697..722e22683796 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -1079,6 +1079,7 @@ "Sluiten" "Hervatten" "Instellingen" + "%1$s is actief" "Inactief, check de app" "Fout. Opnieuw proberen…" "Niet gevonden" diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index fd1134960f1e..538e40647795 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -1079,6 +1079,7 @@ "ଖାରଜ କରନ୍ତୁ" "ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ" "ସେଟିଂସ୍" + "%1$s ଚାଲୁଛି" "ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ" "ତ୍ରୁଟି, ପୁଣି ଚେଷ୍ଟା କରୁଛି…" "ମିଳିଲା ନାହିଁ" diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 7da4635b77e9..59b2e883e833 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -1079,6 +1079,7 @@ "ਖਾਰਜ ਕਰੋ" "ਮੁੜ-ਚਾਲੂ ਕਰੋ" "ਸੈਟਿੰਗਾਂ" + "%1$s ਚੱਲ ਰਿਹਾ ਹੈ" "ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ" "ਗੜਬੜ, ਮੁੜ ਕੋਸ਼ਿਸ਼ ਹੋ ਰਹੀ ਹੈ…" "ਨਹੀਂ ਮਿਲਿਆ" diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 56ed3de4cd88..5bcee051f286 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -1091,6 +1091,7 @@ "Odrzuć" "Wznów" "Ustawienia" + "Aplikacja %1$s jest uruchomiona" "Nieaktywny, sprawdź aplikację" "Błąd, próbuję jeszcze raz…" "Nie znaleziono" diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 860c372eec80..0012c2fe1786 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -1079,6 +1079,7 @@ "Dispensar" "Retomar" "Configurações" + "%1$s está em execução" "Inativo, verifique o app" "Erro. Tentando novamente…" "Não encontrado" diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index b491bd6b8f74..d918042d4fd5 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -1079,6 +1079,7 @@ "Ignorar" "Retomar" "Definições" + "%1$s em execução" "Inativa. Consulte a app." "Erro. A tentar novamente…" "Não encontrado." diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 860c372eec80..0012c2fe1786 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -1079,6 +1079,7 @@ "Dispensar" "Retomar" "Configurações" + "%1$s está em execução" "Inativo, verifique o app" "Erro. Tentando novamente…" "Não encontrado" diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 6046383df330..775d945aa226 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -1085,6 +1085,7 @@ "Închideți" "Reia" "Setări" + "%1$s rulează" "Inactiv, verificați aplicația" "Eroare, se încearcă din nou…" "Nu s-a găsit" diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 405cbdb22a48..62e3e23d79fe 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -1091,6 +1091,7 @@ "Скрыть" "Возобновить" "Настройки" + "Приложение \"%1$s\" запущено" "Нет ответа. Проверьте приложение." "Ошибка. Повторная попытка…" "Не найдено." diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 8c061fcea8ae..95dcc4e7bd24 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -1079,6 +1079,7 @@ "ඉවත ලන්න" "නැවත පටන් ගන්න" "සැකසීම්" + "%1$s ධාවනය වේ" "අක්‍රියයි, යෙදුම පරීක්ෂා කරන්න" "දෝෂයකි, නැවත උත්සාහ කරමින්…" "හමු නොවිණි" diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 4a29d87f44b0..6cc01dde73ac 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -1091,6 +1091,7 @@ "Zavrieť" "Pokračovať" "Nastavenia" + "Aplikácia %1$s je spustená" "Neaktívne, preverte aplikáciu" "Chyba, skúša sa znova…" "Nenájdené" diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index d5de4d3f0d30..220951a27c83 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -1091,6 +1091,7 @@ "Opusti" "Nadaljuj" "Nastavitve" + "%1$s se izvaja" "Neaktivno, poglejte aplikacijo" "Napaka, vnovični poskus …" "Ni mogoče najti" diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index de8090136376..bccdbbe62c3f 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -1079,6 +1079,7 @@ "Hiq" "Vazhdo" "Cilësimet" + "%1$s po ekzekutohet" "Joaktive, kontrollo aplikacionin" "Gabim, po provohet përsëri" "Nuk u gjet" diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 425ce213c1c3..2963464060e3 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1085,6 +1085,7 @@ "Одбаци" "Настави" "Подешавања" + "Апликација %1$s је покренута" "Неактивно. Видите апликацију" "Грешка, покушава се поново…" "Није пронађено" diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 27de6d27b3f9..1c01285b64f0 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -1079,6 +1079,7 @@ "Stäng" "Återuppta" "Inställningar" + "%1$s körs" "Inaktiv, kolla appen" "Fel, försöker igen …" "Hittades inte" diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index fb1ae5569009..29ad94aab806 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -1079,6 +1079,7 @@ "Ondoa" "Endelea" "Mipangilio" + "%1$s inatumika" "Haitumiki, angalia programu" "Hitilafu, inajaribu tena…" "Hakipatikani" diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index e085e63d781f..bbf84562be81 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -1079,6 +1079,7 @@ "மூடுக" "தொடர்க" "அமைப்புகள்" + "%1$s இயங்கிக் கொண்டிருக்கிறது" "செயலில் இல்லை , சரிபார்க்கவும்" "பிழை, மீண்டும் முயல்கிறது…" "இல்லை" diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 865821f9f2a7..84d42870ee1a 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -1079,6 +1079,7 @@ "విస్మరించు" "కొనసాగించండి" "సెట్టింగ్‌లు" + "%1$s రన్ అవుతోంది" "ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి" "లోపం, మళ్లీ ప్రయత్నిస్తోంది..." "కనుగొనబడలేదు" diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index b542be31cd08..d288b5cfc0f4 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1079,6 +1079,7 @@ "ปิด" "เล่นต่อ" "การตั้งค่า" + "%1$s กำลังทำงาน" "ไม่มีการใช้งาน โปรดตรวจสอบแอป" "มีข้อผิดพลาด กำลังลองอีกครั้ง…" "ไม่พบ" diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 238fbc92b1ef..98754b1fcae4 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -1079,6 +1079,7 @@ "I-dismiss" "Ituloy" "Mga Setting" + "Tumatakbo ang %1$s" "Hindi aktibo, tingnan ang app" "Nagka-error, sinusubukan ulit…" "Hindi nahanap" diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 093f18f699a9..5f0bcece16a4 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -1079,6 +1079,7 @@ "Kapat" "Devam ettir" "Ayarlar" + "%1$s çalışıyor" "Devre dışı, uygulamaya bakın" "Hata, yeniden deneniyor…" "Bulunamadı" diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 52e41ff57059..55d892c9102c 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -1091,6 +1091,7 @@ "Закрити" "Відновити" "Налаштування" + "%1$s працює" "Неактивно, перейдіть у додаток" "Помилка. Повторна спроба…" "Не знайдено" diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 0a3853ecfc04..0041155be392 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -1079,6 +1079,7 @@ "برخاست کریں" "دوبارہ شروع کریں" "ترتیبات" + "%1$s چل رہی ہے" "غیر فعال، ایپ چیک کریں" "خرابی، دوبارہ کوشش کی جا رہی ہے…" "نہیں ملا" diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 59240ee1754a..58b4658a492c 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -1079,6 +1079,7 @@ "Yopish" "Davom etish" "Sozlamalar" + "%1$s ishlamoqda" "Nofaol. Ilovani tekshiring" "Xato, qayta urinilmoqda…" "Topilmadi" diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index e51036a745e3..9790b69ad52e 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -1079,6 +1079,7 @@ "Đóng" "Tiếp tục" "Cài đặt" + "%1$s đang chạy" "Không hoạt động, hãy kiểm tra ứng dụng" "Lỗi, đang thử lại…" "Không tìm thấy" diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index b42c89d30634..bd120fe34f6b 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -1079,6 +1079,7 @@ "关闭" "继续播放" "设置" + "“%1$s”正在运行" "无效,请检查应用" "出现错误,正在重试…" "未找到" diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index a1e7054c9faa..a5d8b58287cb 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -1079,6 +1079,7 @@ "關閉" "繼續播放" "設定" + "「%1$s」執行中" "已停用,請檢查應用程式" "發生錯誤,正在重試…" "找不到" diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index ed114c488fff..ef3129e97b70 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -1079,6 +1079,7 @@ "關閉" "繼續播放" "設定" + "「%1$s」執行中" "無效,請查看應用程式" "發生錯誤,正在重試…" "找不到控制項" diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index c8356fa25c11..d9448035837e 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -1079,6 +1079,7 @@ "Cashisa" "Qalisa kabusha" "Izilungiselelo" + "I-%1$s iyasebenza" "Akusebenzi, hlola uhlelo lokusebenza" "Iphutha, iyazama futhi…" "Ayitholakali" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 37952c437e8d..1e7ffffc26e3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2834,6 +2834,8 @@ Resume Settings + + %1$s is running Inactive, check app diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index a687bb89fd75..5c742f25209e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -201,7 +201,6 @@ private void startListening() { filter.addAction(DISABLE_PLUGIN); filter.addDataScheme("package"); mContext.registerReceiver(this, filter, PluginInstanceManager.PLUGIN_PERMISSION, null); - mContext.registerReceiver(this, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index e99245fa438f..f9e4340b4fc8 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -33,6 +33,7 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import java.util.NoSuchElementException; @@ -204,7 +205,7 @@ private void dismiss(int userId) { hide(); if (mKeyguardCallback != null) { mKeyguardCallback.dismiss(/* securityVerified= */ true, userId, - /* bypassSecondaryLockScreen= */true); + /* bypassSecondaryLockScreen= */true, SecurityMode.Invalid); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index bbf71dcaa682..22afd615646d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -30,6 +30,7 @@ import android.view.View; import android.widget.LinearLayout; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; @@ -99,6 +100,7 @@ protected boolean shouldLockout(long deadline) { protected abstract int getPasswordTextViewId(); protected abstract void resetState(); + protected abstract SecurityMode getSecurityMode(); @Override protected void onFinishInflate() { @@ -209,7 +211,7 @@ private void onPasswordChecked(int userId, boolean matched, int timeoutMs, mCallback.reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { mDismissing = true; - mCallback.dismiss(true, userId); + mCallback.dismiss(true, userId, getSecurityMode()); } } else { if (isValidPassword) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index 57b3761c294f..b733d1cfc2c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -87,7 +87,7 @@ public void onTrustGrantedWithFlags(int flags, int userId) { Log.i(TAG, "TrustAgent dismissed Keyguard."); } dismiss(false /* authenticated */, userId, - /* bypassSecondaryLockScreen */ false); + /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid); } else { mViewMediatorCallback.playTrustedSound(); } @@ -193,12 +193,13 @@ public void showErrorMessage(CharSequence message) { * @return True if the keyguard is done. */ public boolean dismiss(int targetUserId) { - return dismiss(false, targetUserId, false); + return dismiss(false, targetUserId, false, getCurrentSecurityMode()); } public boolean handleBackKey() { if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) { - mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser()); + mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser(), + getCurrentSecurityMode()); return true; } return false; @@ -210,9 +211,9 @@ protected KeyguardSecurityContainer getSecurityContainer() { @Override public boolean dismiss(boolean authenticated, int targetUserId, - boolean bypassSecondaryLockScreen) { + boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId, - bypassSecondaryLockScreen); + bypassSecondaryLockScreen, expectedSecurityMode); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index a99b1a1aad87..2cdcf9eddaa2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -23,6 +23,7 @@ import android.view.animation.AnimationUtils; import android.widget.LinearLayout; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; import com.android.systemui.Dependency; @@ -218,4 +219,9 @@ private void enableClipping(boolean enable) { public boolean hasOverlappingRendering() { return false; } + + @Override + public SecurityMode getSecurityMode() { + return SecurityMode.PIN; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 97317cf5580f..1a8a26c07d0a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -38,6 +38,7 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.TextViewInputDisabler; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import java.util.List; @@ -387,4 +388,9 @@ public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_password_unlock); } + + @Override + public SecurityMode getSecurityMode() { + return SecurityMode.Password; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index d0e8b040f251..921e30ddc249 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -40,6 +40,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockscreenCredential; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.animation.AppearAnimationCreator; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; @@ -363,7 +364,7 @@ private void onPatternChecked(int userId, boolean matched, int timeoutMs, mCallback.reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); - mCallback.dismiss(true, userId); + mCallback.dismiss(true, userId, SecurityMode.Pattern); } } else { mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java index e38472745234..bc72f7979a74 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -15,14 +15,17 @@ */ package com.android.keyguard; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; + public interface KeyguardSecurityCallback { /** * Dismiss the given security screen. * @param securityVerified true if the user correctly entered credentials for the given screen. * @param targetUserId a user that needs to be the foreground user at the dismissal completion. + * @param expectedSecurityMode The security mode that is invoking this dismiss. */ - void dismiss(boolean securityVerified, int targetUserId); + void dismiss(boolean securityVerified, int targetUserId, SecurityMode expectedSecurityMode); /** * Dismiss the given security screen. @@ -30,8 +33,10 @@ public interface KeyguardSecurityCallback { * @param targetUserId a user that needs to be the foreground user at the dismissal completion. * @param bypassSecondaryLockScreen true if the user can bypass the secondary lock screen, * if any, during this dismissal. + * @param expectedSecurityMode The security mode that is invoking this dismiss. */ - void dismiss(boolean securityVerified, int targetUserId, boolean bypassSecondaryLockScreen); + void dismiss(boolean securityVerified, int targetUserId, boolean bypassSecondaryLockScreen, + SecurityMode expectedSecurityMode); /** * Manually report user activity to keep the device awake. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index bace9324ac93..b2020d9fe2d5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -193,7 +193,7 @@ public void onEnd(WindowInsetsAnimation animation) { // Used to notify the container when something interesting happens. public interface SecurityCallback { public boolean dismiss(boolean authenticated, int targetUserId, - boolean bypassSecondaryLockScreen); + boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode); public void userActivity(); public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); @@ -689,11 +689,20 @@ void showPrimarySecurityScreen(boolean turningOff) { * completion. * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary * secondary lock screen requirement, if any. + * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid + * indicates that no check should be done * @return true if keyguard is done */ boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, - boolean bypassSecondaryLockScreen) { + boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); + if (expectedSecurityMode != SecurityMode.Invalid + && expectedSecurityMode != getCurrentSecurityMode()) { + Log.w(TAG, "Attempted to invoke showNextSecurityScreenOrFinish with securityMode " + + expectedSecurityMode + ", but current mode is " + getCurrentSecurityMode()); + return false; + } + boolean finish = false; boolean strongAuth = false; int eventSubtype = -1; @@ -832,14 +841,17 @@ public void onUserInput() { } @Override - public void dismiss(boolean authenticated, int targetId) { - dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); + public void dismiss(boolean authenticated, int targetId, + SecurityMode expectedSecurityMode) { + dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false, + expectedSecurityMode); } @Override public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { - mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); + boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { + mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen, + expectedSecurityMode); } public boolean isVerifyUnlockOnly() { @@ -891,10 +903,11 @@ public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } @Override public boolean isVerifyUnlockOnly() { return false; } @Override - public void dismiss(boolean securityVerified, int targetUserId) { } + public void dismiss(boolean securityVerified, int targetUserId, + SecurityMode expectedSecurityMode) { } @Override public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { } + boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { } @Override public void onUserInput() { } @Override @@ -946,8 +959,9 @@ public SecurityMode getCurrentSecuritySelection() { return mCurrentSecuritySelection; } - public void dismiss(boolean authenticated, int targetUserId) { - mCallback.dismiss(authenticated, targetUserId); + public void dismiss(boolean authenticated, int targetUserId, + SecurityMode expectedSecurityMode) { + mCallback.dismiss(authenticated, targetUserId, expectedSecurityMode); } public boolean needsInput() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index 76adf04b21e7..6be80c49bd06 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -37,6 +37,7 @@ import android.view.WindowManager; import android.widget.ImageView; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -348,7 +349,8 @@ public void run() { mRemainingAttempts = -1; mShowDefaultMessage = true; if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser(), + SecurityMode.SimPin); } } else { mShowDefaultMessage = false; @@ -398,5 +400,10 @@ public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); } + + @Override + public SecurityMode getSecurityMode() { + return SecurityMode.SimPin; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index a84664ceee3d..0e9b301f03d0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -36,6 +36,7 @@ import android.view.WindowManager; import android.widget.ImageView; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -76,7 +77,8 @@ public void onSimStateChanged(int subId, int slotId, int simState) { // mCallback can be null if onSimStateChanged callback is called when keyguard // isn't active. if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser(), + SecurityMode.SimPuk); } break; } @@ -422,8 +424,8 @@ public void run() { mRemainingAttempts = -1; mShowDefaultMessage = true; if (mCallback != null) { - mCallback.dismiss(true, - KeyguardUpdateMonitor.getCurrentUser()); + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser(), + SecurityMode.SimPuk); } } else { mShowDefaultMessage = false; @@ -479,6 +481,11 @@ public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock); } + + @Override + public SecurityMode getSecurityMode() { + return SecurityMode.SimPuk; + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index fb819f04e0e1..e09a05da655f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -758,13 +758,14 @@ private void addToWindowManagerMaybe() { // themselves. ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Start not focusable - we'll become focusable when expanded so the ActivityView // can use the IME. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); + mWmLayoutParams.setTrustedOverlay(); mWmLayoutParams.setFitInsetsTypes(0); mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mWmLayoutParams.token = new Binder(); diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 977e46ac3b44..d2ded71487dc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -131,6 +131,12 @@ class ControlsProviderLifecycleManager( wrapper = null bindService(false) } + + override fun onNullBinding(name: ComponentName?) { + if (DEBUG) Log.d(TAG, "onNullBinding $name") + wrapper = null + context.unbindService(this) + } } private fun handlePendingServiceMethods() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 40662536e57e..45916d6e24ab 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.ui.CanUseIconPredicate import com.android.systemui.controls.ui.RenderInfo private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -49,7 +50,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit * @property elevation elevation of each control view */ class ControlAdapter( - private val elevation: Float + private val elevation: Float, + private val currentUserId: Int ) : RecyclerView.Adapter() { companion object { @@ -84,6 +86,7 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, + currentUserId, model?.moveHelper // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) @@ -189,6 +192,7 @@ private class ZoneHolder(view: View) : Holder(view) { */ internal class ControlHolder( view: View, + currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, val favoriteCallback: ModelFavoriteChanger ) : Holder(view) { @@ -205,6 +209,7 @@ internal class ControlHolder( visibility = View.VISIBLE } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val accessibilityDelegate = ControlHolderAccessibilityDelegate( this::stateDescription, this::getLayoutPosition, @@ -264,7 +269,9 @@ internal class ControlHolder( val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.imageTintList = null - ci.customIcon?.let { + ci.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) } ?: run { icon.setImageDrawable(ri.icon) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index f68388d5db3f..81ad7d27c4b3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -176,7 +176,7 @@ class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById(R.id.list) recyclerView.alpha = 0.0f - val adapter = ControlAdapter(elevation).apply { + val adapter = ControlAdapter(elevation, currentUserTracker.currentUserId).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { var hasAnimated = false override fun onChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index b2821579c389..ca06b4ef8843 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -163,7 +163,8 @@ class ControlsFavoritingActivity @Inject constructor( } executor.execute { - structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.adapter = StructureAdapter(listOfStructures, + currentUserTracker.currentUserId) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getString(R.string.controls_favorite_load_error, @@ -209,7 +210,7 @@ class ControlsFavoritingActivity @Inject constructor( structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f structurePager.apply { - adapter = StructureAdapter(emptyList()) + adapter = StructureAdapter(emptyList(), currentUserTracker.currentUserId) registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index cb67454195ec..7524f1cc2226 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R class StructureAdapter( - private val models: List + private val models: List, + private val currentUserId: Int ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { val layoutInflater = LayoutInflater.from(parent.context) return StructureHolder( - layoutInflater.inflate(R.layout.controls_structure_page, parent, false) + layoutInflater.inflate(R.layout.controls_structure_page, parent, false), + currentUserId ) } @@ -40,7 +42,8 @@ class StructureAdapter( holder.bind(models[index].model) } - class StructureHolder(view: View) : RecyclerView.ViewHolder(view) { + class StructureHolder(view: View, currentUserId: Int) : + RecyclerView.ViewHolder(view) { private val recyclerView: RecyclerView private val controlAdapter: ControlAdapter @@ -48,7 +51,7 @@ class StructureAdapter( init { recyclerView = itemView.requireViewById(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation) + controlAdapter = ControlAdapter(elevation, currentUserId) setUpRecyclerView() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt new file mode 100644 index 000000000000..61c21237144d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ContentProvider +import android.graphics.drawable.Icon + +class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean { + + override fun invoke(icon: Icon): Boolean = + if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId + } else { + true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index e15380b42a78..f6b420ba57f3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.ui import android.app.Dialog +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -74,7 +75,7 @@ class ControlActionCoordinatorImpl @Inject constructor( bouncerOrRun(Action(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { - showDialog(cvh, control.getAppIntent().getIntent()) + showDetail(cvh, control.getAppIntent()) } else { cvh.action(CommandAction(templateId)) } @@ -100,7 +101,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - showDialog(cvh, it.getAppIntent().getIntent()) + showDetail(cvh, it.getAppIntent()) } }, false /* blockable */)) } @@ -155,17 +156,17 @@ class ControlActionCoordinatorImpl @Inject constructor( bgExecutor.execute { vibrator.vibrate(effect) } } - private fun showDialog(cvh: ControlViewHolder, intent: Intent) { + private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) { bgExecutor.execute { - val activities: List = cvh.context.packageManager.queryIntentActivities( - intent, + val activities: List = context.packageManager.queryIntentActivities( + pendingIntent.getIntent(), PackageManager.MATCH_DEFAULT_ONLY ) uiExecutor.execute { // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty()) { - dialog = DetailDialog(cvh, intent).also { + dialog = DetailDialog(cvh, pendingIntent).also { it.setOnDismissListener { _ -> dialog = null } it.show() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 0e4f68431c16..e69524da0fb6 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -62,7 +62,8 @@ class ControlViewHolder( val controlsController: ControlsController, val uiExecutor: DelayableExecutor, val bgExecutor: DelayableExecutor, - val controlActionCoordinator: ControlActionCoordinator + val controlActionCoordinator: ControlActionCoordinator, + val currentUserId: Int ) { companion object { @@ -99,6 +100,7 @@ class ControlViewHolder( } } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val toggleBackgroundIntensity: Float = layout.context.resources .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) private var stateAnimator: ValueAnimator? = null @@ -411,7 +413,9 @@ class ControlViewHolder( status.setTextColor(color) - control?.getCustomIcon()?.let { + control?.getCustomIcon() + ?.takeIf(canUseIconPredicate) + ?.let { // do not tint custom icons, assume the intended icon color is correct if (icon.imageTintList != null) { icon.imageTintList = null diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 5a525974f3cb..17453230310f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -400,8 +400,8 @@ class ControlsUiControllerImpl @Inject constructor ( controlsController.get(), uiExecutor, bgExecutor, - controlActionCoordinator - ) + controlActionCoordinator, + controlsController.get().currentUserId) cvh.bindData(it) controlViewsById.put(key, cvh) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 9ec14523a809..a7f7eb7ffb44 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -16,8 +16,11 @@ package com.android.systemui.controls.ui +import android.app.ActivityOptions import android.app.ActivityView +import android.app.PendingIntent import android.app.Dialog +import android.content.ComponentName import android.content.Intent import android.provider.Settings import android.view.View @@ -37,9 +40,8 @@ import com.android.systemui.R */ class DetailDialog( val cvh: ControlViewHolder, - val intent: Intent + val pendingIntent: PendingIntent ) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) { - companion object { private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset" /* @@ -49,18 +51,19 @@ class DetailDialog( private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL" } + private val fillInIntent = Intent().apply { + putExtra(EXTRA_USE_PANEL, true) + + // Apply flags to make behaviour match documentLaunchMode=always. + addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) + addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + } + var activityView = ActivityView(context, null, 0, false) val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() { override fun onActivityViewReady(view: ActivityView) { - val launchIntent = Intent(intent) - launchIntent.putExtra(EXTRA_USE_PANEL, true) - - // Apply flags to make behaviour match documentLaunchMode=always. - launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) - launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - - view.startActivity(launchIntent) + view.startActivity(pendingIntent, fillInIntent, ActivityOptions.makeBasic()) } override fun onActivityViewDestroyed(view: ActivityView) {} @@ -68,6 +71,12 @@ class DetailDialog( override fun onTaskRemovalStarted(taskId: Int) { dismiss() } + + override fun onTaskCreated(taskId: Int, name: ComponentName?) { + requireViewById(R.id.controls_activity_view).apply { + setAlpha(1f) + } + } } init { @@ -76,6 +85,7 @@ class DetailDialog( requireViewById(R.id.controls_activity_view).apply { addView(activityView) + setAlpha(0f) } requireViewById(R.id.control_detail_close).apply { @@ -86,7 +96,7 @@ class DetailDialog( setOnClickListener { v: View -> dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - v.context.startActivity(intent) + pendingIntent.send() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 941bd3fdf274..e544e0720896 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -569,6 +569,13 @@ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSo } } + @Override + public void onStrongAuthStateChanged(int userId) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + doKeyguardLocked(null); + } + } + @Override public void onTrustChanged(int userId) { if (userId == KeyguardUpdateMonitor.getCurrentUser()) { @@ -986,9 +993,9 @@ private long getLockTimeout(int userId) { final ContentResolver cr = mContext.getContentResolver(); // From SecuritySettings - final long lockAfterTimeout = Settings.Secure.getInt(cr, + final long lockAfterTimeout = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId); // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() @@ -1000,8 +1007,8 @@ private long getLockTimeout(int userId) { timeout = lockAfterTimeout; } else { // From DisplaySettings - long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, - KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId); // policy in effect. Make sure we don't go beyond policy limit. displayTimeout = Math.max(displayTimeout, 0); // ignore negative values @@ -1163,6 +1170,10 @@ public void setKeyguardEnabled(boolean enabled) { mExternallyEnabled = enabled; if (!enabled && mShowing) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + Log.d(TAG, "keyguardEnabled(false) overridden by user lockdown"); + return; + } if (mExitSecureCallback != null) { if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring"); // we're in the process of handling a request to verify the user @@ -1325,7 +1336,9 @@ private void handleSetOccluded(boolean isOccluded, boolean animate) { public void doKeyguardTimeout(Bundle options) { mHandler.removeMessages(KEYGUARD_TIMEOUT); Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - A call to timeout means the device should lock + // as soon as possible and not wait for other messages on the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); } /** @@ -1397,8 +1410,9 @@ private void doKeyguardLocked(Bundle options) { return; } - // if another app is disabling us, don't show - if (!mExternallyEnabled) { + // if another app is disabling us, don't show unless we're in lockdown mode + if (!mExternallyEnabled + && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; @@ -1539,12 +1553,15 @@ private void notifyScreenTurnedOff() { * @see #handleShow */ private void showLocked(Bundle options) { - Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock"); + Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock"); if (DEBUG) Log.d(TAG, "showLocked"); // ensure we stay awake until we are finished displaying the keyguard mShowKeyguardWakeLock.acquire(); Message msg = mHandler.obtainMessage(SHOW, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - This call can originate from #doKeyguardTimeout, + // meaning the device should lock as soon as possible and not wait for other messages on + // the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); Trace.endSection(); } @@ -1706,6 +1723,7 @@ public void handleMessage(Message msg) { case KEYGUARD_TIMEOUT: synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); + notifyDefaultDisplayCallbacks(mShowing); } break; case DISMISS: @@ -1841,7 +1859,10 @@ private void playSounds(boolean locked) { private void playSound(int soundId) { if (soundId == 0) return; final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr, + Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1, + KeyguardUpdateMonitor.getCurrentUser()); + if (lockscreenSoundsEnabled == 1) { mLockSounds.stop(mLockSoundStreamId); // Init mAudioManager @@ -2335,7 +2356,7 @@ private void notifyDefaultDisplayCallbacks(boolean showing) { for (int i = size - 1; i >= 0; i--) { IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i); try { - callback.onShowingStateChanged(showing); + callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser()); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onShowingStateChanged", e); if (e instanceof DeadObjectException) { @@ -2384,7 +2405,7 @@ public void addStateMonitorCallback(IKeyguardStateCallback callback) { mKeyguardStateCallbacks.add(callback); try { callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure()); - callback.onShowingStateChanged(mShowing); + callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser()); callback.onInputRestrictedStateChanged(mInputRestricted); callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust( KeyguardUpdateMonitor.getCurrentUser())); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 2d7aafc497d8..7524fc9feb26 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -18,11 +18,14 @@ package com.android.systemui.media import android.app.Notification import android.app.PendingIntent +import android.app.UriGrantsManager import android.content.BroadcastReceiver +import android.content.ContentProvider import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -34,6 +37,7 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.net.Uri +import android.os.Process import android.os.UserHandle import android.service.notification.StatusBarNotification import android.text.TextUtils @@ -374,7 +378,13 @@ class MediaDataManager( // Album art var artworkBitmap = desc.iconBitmap if (artworkBitmap == null && desc.iconUri != null) { - artworkBitmap = loadBitmapFromUri(desc.iconUri!!) + val appUid = try { + context.packageManager.getApplicationInfo(packageName, 0)?.uid!! + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Could not get app UID for $packageName", e) + Process.INVALID_UID + } + artworkBitmap = loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName) } val artworkIcon = if (artworkBitmap != null) { Icon.createWithBitmap(artworkBitmap) @@ -445,13 +455,17 @@ class MediaDataManager( val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context) // Song name - var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) - if (song == null) { - song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) + var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) + if (song.isNullOrBlank()) { + song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE) } - if (song == null) { + if (song.isNullOrBlank()) { song = HybridGroupManager.resolveTitle(notif) } + if (song.isNullOrBlank()) { + // For apps that don't include a title, add a placeholder + song = context.getString(R.string.controls_media_empty_title, app) + } // Artist name var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) @@ -527,6 +541,29 @@ class MediaDataManager( return null } + /** Returns a bitmap if the user can access the given URI, else null */ + private fun loadBitmapFromUriForUser( + uri: Uri, + userId: Int, + appUid: Int, + packageName: String + ): Bitmap? { + try { + val ugm = UriGrantsManager.getService() + ugm.checkGrantUriPermission_ignoreNonSystem( + appUid, + packageName, + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, userId) + ) + return loadBitmapFromUri(uri) + } catch (e: SecurityException) { + Log.e(TAG, "Failed to get URI permission: $e") + } + return null + } + /** * Load a bitmap from a URI * @param uri the uri to load diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 936db8735ad8..b1699d7194bc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -90,9 +90,16 @@ class MediaResumeListener @Inject constructor( Log.e(TAG, "Error getting package information", e) } - Log.d(TAG, "Adding resume controls $desc") - mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token, - appName.toString(), appIntent, component.packageName) + Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc") + mediaDataManager.addResumptionControls( + browser.userId, + desc, + resumeAction, + token, + appName.toString(), + appIntent, + component.packageName + ) } } @@ -133,7 +140,11 @@ class MediaResumeListener @Inject constructor( val component = ComponentName(packageName, className) resumeComponents.add(component) } - Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + Log.d( + TAG, + "loaded resume components for $currentUserId: " + + "${resumeComponents.toArray().contentToString()}" + ) } /** @@ -144,9 +155,19 @@ class MediaResumeListener @Inject constructor( return } + val pm = context.packageManager resumeComponents.forEach { - val browser = mediaBrowserFactory.create(mediaBrowserCallback, it) - browser.findRecentMedia() + // Verify that the service exists for this user + val intent = Intent(MediaBrowserService.SERVICE_INTERFACE) + intent.component = it + val inf = pm.resolveServiceAsUser(intent, 0, currentUserId) + if (inf != null) { + val browser = + mediaBrowserFactory.create(mediaBrowserCallback, it, currentUserId) + browser.findRecentMedia() + } else { + Log.d(TAG, "User $currentUserId does not have component $it") + } } } @@ -160,7 +181,7 @@ class MediaResumeListener @Inject constructor( Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) - val resumeInfo = pm.queryIntentServices(serviceIntent, 0) + val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId) val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName @@ -210,7 +231,9 @@ class MediaResumeListener @Inject constructor( mediaBrowser = null } }, - componentName) + componentName, + currentUserId + ) mediaBrowser?.testConnection() } @@ -267,7 +290,8 @@ class MediaResumeListener @Inject constructor( mediaBrowser = null } }, - componentName) + componentName, + currentUserId) mediaBrowser?.restart() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index a4d44367be73..a17dc5d7d2c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -16,6 +16,7 @@ package com.android.systemui.media; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -49,6 +50,8 @@ public class ResumeMediaBrowser { private final Context mContext; private final Callback mCallback; private MediaBrowserFactory mBrowserFactory; + @UserIdInt private final int mUserId; + private MediaBrowser mMediaBrowser; private ComponentName mComponentName; @@ -57,13 +60,15 @@ public class ResumeMediaBrowser { * @param context the context * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to + * @param userId ID of the current user */ public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName, - MediaBrowserFactory browserFactory) { + MediaBrowserFactory browserFactory, @UserIdInt int userId) { mContext = context; mCallback = callback; mComponentName = componentName; mBrowserFactory = browserFactory; + mUserId = userId; } /** @@ -224,6 +229,14 @@ protected MediaController createMediaController(MediaSession.Token token) { return new MediaController(mContext, token); } + /** + * Get the ID of the user associated with this broswer + * @return the user ID + */ + public @UserIdInt int getUserId() { + return mUserId; + } + /** * Get the media session token * @return the token, or null if the MediaBrowser is null or disconnected @@ -257,7 +270,7 @@ public void testConnection() { disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); - mMediaBrowser = mBrowserFactory.create( + mMediaBrowser = mBrowserFactory.create( mComponentName, mConnectionCallback, rootHints); diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java index 2261aa5ac265..5744190cfe63 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java @@ -16,6 +16,7 @@ package com.android.systemui.media; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; @@ -39,10 +40,12 @@ public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFac * * @param callback will be called on connection or error, and addTrack when media item found * @param componentName component to browse + * @param userId ID of the current user * @return */ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback, - ComponentName componentName) { - return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory); + ComponentName componentName, @UserIdInt int userId) { + return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, + userId); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 29d77a72c73a..2e9455d5e0d7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -442,7 +442,8 @@ mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams), final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds); + final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds, + destinationBounds); scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); @@ -457,14 +458,17 @@ mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams), /** * Returns the source hint rect if it is valid (if provided and is contained by the current - * task bounds). + * task bounds and not too small). */ - private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) { + private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds, + Rect destinationBounds) { final Rect sourceHintRect = info.pictureInPictureParams != null && info.pictureInPictureParams.hasSourceBoundsHint() ? info.pictureInPictureParams.getSourceRectHint() : null; - if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) { + if (sourceHintRect != null && sourceBounds.contains(sourceHintRect) + && sourceHintRect.width() > destinationBounds.width() + && sourceHintRect.height() > destinationBounds.height()) { return sourceHintRect; } return null; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 586399c6dfd5..9771db986b29 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -53,6 +53,7 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -560,13 +561,17 @@ private void updateActionViews(Rect stackBounds) { final RemoteAction action = mActions.get(i); final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i); - // TODO: Check if the action drawable has changed before we reload it - action.getIcon().loadDrawableAsync(this, d -> { - if (d != null) { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); - } - }, mHandler); + final int iconType = action.getIcon().getType(); + if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // Disallow loading icon from content URI + actionView.setImageDrawable(null); + } else { + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(this, d -> { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + }, mHandler); + } actionView.setContentDescription(action.getContentDescription()); if (action.isEnabled()) { actionView.setOnClickListener(v -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 2bef355d59f3..e0e603f7514d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -134,11 +134,11 @@ public int getState() { } @Override - public boolean setState(int state) { + public boolean setState(int state, boolean force) { if (state > MAX_STATE || state < MIN_STATE) { throw new IllegalArgumentException("Invalid state " + state); } - if (state == mState) { + if (!force && state == mState) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index 07b35502478f..fc40077810ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -58,7 +58,19 @@ public interface SysuiStatusBarStateController extends StatusBarStateController * @param state see {@link StatusBarState} for valid options * @return {@code true} if the state changed, else {@code false} */ - boolean setState(int state); + default boolean setState(int state) { + return setState(state, false /* force */); + } + + /** + * Update the status bar state + * @param state see {@link StatusBarState} for valid options + * @param force whether to set the state even if it's the same as the current state. This will + * dispatch the state to all StatusBarStateListeners, ensuring that all listening + * components are reset to this state. + * @return {@code true} if the state was changed or set forcefully + */ + boolean setState(int state, boolean force); /** * Update the dozing state from {@link StatusBar}'s perspective diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index eeb91b548d65..5158f2eb0324 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -18,6 +18,7 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; +import android.app.Notification; import android.app.NotificationManager; import android.content.ContentResolver; import android.database.ContentObserver; @@ -176,9 +177,100 @@ public boolean shouldHeadsUp(NotificationEntry entry) { */ @Override public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { - return entry.getSbn().getNotification().fullScreenIntent != null - && (!shouldHeadsUp(entry) - || mStatusBarStateController.getState() == StatusBarState.KEYGUARD); + if (entry.getSbn().getNotification().fullScreenIntent == null) { + return false; + } + + // Never show FSI when suppressed by DND + if (entry.shouldSuppressFullScreenIntent()) { + if (DEBUG) { + Log.d(TAG, "No FullScreenIntent: Suppressed by DND: " + entry.getKey()); + } + return false; + } + + // Never show FSI if importance is not HIGH + if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No FullScreenIntent: Not important enough: " + entry.getKey()); + } + return false; + } + + // If the notification has suppressive GroupAlertBehavior, block FSI and warn. + StatusBarNotification sbn = entry.getSbn(); + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + // b/231322873: Detect and report an event when a notification has both an FSI and a + // suppressive groupAlertBehavior, and now correctly block the FSI from firing. + final int uid = entry.getSbn().getUid(); + android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); + if (DEBUG) { + Log.w(TAG, "No FullScreenIntent: WARNING: GroupAlertBehavior will prevent HUN: " + + entry.getKey()); + } + return false; + } + + // If the notification has suppressive BubbleMetadata, block FSI and warn. + Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata(); + if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) { + // b/274759612: Detect and report an event when a notification has both an FSI and a + // suppressive BubbleMetadata, and now correctly block the FSI from firing. + final int uid = entry.getSbn().getUid(); + android.util.EventLog.writeEvent(0x534e4554, "274759612", uid, "bubbleMetadata"); + if (DEBUG) { + Log.w(TAG, "No FullScreenIntent: WARNING: BubbleMetadata may prevent HUN: " + + entry.getKey()); + } + return false; + } + + // If the screen is off, then launch the FullScreenIntent + if (!mPowerManager.isInteractive()) { + if (DEBUG) { + Log.d(TAG, "FullScreenIntent: Device is not interactive: " + entry.getKey()); + } + return true; + } + + // If the device is currently dreaming, then launch the FullScreenIntent + if (isDreaming()) { + if (DEBUG) { + Log.d(TAG, "FullScreenIntent: Device is dreaming: " + entry.getKey()); + } + return true; + } + + // If the keyguard is showing, then launch the FullScreenIntent + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + if (DEBUG) { + Log.d(TAG, "FullScreenIntent: Keyguard is showing: " + entry.getKey()); + } + return true; + } + + // If the notification should HUN, then we don't need FSI + if (shouldHeadsUp(entry)) { + if (DEBUG) { + Log.d(TAG, "No FullScreenIntent: Expected to HUN: " + entry.getKey()); + } + return false; + } + + // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI. + if (DEBUG) { + Log.d(TAG, "FullScreenIntent: Expected not to HUN: " + entry.getKey()); + } + return true; + } + + private boolean isDreaming() { + try { + return mDreamManager.isDreaming(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query dream manager.", e); + return false; + } } private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { @@ -229,13 +321,7 @@ private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { return false; } - boolean isDreaming = false; - try { - isDreaming = mDreamManager.isDreaming(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to query dream manager.", e); - } - boolean inUse = mPowerManager.isScreenOn() && !isDreaming; + boolean inUse = mPowerManager.isScreenOn() && !isDreaming(); if (!inUse) { if (DEBUG_HEADS_UP) { 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 70dd80e71e7a..8b29fb596ea7 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 @@ -65,6 +65,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; @@ -1612,7 +1613,8 @@ public void initialize( OnAppOpsClickListener onAppOpsClickListener, FalsingManager falsingManager, StatusBarStateController statusBarStateController, - PeopleNotificationIdentifier peopleNotificationIdentifier) { + PeopleNotificationIdentifier peopleNotificationIdentifier, + IStatusBarService statusBarService) { mAppName = appName; if (mMenuRow == null) { mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); 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 7a6109d2ce78..8c92e730cd66 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 @@ -22,6 +22,8 @@ import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -69,6 +71,7 @@ public class ExpandableNotificationRowController { private final FalsingManager mFalsingManager; private final boolean mAllowLongPress; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + private final IStatusBarService mStatusBarService; @Inject public ExpandableNotificationRowController(ExpandableNotificationRow view, @@ -84,7 +87,8 @@ public ExpandableNotificationRowController(ExpandableNotificationRow view, NotificationGutsManager notificationGutsManager, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager, - PeopleNotificationIdentifier peopleNotificationIdentifier) { + PeopleNotificationIdentifier peopleNotificationIdentifier, + IStatusBarService statusBarService) { mView = view; mActivatableNotificationViewController = activatableNotificationViewController; mMediaManager = mediaManager; @@ -105,6 +109,7 @@ public ExpandableNotificationRowController(ExpandableNotificationRow view, mAllowLongPress = allowLongPress; mFalsingManager = falsingManager; mPeopleNotificationIdentifier = peopleNotificationIdentifier; + mStatusBarService = statusBarService; } /** @@ -125,7 +130,8 @@ public void init() { mOnAppOpsClickListener, mFalsingManager, mStatusBarStateController, - mPeopleNotificationIdentifier + mPeopleNotificationIdentifier, + mStatusBarService ); mView.setOnDismissRunnable(mOnDismissRunnable); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index a7d83b3b2774..acab13df0a6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -435,6 +435,7 @@ public RemoteViews getRemoteView() { CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); + return cancellationSignal; } @@ -700,6 +701,7 @@ public void setInflateSynchronously(boolean inflateSynchronously) { public static class AsyncInflationTask extends AsyncTask implements InflationCallback, InflationTask { + private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L; private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; @@ -786,10 +788,16 @@ protected InflationProgress doInBackground(Void... params) { InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); - return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, + + InflationProgress result = inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mSmartReplyConstants, mSmartReplyController, mRow.getExistingSmartRepliesAndActions()); + + // wait for image resolver to finish preloading + mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS); + + return result; } catch (Exception e) { mError = e; return null; @@ -824,6 +832,9 @@ private void handleError(Exception e) { mCallback.handleInflationException(mRow.getEntry(), new InflationException("Couldn't inflate contentViews" + e)); } + + // Cancel any image loading tasks, not useful any more + mRow.getImageResolver().cancelRunningTasks(); } @Override @@ -850,6 +861,9 @@ public void onAsyncInflationFinished(NotificationEntry entry) { // Notify the resolver that the inflation task has finished, // try to purge unnecessary cached entries. mRow.getImageResolver().purgeCache(); + + // Cancel any image loading tasks that have not completed at this point + mRow.getImageResolver().cancelRunningTasks(); } private class RtlEnabledContext extends ContextWrapper { 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 1f5b063b0aa2..411c02e3be1d 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 @@ -24,9 +24,11 @@ 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; @@ -45,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.statusbar.MediaTransferManager; import com.android.systemui.statusbar.RemoteInputController; @@ -126,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). */ @@ -182,6 +187,7 @@ public NotificationContentView(Context context, AttributeSet attrs) { mMediaTransferManager = new MediaTransferManager(getContext()); mSmartReplyConstants = Dependency.get(SmartReplyConstants.class); mSmartReplyController = Dependency.get(SmartReplyController.class); + mStatusBarService = Dependency.get(IStatusBarService.class); initView(); } @@ -1965,4 +1971,35 @@ public int getOriginalIconColor() { } 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); + if (mStatusBarService != null) { + // report notification inflation errors back up + // to notification delegates + mStatusBarService.onNotificationError( + mStatusBarNotification.getPackageName(), + mStatusBarNotification.getTag(), + mStatusBarNotification.getId(), + mStatusBarNotification.getUid(), + mStatusBarNotification.getInitialPid(), + exception.getMessage(), + mStatusBarNotification.getUser().getIdentifier()); + } + } catch (RemoteException ex) { + Log.e(TAG, "cancelNotification failed: " + ex); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java index 4b0e2ffd5d7f..6fdc8a3dce0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java @@ -21,10 +21,12 @@ import android.os.AsyncTask; import android.util.Log; -import java.io.IOException; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A cache for inline images of image messages. @@ -57,12 +59,13 @@ public void preload(Uri uri) { } @Override - public Drawable get(Uri uri) { + public Drawable get(Uri uri, long timeoutMs) { Drawable result = null; try { - result = mCache.get(uri).get(); - } catch (InterruptedException | ExecutionException ex) { - Log.d(TAG, "get: Failed get image from " + uri); + result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException + | TimeoutException | CancellationException ex) { + Log.d(TAG, "get: Failed get image from " + uri + " " + ex); } return result; } @@ -73,6 +76,15 @@ public void purge() { mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey())); } + @Override + public void cancelRunningTasks() { + mCache.forEach((key, value) -> { + if (value.getStatus() != AsyncTask.Status.FINISHED) { + value.cancel(true); + } + }); + } + private static class PreloadImageTask extends AsyncTask { private final NotificationInlineImageResolver mResolver; @@ -87,7 +99,7 @@ protected Drawable doInBackground(Uri... uris) { try { drawable = mResolver.resolveImage(target); - } catch (IOException | SecurityException ex) { + } catch (Exception ex) { Log.d(TAG, "PreloadImageTask: Resolve failed from " + target, ex); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index 7bd192d850c1..b240855fe6e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; +import android.os.SystemClock; import android.util.Log; import com.android.internal.R; @@ -49,6 +50,9 @@ public class NotificationInlineImageResolver implements ImageResolver { private static final String TAG = NotificationInlineImageResolver.class.getSimpleName(); + // Timeout for loading images from ImageCache when calling from UI thread + private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L; + private final Context mContext; private final ImageCache mImageCache; private Set mWantedUriSet; @@ -132,16 +136,20 @@ Drawable resolveImage(Uri uri) throws IOException { return image; } + /** + * Loads an image from the Uri. + * This method is synchronous and is usually called from the Main thread. + * It will time-out after MAX_UI_THREAD_TIMEOUT_MS. + * + * @param uri Uri of the target image. + * @return drawable of the image, null if loading failed/timeout + */ @Override public Drawable loadImage(Uri uri) { Drawable result = null; try { if (hasCache()) { - // if the uri isn't currently cached, try caching it first - if (!mImageCache.hasEntry(uri)) { - mImageCache.preload((uri)); - } - result = mImageCache.get(uri); + result = loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS); } else { result = resolveImage(uri); } @@ -151,6 +159,14 @@ public Drawable loadImage(Uri uri) { return result; } + private Drawable loadImageFromCache(Uri uri, long timeoutMs) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); + } + return mImageCache.get(uri, timeoutMs); + } + /** * Resolve the message list from specified notification and * refresh internal cache according to the result. @@ -222,6 +238,30 @@ Set getWantedUriSet() { return mWantedUriSet; } + /** + * Wait for a maximum timeout for images to finish preloading + * @param timeoutMs total timeout time + */ + void waitForPreloadedImages(long timeoutMs) { + if (!hasCache()) { + return; + } + Set preloadedUris = getWantedUriSet(); + if (preloadedUris != null) { + // Decrement remaining timeout after each image check + long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs; + preloadedUris.forEach( + uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime())); + } + } + + void cancelRunningTasks() { + if (!hasCache()) { + return; + } + mImageCache.cancelRunningTasks(); + } + /** * A interface for internal cache implementation of this resolver. */ @@ -231,7 +271,7 @@ interface ImageCache { * @param uri The uri of the image. * @return Drawable of the image. */ - Drawable get(Uri uri); + Drawable get(Uri uri, long timeoutMs); /** * Set the image resolver that actually resolves image from specified uri. @@ -256,6 +296,11 @@ interface ImageCache { * Purge unnecessary entries in the cache. */ void purge(); + + /** + * Cancel all unfinished image loading tasks + */ + void cancelRunningTasks(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index df687d71efa2..88cdbf9daad2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -157,6 +157,8 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.ZenModeController; @@ -300,6 +302,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private boolean mExpandedInThisMotion; private boolean mShouldShowShelfOnly; protected boolean mScrollingEnabled; + private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; @@ -509,6 +512,8 @@ public void getOutline(View view, Outline outline) { private final NotifPipeline mNotifPipeline; private final NotifCollection mNotifCollection; private final NotificationEntryManager mEntryManager; + private final DeviceProvisionedController mDeviceProvisionedController = + Dependency.get(DeviceProvisionedController.class); private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); @VisibleForTesting @@ -649,6 +654,29 @@ public NotificationStackScrollLayout( }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL, Settings.Secure.NOTIFICATION_HISTORY_ENABLED); + mDeviceProvisionedController.addCallback( + new DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSwitched() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSetupChanged() { + updateCurrentUserIsSetup(); + } + + private void updateCurrentUserIsSetup() { + setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); + } + }); + + mFeatureFlags = featureFlags; mNotifPipeline = notifPipeline; mEntryManager = entryManager; @@ -776,6 +804,7 @@ public void updateFooter() { boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL); mStatusBar.setHasClearableNotifs(hasActiveClearableNotifications(ROWS_ALL)); boolean showFooterView = (showDismissView || hasActiveNotifications()) + && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD && !mRemoteInputManager.getController().isRemoteInputActive(); boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), @@ -5949,6 +5978,16 @@ public float calculateAppearFractionBypass() { return MathUtils.smoothStep(0, totalDistance, dragDownAmount); } + /** + * Sets whether the current user is set up, which is required to show the footer (b/193149550) + */ + public void setCurrentUserSetup(boolean isCurrentUserSetup) { + if (mIsCurrentUserSetup != isCurrentUserSetup) { + mIsCurrentUserSetup = isCurrentUserSetup; + updateFooter(); + } + } + /** * A listener that is notified when the empty space below the notifications is clicked on */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index 806854dbba0d..523e490eb9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -31,6 +31,7 @@ import android.graphics.PixelFormat; import android.os.Binder; import android.os.Handler; +import android.os.Build; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; @@ -280,6 +281,16 @@ private void applyKeyguardFlags(State state) { } Trace.setCounter("display_mode_id", mLpChanged.preferredDisplayModeId); } + + if (state.mBouncerShowing && !isDebuggable()) { + mLpChanged.flags |= LayoutParams.FLAG_SECURE; + } else { + mLpChanged.flags &= ~LayoutParams.FLAG_SECURE; + } + } + + protected boolean isDebuggable() { + return Build.IS_DEBUGGABLE; } private void adjustScreenOrientation(State state) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 55a537a2e578..e230300e714c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3467,6 +3467,10 @@ public boolean isFullScreenUserSwitcherState() { } boolean updateIsKeyguard() { + return updateIsKeyguard(false /* force */); + } + + boolean updateIsKeyguard(boolean force) { boolean wakeAndUnlocking = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -3489,7 +3493,7 @@ boolean updateIsKeyguard() { showKeyguardImpl(); } } else { - return hideKeyguardImpl(); + return hideKeyguardImpl(force); } return false; } @@ -3620,11 +3624,11 @@ private void runLaunchTransitionEndRunnable() { /** * @return true if we would like to stay in the shade, false if it should go away entirely */ - public boolean hideKeyguardImpl() { + public boolean hideKeyguardImpl(boolean force) { mIsKeyguard = false; Trace.beginSection("StatusBar#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE))) { + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) { //TODO: StatusBarStateController should probably know about hiding the keyguard and // notify listeners. @@ -4056,7 +4060,8 @@ public void onFinishedGoingToSleep() { // is correct. mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource)); } - updateIsKeyguard(); + // When finished going to sleep, force the status bar state to avoid stale state. + updateIsKeyguard(true /* force */); } @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 9be2d124026c..3114b0a99e97 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -46,6 +46,7 @@ import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; import org.junit.After; @@ -191,7 +192,7 @@ private SurfaceView verifySurfaceReady() throws Exception { private void verifyViewDismissed(SurfaceView v) throws Exception { verify(mParent).removeView(v); - verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true); + verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true, SecurityMode.Invalid); assertThat(mContext.isBound(mComponentName)).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index 2d3757c29ebf..12096bc06748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.controls.controller import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection import android.os.UserHandle import android.service.controls.IControlsActionCallback import android.service.controls.IControlsProvider @@ -43,6 +46,8 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -57,8 +62,6 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { private lateinit var subscriberService: IControlsSubscriber.Stub @Mock private lateinit var service: IControlsProvider.Stub - @Mock - private lateinit var loadCallback: ControlsBindingController.LoadCallback @Captor private lateinit var wrapperCaptor: ArgumentCaptor @@ -75,7 +78,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mContext.addMockService(componentName, service) + context.addMockService(componentName, service) executor = FakeExecutor(FakeSystemClock()) `when`(service.asBinder()).thenCallRealMethod() `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service) @@ -98,7 +101,36 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { fun testBindService() { manager.bindService() executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) + } + + @Test + fun testNullBinding() { + val mockContext = mock(Context::class.java) + lateinit var serviceConnection: ServiceConnection + `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer { + val component = (it.arguments[0] as Intent).component + if (component == componentName) { + serviceConnection = it.arguments[1] as ServiceConnection + serviceConnection.onNullBinding(component) + true + } else { + false + } + } + + val nullManager = ControlsProviderLifecycleManager( + mockContext, + executor, + actionCallbackService, + UserHandle.of(0), + componentName + ) + + nullManager.bindService() + executor.runAllReady() + + verify(mockContext).unbindService(serviceConnection) } @Test @@ -109,7 +141,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.unbindService() executor.runAllReady() - assertFalse(mContext.isBound(componentName)) + assertFalse(context.isBound(componentName)) } @Test @@ -119,7 +151,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { verify(service).load(subscriberService) - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) } @Test @@ -129,7 +161,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.unbindService() executor.runAllReady() - assertFalse(mContext.isBound(componentName)) + assertFalse(context.isBound(componentName)) } @Test @@ -162,7 +194,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.maybeBindAndSubscribe(list, subscriberService) executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) verify(service).subscribe(list, subscriberService) } @@ -173,7 +205,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { manager.maybeBindAndSendAction(controlId, action) executor.runAllReady() - assertTrue(mContext.isBound(componentName)) + assertTrue(context.isBound(componentName)) verify(service).action(eq(controlId), capture(wrapperCaptor), eq(actionCallbackService)) assertEquals(action, wrapperCaptor.getValue().getWrappedAction()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt new file mode 100644 index 000000000000..ed17f179eeb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CanUseIconPredicateTest : SysuiTestCase() { + + private companion object { + const val USER_ID_1 = 1 + const val USER_ID_2 = 2 + } + + val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1) + + @Test + fun testReturnsFalseForDifferentUser() { + val user2Icon = Icon.createWithContentUri("content://$USER_ID_2@test") + + assertThat(underTest.invoke(user2Icon)).isFalse() + } + + @Test + fun testReturnsTrueForCorrectUser() { + val user1Icon = Icon.createWithContentUri("content://$USER_ID_1@test") + + assertThat(underTest.invoke(user1Icon)).isTrue() + } + + @Test + fun testReturnsTrueForUriWithoutUser() { + val uriIcon = Icon.createWithContentUri(Uri.parse("content://test")) + + assertThat(underTest.invoke(uriIcon)).isTrue() + } + + @Test + fun testReturnsTrueForNonUriIcon() { + val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) + + assertThat(underTest.invoke(bitmapIcon)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt new file mode 100644 index 000000000000..0ad03d2b41b3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.app.ActivityView +import android.app.PendingIntent +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.capture +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class DetailDialogTest : SysuiTestCase() { + + @Mock + private lateinit var activityView: ActivityView + @Mock + private lateinit var controlViewHolder: ControlViewHolder + @Mock + private lateinit var pendingIntent: PendingIntent + @Captor + private lateinit var viewCaptor: ArgumentCaptor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testPendingIntentIsUnModified() { + // GIVEN the dialog is created with a PendingIntent + val dialog = createDialog(pendingIntent) + + // WHEN the ActivityView is initialized + dialog.stateCallback.onActivityViewReady(capture(viewCaptor)) + + // THEN the PendingIntent used to call startActivity is unmodified by systemui + verify(viewCaptor.value).startActivity(eq(pendingIntent), any(), any()) + } + + private fun createDialog(pendingIntent: PendingIntent): DetailDialog { + return DetailDialog( + controlViewHolder, + pendingIntent + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 2f99b2a2a449..e7a1ad21d363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -1,16 +1,22 @@ package com.android.systemui.media +import android.app.IUriGrantsManager import android.app.Notification.MediaStyle import android.app.PendingIntent +import android.app.UriGrantsManager import android.graphics.Bitmap +import android.graphics.ImageDecoder import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession +import android.net.Uri import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager @@ -26,6 +32,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito @@ -35,13 +42,16 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.quality.Strictness private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "SystemUI" +private const val APP_NAME = "com.android.systemui.tests" private const val SESSION_ARTIST = "artist" private const val SESSION_TITLE = "title" +private const val SESSION_BLANK_TITLE = " " +private const val SESSION_EMPTY_TITLE = "" private const val USER_ID = 0 private fun anyObject(): T { @@ -73,6 +83,8 @@ class MediaDataManagerTest : SysuiTestCase() { lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor + @Mock private lateinit var ugm: IUriGrantsManager + @Mock private lateinit var imageSource: ImageDecoder.Source @Before fun setup() { @@ -168,6 +180,95 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } + @Test + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { + // When the manager has a notification with an empty title + val listener = mock(MediaDataManager.Listener::class.java) + mediaDataManager.addListener(listener) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { + // GIVEN that the manager has a notification with a blank title + val listener = mock(MediaDataManager.Listener::class.java) + mediaDataManager.addListener(listener) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() { + // When the app sets the metadata title fields to empty strings, but does include a + // non-blank notification title + val listener = mock(MediaDataManager.Listener::class.java) + mediaDataManager.addListener(listener) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setContentTitle(SESSION_TITLE) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + } + build() + } + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then the media control is added using the notification's title + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor) + ) + assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) + } + @Test fun testOnNotificationRemoved_withResumption() { // GIVEN that the manager has a notification with a resume action @@ -269,4 +370,111 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR) } + + @Test + fun testResumeMediaLoaded_hasArtPermission_artLoaded() { + // When resume media is loaded and user/app has permission to access the art URI, + var mockSession = ExtendedMockito.mockitoSession() + .mockStatic(UriGrantsManager::class.java) + .mockStatic(ImageDecoder::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + try { + whenever(UriGrantsManager.getService()).thenReturn(ugm) + whenever( + ugm.checkGrantUriPermission_ignoreNonSystem( + anyInt(), + anyObject(), + anyObject(), + anyInt(), + anyInt() + ) + ) + .thenReturn(1) + val artwork = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val uri = Uri.parse("content://example") + whenever(ImageDecoder.createSource(anyObject(), eq(uri))).thenReturn(imageSource) + whenever(ImageDecoder.decodeBitmap(anyObject(), anyObject())).thenReturn(artwork) + + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setIconUri(uri) + build() + } + addResumeControlAndLoad(desc) + + // Then the artwork is loaded + assertThat(mediaDataCaptor.value.artwork).isNotNull() + } finally { + mockSession.finishMocking() + } + } + + @Test + fun testResumeMediaLoaded_noArtPermission_noArtLoaded() { + // When resume media is loaded and user/app does not have permission to access the art URI + var mockSession = ExtendedMockito.mockitoSession() + .mockStatic(UriGrantsManager::class.java) + .mockStatic(ImageDecoder::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + try { + whenever(UriGrantsManager.getService()).thenReturn(ugm) + whenever( + ugm.checkGrantUriPermission_ignoreNonSystem( + anyInt(), + anyObject(), + anyObject(), + anyInt(), + anyInt() + ) + ) + .thenThrow(SecurityException("Test no permission")) + val artwork = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val uri = Uri.parse("content://example") + whenever(ImageDecoder.createSource(anyObject(), eq(uri))).thenReturn(imageSource) + whenever(ImageDecoder.decodeBitmap(anyObject(), anyObject())).thenReturn(artwork) + + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setIconUri(uri) + build() + } + addResumeControlAndLoad(desc) + + // Then the artwork is not loaded + assertThat(mediaDataCaptor.value.artwork).isNull() + } finally { + mockSession.finishMocking() + } + } + + /** Helper function to add a resumption control and capture the resulting MediaData */ + private fun addResumeControlAndLoad( + desc: MediaDescription, + packageName: String = PACKAGE_NAME + ) { + val listener = mock(MediaDataManager.Listener::class.java) + mediaDataManager.addListener(listener) + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + packageName + ) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + + verify(listener) + .onMediaDataLoaded( + eq(packageName), + eq(null), + capture(mediaDataCaptor) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index 5d81de6bce00..e4ca06e832a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -87,6 +87,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var pendingIntent: PendingIntent @Captor lateinit var callbackCaptor: ArgumentCaptor + @Captor lateinit var userIdCaptor: ArgumentCaptor private lateinit var executor: FakeExecutor private lateinit var data: MediaData @@ -106,7 +107,7 @@ class MediaResumeListenerTest : SysuiTestCase() { Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) - whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor))) .thenReturn(resumeBrowser) // resume components are stored in sharedpreferences @@ -117,6 +118,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + whenever(mockContext.userId).thenReturn(context.userId) executor = FakeExecutor(FakeSystemClock()) resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, @@ -255,4 +257,80 @@ class MediaResumeListenerTest : SysuiTestCase() { verify(mediaDataManager, times(3)).addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) } + + @Test + fun testUserUnlocked_userChangeWhileQuerying() { + val firstUserId = context.userId + val secondUserId = firstUserId + 1 + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + + setUpMbsWithValidResolveInfo() + whenever(resumeBrowser.token).thenReturn(token) + whenever(resumeBrowser.appIntent).thenReturn(pendingIntent) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, firstUserId) + } + // When the first user unlocks and we query their recent media + resumeListener.userChangeReceiver.onReceive(context, unlockIntent) + whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value) + verify(resumeBrowser, times(3)).findRecentMedia() + + // And the user changes before the MBS response is received + val changeIntent = + Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, secondUserId) + } + resumeListener.userChangeReceiver.onReceive(context, changeIntent) + callbackCaptor.value.addTrack(description, component, resumeBrowser) + + // Then the loaded media is correctly associated with the first user + verify(mediaDataManager) + .addResumptionControls( + eq(firstUserId), + eq(description), + any(), + eq(token), + eq(PACKAGE_NAME), + eq(pendingIntent), + eq(PACKAGE_NAME) + ) + } + + @Test + fun testUserUnlocked_noComponent_doesNotQuery() { + // Set up a valid MBS, but user does not have the service available + setUpMbsWithValidResolveInfo() + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + } + + // When the user is unlocked, but does not have the component installed + resumeListener.userChangeReceiver.onReceive(context, unlockIntent) + + // Then we never attempt to connect to it + verify(resumeBrowser, never()).findRecentMedia() + } + + /** Sets up mocks to successfully find a MBS that returns valid media. */ + private fun setUpMbsWithValidResolveInfo() { + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + val resolveInfo = ResolveInfo() + val serviceInfo = ServiceInfo() + serviceInfo.packageName = PACKAGE_NAME + resolveInfo.serviceInfo = serviceInfo + resolveInfo.serviceInfo.name = CLASS_NAME + val resumeInfo = listOf(resolveInfo) + whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo) + whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) + } } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index e254cd2c82a7..fb945f3ac3ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -24,6 +24,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.google.common.truth.Truth.assertThat; @@ -84,6 +85,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { BatteryController mBatteryController; @Mock Handler mMockHandler; + @Mock + PendingIntent mPendingIntent; private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider; @@ -399,6 +402,115 @@ public void testShouldNotHeadsUp_justLaunchedFullscreen() { assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + @Test + public void testShouldNotFullScreen_notPendingIntent() throws RemoteException { + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + + @Test + public void testShouldNotFullScreen_notHighImportance() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + + @Test + public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + + + @Test + public void testShouldNotFullScreen_isSuppressedByBubbleMetadata() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo") + .setSuppressNotification(true).build(); + entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + + @Test + public void testShouldFullScreen_notInteractive() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo") + .setSuppressNotification(false).build(); + entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + } + + @Test + public void testShouldFullScreen_isDreaming() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + } + + @Test + public void testShouldFullScreen_onKeyguard() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + } + + @Test + public void testShouldNotFullScreen_willHun() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + + @Test + public void testShouldFullScreen_packageSnoozed() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + } + /** * Bubbles can happen. */ @@ -502,6 +614,10 @@ private NotificationEntry createNotification(int importance) { .setContentText("content text") .build(); + return createNotification(importance, n); + } + + private NotificationEntry createNotification(int importance, Notification n) { return new NotificationEntryBuilder() .setPkg("a") .setOpPkg("a") @@ -511,6 +627,20 @@ private NotificationEntry createNotification(int importance) { .build(); } + private NotificationEntry createFsiNotification(int importance, boolean silent) { + Notification n = new Notification.Builder(getContext(), "a") + .setContentTitle("title") + .setContentText("content text") + .setFullScreenIntent(mPendingIntent, true) + .setGroup("fsi") + .setGroupAlertBehavior(silent + ? Notification.GROUP_ALERT_SUMMARY + : Notification.GROUP_ALERT_ALL) + .build(); + + return createNotification(importance, n); + } + private final NotificationInterruptSuppressor mSuppressAwakeHeadsUp = new NotificationInterruptSuppressor() { 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 299dd0c92ff2..484849f735df 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 @@ -40,6 +40,7 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.test.filters.SmallTest; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -252,7 +253,8 @@ public void setUp() { true, null, mFalsingManager, - mPeopleNotificationIdentifier + mPeopleNotificationIdentifier, + 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 0c6409b38d21..bb726191a09d 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.view.LayoutInflater; import android.widget.RemoteViews; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; @@ -424,7 +425,9 @@ private ExpandableNotificationRow generateRow( mock(ExpandableNotificationRow.OnAppOpsClickListener.class), mock(FalsingManager.class), mStatusBarStateController, - mPeopleNotificationIdentifier); + mPeopleNotificationIdentifier, + mock(IStatusBarService.class)); + row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); inflateAndWait(entry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 2ae4caeca963..ba3d95f9b178 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -354,6 +354,7 @@ public void testInflateFooterView() { @Test public void testUpdateFooter_noNotifications() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); assertEquals(0, mEntryManager.getActiveNotificationsCount()); FooterView view = mock(FooterView.class); @@ -365,6 +366,8 @@ public void testUpdateFooter_noNotifications() { @Test public void testUpdateFooter_remoteInput() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); + ArrayList entries = new ArrayList<>(); entries.add(new NotificationEntryBuilder().build()); addEntriesToEntryManager(entries); @@ -384,6 +387,7 @@ public void testUpdateFooter_remoteInput() { @Test public void testUpdateFooter_oneClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); ArrayList entries = new ArrayList<>(); entries.add(new NotificationEntryBuilder().build()); @@ -400,9 +404,30 @@ public void testUpdateFooter_oneClearableNotification() { verify(mStackScroller).updateFooterView(true, true, true); } + @Test + public void testUpdateFooter_oneClearableNotification_beforeUserSetup() { + setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(false); + + ArrayList entries = new ArrayList<>(); + entries.add(new NotificationEntryBuilder().build()); + addEntriesToEntryManager(entries); + + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + when(row.canViewBeDismissed()).thenReturn(true); + when(mStackScroller.getChildCount()).thenReturn(1); + when(mStackScroller.getChildAt(anyInt())).thenReturn(row); + + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + mStackScroller.updateFooter(); + verify(mStackScroller).updateFooterView(false, true, true); + } + @Test public void testUpdateFooter_oneNonClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); ArrayList entries = new ArrayList<>(); entries.add(new NotificationEntryBuilder().build()); @@ -416,6 +441,8 @@ public void testUpdateFooter_oneNonClearableNotification() { @Test public void testUpdateFooter_atEnd() { + mStackScroller.setCurrentUserSetup(true); + // add footer mStackScroller.inflateFooterView(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java index 8c37cf1514fd..6bdc13fa7d59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java @@ -18,6 +18,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static com.google.common.truth.Truth.assertThat; @@ -83,7 +84,12 @@ public void setUp() { mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager) { + @Override + protected boolean isDebuggable() { + return false; + } + }; mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -183,4 +189,20 @@ public void setKeyguardShowing_notFocusable_byDefault() { assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue(); assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue(); } + + @Test + public void setKeyguardShowing_enablesSecureFlag() { + mNotificationShadeWindowController.setBouncerShowing(true); + + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue(); + } + + @Test + public void setKeyguardNotShowing_disablesSecureFlag() { + mNotificationShadeWindowController.setBouncerShowing(false); + + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index c6dba6b8b800..2cc4ceea3f94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -815,12 +815,14 @@ public void testShowKeyguardImplementation_setsState() { // By default, showKeyguardImpl sets state to KEYGUARD. mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.KEYGUARD)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.KEYGUARD), eq(false) /* force */); // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER. when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true); mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */); } @Test diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 443a9bc33b90..b4166f0bedfd 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -89,4 +89,32 @@ without any consequences. [CHAR LIMIT=20] --> Dismiss + + + %1$s… ( + %2$s) + + + + + %1$s ( + %2$s) + diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index e66f2cc17a7f..a1fbffe50af7 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -44,12 +44,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 IConnectivityManager mService; + private View mView; + public ConfirmDialog() { this(VpnManager.TYPE_VPN_SERVICE); } @@ -58,6 +64,42 @@ public ConfirmDialog(@VpnManager.VpnType int vpnType) { 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<br>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); @@ -80,15 +122,16 @@ protected void onCreate(Bundle savedInstanceState) { 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/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 75fcc4c490ea..f9fbda357075 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -456,25 +456,27 @@ public void onPackageRemoved(String packageName, int uid) { userState.mBindingServices.removeIf(filter); userState.mCrashedServices.removeIf(filter); final Iterator it = userState.mEnabledServices.iterator(); + boolean anyServiceRemoved = false; while (it.hasNext()) { final ComponentName comp = it.next(); final String compPkg = comp.getPackageName(); if (compPkg.equals(packageName)) { it.remove(); - // Update the enabled services setting. - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - // Update the touch exploration granted services setting. userState.mTouchExplorationGrantedServices.remove(comp); - persistComponentNamesToSettingLocked( - Settings.Secure. - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - userState.mTouchExplorationGrantedServices, userId); - onUserStateChangedLocked(userState); - return; + anyServiceRemoved = true; } } + if (anyServiceRemoved) { + // Update the enabled services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, userId); + // Update the touch exploration granted services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + userState.mTouchExplorationGrantedServices, userId); + onUserStateChangedLocked(userState); + } } } @@ -791,7 +793,8 @@ public List getEnabledAccessibilityServiceList(int fee final List result = new ArrayList<>(serviceCount); for (int i = 0; i < serviceCount; ++i) { final AccessibilityServiceConnection service = services.get(i); - if ((service.mFeedbackType & feedbackType) != 0) { + if ((service.mFeedbackType & feedbackType) != 0 + || feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) { result.add(service.getServiceInfo()); } } @@ -1350,6 +1353,12 @@ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState u AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); + if (!accessibilityServiceInfo.isWithinParcelableSize()) { + Slog.e(LOG_TAG, "Skipping service " + + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + + " because service info size is larger than safe parcelable limits."); + continue; + } if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { // Restore the crashed attribute. accessibilityServiceInfo.crashed = true; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index d15c60b9501d..468e93a8f683 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -725,8 +725,7 @@ private int getTypeForWindowManagerWindowType(int windowType) { case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY: - case WindowManager.LayoutParams.TYPE_SCREENSHOT: - case WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY: { + case WindowManager.LayoutParams.TYPE_SCREENSHOT: { return AccessibilityWindowInfo.TYPE_SYSTEM; } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index d7a3a32f102a..f989f73b11b1 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -1315,11 +1315,12 @@ public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, In try { // Ask ActivityManager to bind it. Notice that we are binding the service with the // caller app instead of DevicePolicyManagerService. - if(ActivityManager.getService().bindService( + if (ActivityManager.getService().bindService( caller, activtiyToken, intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - connection, flags, mContext.getOpPackageName(), - widget.provider.getUserId()) != 0) { + connection, flags & (Context.BIND_AUTO_CREATE + | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE), + mContext.getOpPackageName(), widget.provider.getUserId()) != 0) { // Add it to the mapping of RemoteViewsService to appWidgetIds so that we // can determine when we can call back to the RemoteViewsService later to diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 80b0375a229d..8954a0c39091 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -18,6 +18,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.WindowNode; @@ -31,13 +33,18 @@ import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; + +import java.util.Arrays; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Helper { @@ -71,6 +78,44 @@ private Helper() { throw new UnsupportedOperationException("contains static members only"); } + private static boolean checkRemoteViewUriPermissions( + @UserIdInt int userId, @NonNull RemoteViews rView) { + final AtomicBoolean permissionsOk = new AtomicBoolean(true); + + rView.visitUris(uri -> { + int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri); + boolean allowed = uriOwnerId == userId; + permissionsOk.set(allowed && permissionsOk.get()); + }); + + return permissionsOk.get(); + } + + /** + * Checks the URI permissions of the remote view, + * to see if the current userId is able to access it. + * + * Returns the RemoteView that is passed if user is able, null otherwise. + * + * TODO: instead of returning a null remoteview when + * the current userId cannot access an URI, + * return a new RemoteView with the URI removed. + */ + public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) { + if (rView == null) return null; + + int userId = ActivityManager.getCurrentUser(); + + boolean ok = checkRemoteViewUriPermissions(userId, rView); + if (!ok) { + Slog.w(TAG, + "sanitizeRemoteView() user: " + userId + + " tried accessing resource that does not belong to them"); + } + return (ok ? rView : null); + } + + @Nullable static AutofillId[] toArray(@Nullable ArraySet set) { if (set == null) return null; diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 890208720f97..b35fd92e366d 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -141,8 +141,9 @@ public static boolean isFullScreen(Context context) { final LayoutInflater inflater = LayoutInflater.from(mContext); - final RemoteViews headerPresentation = response.getHeader(); - final RemoteViews footerPresentation = response.getFooter(); + final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader()); + final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter()); + final ViewGroup decor; if (mFullScreen) { decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null); @@ -220,6 +221,9 @@ public static boolean isFullScreen(Context context) { ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker); final View content; try { + if (Helper.sanitizeRemoteView(response.getPresentation()) == null) { + throw new RuntimeException("Permission error accessing RemoteView"); + } content = response.getPresentation().applyWithTheme( mContext, decor, interceptionHandler, mThemeId); container.addView(content); @@ -298,7 +302,8 @@ public static boolean isFullScreen(Context context) { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - final RemoteViews presentation = dataset.getFieldPresentation(index); + final RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldPresentation(index)); if (presentation == null) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " + "service didn't provide a presentation for it on " + dataset); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 1c3116699b2d..7b45ef881e94 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -361,8 +361,7 @@ private boolean applyCustomDescription(@NonNull Context context, @NonNull View s return false; } writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION); - - final RemoteViews template = customDescription.getPresentation(); + final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation()); if (template == null) { Slog.w(TAG, "No remote view on custom description"); return false; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ad91924c1e63..9f503419a7cf 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -43,7 +43,6 @@ import android.content.SharedPreferences; import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.net.NetworkPolicyManager; import android.os.Binder; @@ -385,20 +384,12 @@ public PendingIntent requestNotificationAccess(ComponentName component) String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); int userId = getCallingUserId(); - String packageTitle = BidiFormatter.getInstance().unicodeWrap( - getPackageInfo(callingPackage, userId) - .applicationInfo - .loadSafeLabel(getContext().getPackageManager(), - PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, - PackageItemInfo.SAFE_LABEL_FLAG_TRIM - | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE) - .toString()); - long identity = Binder.clearCallingIdentity(); + final long identity = Binder.clearCallingIdentity(); try { return PendingIntent.getActivityAsUser(getContext(), 0 /* request code */, NotificationAccessConfirmationActivityContract.launcherIntent( - userId, component, packageTitle), + getContext(), userId, component), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null /* options */, diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index fa84427ac064..920fe1b62417 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -198,6 +198,16 @@ public abstract void reportLocusUpdate(@NonNull ComponentName activity, @UserIdI */ public abstract void setActiveAdminApps(Set adminApps, int userId); + /** + * Called by DevicePolicyManagerService to inform about the protected packages for a user. + * User control will be disabled for protected packages. + * + * @param packageNames the set of protected packages for {@code userId}. + * @param userId the userId to which the protected packages belong. + */ + public abstract void setAdminProtectedPackages(@Nullable Set packageNames, + @UserIdInt int userId); + /** * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and * pushed to UsageStatsService. diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index b241bd16d3ee..83801ff0534c 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -998,4 +998,10 @@ public abstract void setIntegrityVerificationResult(int verificationId, * Returns {@code true} if the package is suspending any packages for the user. */ public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId); + + /** + * Get installed SDK version of the package + * @param pkg package for which to retrieve the installed sdk version + */ + public abstract int getInstalledSdkVersion(AndroidPackage pkg); } diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java index 38983488654e..502400dbd8ae 100644 --- a/services/core/java/android/os/UserManagerInternal.java +++ b/services/core/java/android/os/UserManagerInternal.java @@ -279,4 +279,12 @@ public abstract boolean isSettingRestrictedForUser(String setting, int userId, S * Gets all {@link UserInfo UserInfos}. */ public abstract @NonNull UserInfo[] getUserInfos(); + + /** + * Returns {@code true} if the system should ignore errors when preparing + * the storage directories for the user with ID {@code userId}. This will + * return {@code false} for all new users; it will only return {@code true} + * for users that already existed on-disk from an older version of Android. + */ + public abstract boolean shouldIgnorePrepareStorageErrors(int userId); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 7cdcc01bc00d..7cbe0c8ba57e 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -28,6 +28,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -75,6 +76,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.EventLog; import android.util.KeyValueListParser; import android.util.Log; import android.util.LongArrayQueue; @@ -270,6 +272,8 @@ interface Stats { * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. */ Bundle mIdleOptions; + ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic(); + BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic(); private final SparseArray mNextAlarmClockForUser = new SparseArray<>(); @@ -486,6 +490,7 @@ public void updateAllowWhileIdleWhitelistDurationLocked() { mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION; BroadcastOptions opts = BroadcastOptions.makeBasic(); opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION); + opts.setPendingIntentBackgroundActivityLaunchAllowed(false); mIdleOptions = opts.toBundle(); } } @@ -1480,6 +1485,8 @@ public void dumpDebug(ProtoOutputStream proto, long fieldId) { @Override public void onStart() { mInjector.init(); + mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); + mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); mListenerDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -1776,7 +1783,11 @@ void setImpl(int type, long triggerAtTime, long windowLength, long interval, if (DEBUG_PER_UID_LIMIT && UserHandle.isCore(callingUid)) { logAllAlarmsForUidLocked(callingUid); } - throw new IllegalStateException(errorMsg); + if (callingUid != Process.SYSTEM_UID) { + throw new IllegalStateException(errorMsg); + } else { + EventLog.writeEvent(0x534e4554, "234441463", -1, errorMsg); + } } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed, interval, operation, directReceiver, listenerTag, flags, true, workSource, @@ -4160,6 +4171,13 @@ private static int getAlarmAttributionUid(Alarm alarm) { return alarm.creatorUid; } + private Bundle getAlarmOperationBundle(Alarm alarm) { + if (alarm.operation.isActivity()) { + return mActivityOptsRestrictBal.toBundle(); + } + return mBroadcastOptsRestrictBal.toBundle(); + } + @VisibleForTesting class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; @@ -4198,7 +4216,11 @@ public void handleMessage(Message msg) { for (int i=0; i 0) { // This IntentSender is no longer valid, but this @@ -4734,7 +4756,7 @@ public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) mBackgroundIntent.putExtra( Intent.EXTRA_ALARM_COUNT, alarm.count), mDeliveryTracker, mHandler, null, - allowWhileIdle ? mIdleOptions : null); + allowWhileIdle ? mIdleOptions : getAlarmOperationBundle(alarm)); } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this diff --git a/services/core/java/com/android/server/PendingIntentUtils.java b/services/core/java/com/android/server/PendingIntentUtils.java index 1600101b20f4..a72a4d254a2a 100644 --- a/services/core/java/com/android/server/PendingIntentUtils.java +++ b/services/core/java/com/android/server/PendingIntentUtils.java @@ -34,6 +34,7 @@ public class PendingIntentUtils { public static Bundle createDontSendToRestrictedAppsBundle(@Nullable Bundle bundle) { final BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); if (bundle == null) { return options.toBundle(); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 46538d0207f6..f34b56df35e8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -132,6 +132,7 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.DataUnit; +import android.util.EventLog; import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; @@ -3411,7 +3412,21 @@ private void prepareUserStorageInternal(String volumeUuid, int userId, int seria } } } catch (Exception e) { + EventLog.writeEvent(0x534e4554, "224585613", -1, ""); Slog.wtf(TAG, e); + // Make sure to re-throw this exception; we must not ignore failure + // to prepare the user storage as it could indicate that encryption + // wasn't successfully set up. + // + // Very unfortunately, these errors need to be ignored for broken + // users that already existed on-disk from older Android versions. + UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + if (umInternal.shouldIgnorePrepareStorageErrors(userId)) { + Slog.wtf(TAG, "ignoring error preparing storage for existing user " + userId + + "; device may be insecure!"); + return; + } + throw new RuntimeException(e); } } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index db3c25a7e43a..ae72a65692e7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -87,6 +87,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -1806,6 +1807,14 @@ private boolean addAccountInternal(UserAccounts accounts, Account account, Strin if (account == null) { return false; } + if (account.name != null && account.name.length() > 200) { + Log.w(TAG, "Account cannot be added - Name longer than 200 chars"); + return false; + } + if (account.type != null && account.type.length() > 200) { + Log.w(TAG, "Account cannot be added - Name longer than 200 chars"); + return false; + } if (!isLocalUnlockedUser(accounts.userId)) { Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user " + accounts.userId + " is locked. callingUid=" + callingUid); @@ -1820,6 +1829,11 @@ private boolean addAccountInternal(UserAccounts accounts, Account account, Strin + ", skipping since the account already exists"); return false; } + if (accounts.accountsDb.findAllDeAccounts().size() > 100) { + Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString() + + ", skipping since more than 50 accounts on device exist"); + return false; + } long accountId = accounts.accountsDb.insertCeAccount(account, password); if (accountId < 0) { Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString() @@ -1994,6 +2008,10 @@ public void renameAccount( + ", pid " + Binder.getCallingPid()); } if (accountToRename == null) throw new IllegalArgumentException("account is null"); + if (newName != null && newName.length() > 200) { + Log.e(TAG, "renameAccount failed - account name longer than 200"); + throw new IllegalArgumentException("account name longer than 200"); + } int userId = UserHandle.getCallingUserId(); if (!isAccountManagedByCaller(accountToRename.type, callingUid, userId)) { String msg = String.format( @@ -3005,7 +3023,7 @@ public void onResult(Bundle result) { */ if (!checkKeyIntent( Binder.getCallingUid(), - intent)) { + result)) { onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "invalid intent in bundle returned"); return; @@ -3412,11 +3430,10 @@ public void onResult(Bundle result) { Bundle.setDefusable(result, true); mNumResults++; Intent intent = null; - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), - intent)) { + result)) { onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "invalid intent in bundle returned"); return; @@ -4767,16 +4784,20 @@ IAccountManagerResponse getResponseAndClose() { * into launching arbitrary intents on the device via by tricking to click authenticator * supplied entries in the system Settings app. */ - protected boolean checkKeyIntent(int authUid, Intent intent) { + protected boolean checkKeyIntent(int authUid, Bundle bundle) { + if (!checkKeyIntentParceledCorrectly(bundle)) { + EventLog.writeEvent(0x534e4554, "250588548", authUid, ""); + return false; + } + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent == null) { + return true; + } // Explicitly set an empty ClipData to ensure that we don't offer to // promote any Uris contained inside for granting purposes if (intent.getClipData() == null) { intent.setClipData(ClipData.newPlainText(null, null)); } - intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)); long bid = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); @@ -4804,6 +4825,43 @@ protected boolean checkKeyIntent(int authUid, Intent intent) { } } + /** + * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't + * violate our security policy. + * + * In particular we want to make sure the Authenticator doesn't trick users + * into launching arbitrary intents on the device via exploiting any other Parcel read/write + * mismatch problems. + */ + private boolean checkKeyIntentParceledCorrectly(Bundle bundle) { + Parcel p = Parcel.obtain(); + p.writeBundle(bundle); + p.setDataPosition(0); + Bundle simulateBundle = p.readBundle(); + p.recycle(); + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent != null && intent.getClass() != Intent.class) { + return false; + } + Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT); + if (intent == null) { + return (simulateIntent == null); + } + if (!intent.filterEquals(simulateIntent)) { + return false; + } + + if (intent.getSelector() != simulateIntent.getSelector()) { + return false; + } + + int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; + return (simulateIntent.getFlags() & prohibitedFlags) == 0; + } + private boolean isExportedSystemActivity(ActivityInfo activityInfo) { String className = activityInfo.name; return "android".equals(activityInfo.packageName) && @@ -4946,11 +5004,10 @@ public void onResult(Bundle result) { } } } - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), - intent)) { + result)) { onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "invalid intent in bundle returned"); return; diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index ed83a644cbfb..95da946496e8 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -18,6 +18,7 @@ import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; +import android.annotation.NonNull; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.Notification; @@ -171,6 +172,12 @@ protected AdbDebuggingManager(Context context, String confirmComponent, File tes mAdbConnectionInfo = new AdbConnectionInfo(); } + static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent, + @NonNull UserHandle userHandle) { + context.sendBroadcastAsUser(intent, userHandle, + android.Manifest.permission.MANAGE_DEBUGGING); + } + class PairingThread extends Thread implements NsdManager.RegistrationListener { private NsdManager mNsdManager; private String mPublicKey; @@ -1279,7 +1286,7 @@ private void sendServerConnectionState(boolean connected, int port) { ? AdbManager.WIRELESS_STATUS_CONNECTED : AdbManager.WIRELESS_STATUS_DISCONNECTED); intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); } private void onAdbdWifiServerConnected(int port) { @@ -1351,7 +1358,8 @@ private void onPairingResult(String publicKey) { if (publicKey == null) { Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_FAIL); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, + UserHandle.ALL); } else { Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, @@ -1364,7 +1372,8 @@ private void onPairingResult(String publicKey) { } PairDevice device = new PairDevice(fingerprints, hostname, false); intent.putExtra(AdbManager.WIRELESS_PAIR_DEVICE_EXTRA, device); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, + UserHandle.ALL); // Add the key into the keystore mAdbKeyStore.setLastConnectionTime(publicKey, System.currentTimeMillis()); @@ -1378,14 +1387,14 @@ private void sendPairingPortToUI(int port) { intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_CONNECTED); intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); } private void sendPairedDevicesToUI(Map devices) { Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION); // Map is not serializable, so need to downcast intent.putExtra(AdbManager.WIRELESS_DEVICES_EXTRA, (HashMap) devices); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); } private void updateUIPairCode(String code) { @@ -1395,7 +1404,7 @@ private void updateUIPairCode(String code) { intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code); intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_PAIRING_CODE); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); } } diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 29bb5428dd84..5b16daa5e835 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -431,7 +431,7 @@ private void broadcastPortInfo(int port) { ? AdbManager.WIRELESS_STATUS_CONNECTED : AdbManager.WIRELESS_STATUS_DISCONNECTED); intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); Slog.i(TAG, "sent port broadcast port=" + port); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 1cd7115f8edc..7c81d2aaaf0b 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1488,7 +1488,6 @@ must call startForeground() within a timeout anyway, so we don't need this final long delayMs = SystemClock.elapsedRealtime() - r.mLastSetFgsRestrictionTime; if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { - resetFgsRestrictionLocked(r); setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.pid, r.appInfo.uid, r, false); EventLog.writeEvent(0x534e4554, "183147114", r.appInfo.uid, @@ -2518,6 +2517,11 @@ private ServiceLookupResult retrieveServiceLocked(Intent service, throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not an isolatedProcess"); } + if (AppGlobals.getPackageManager().getPackageUid(callingPackage, + 0, userId) != callingUid) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + + "calling package not owned by calling UID "); + } // Run the service under the calling package's application. ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo( callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId); @@ -5002,14 +5006,16 @@ private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage return true; } - if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) { - return true; - } - - final boolean isWhiteListedPackage = - mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage); - if (isWhiteListedPackage) { - return true; + if (verifyPackage(callingPackage, callingUid)) { + final boolean isWhiteListedPackage = + mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage); + if (isWhiteListedPackage) { + return true; + } + } else { + EventLog.writeEvent(0x534e4554, "215003903", callingUid, + "callingPackage:" + callingPackage + " does not belong to callingUid:" + + callingUid); } // Is the calling UID a device owner app? @@ -5046,5 +5052,23 @@ private void setFgsRestrictionLocked(String callingPackage, private void resetFgsRestrictionLocked(ServiceRecord r) { r.mAllowWhileInUsePermissionInFgs = false; + r.mLastSetFgsRestrictionTime = 0; + } + + /** + * Checks if a given packageName belongs to a given uid. + * @param packageName the package of the caller + * @param uid the uid of the caller + * @return true or false + */ + private boolean verifyPackage(String packageName, int uid) { + if (uid == ROOT_UID || uid == SYSTEM_UID) { + //System and Root are always allowed + return true; + } + final int userId = UserHandle.getUserId(uid); + final int packageUid = mAm.getPackageManagerInternalLocked() + .getPackageUid(packageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId); + return UserHandle.isSameApp(uid, packageUid); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a8d981e2f64c..34074bb2ab90 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -162,6 +162,7 @@ import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.app.ApplicationThreadConstants; +import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.ContentProviderHolder; import android.app.IActivityController; @@ -364,6 +365,7 @@ import com.android.server.firewall.IntentFirewall; import com.android.server.job.JobSchedulerInternal; import com.android.server.pm.Installer; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.uri.GrantUri; import com.android.server.uri.NeededUriGrants; @@ -3270,6 +3272,22 @@ CompatibilityInfo compatibilityInfoForPackage(ApplicationInfo ai) { } } + /** + * Enforces that the uid of the caller matches the uid of the package. + * + * @param packageName the name of the package to match uid against. + * @param callingUid the uid of the caller. + * @throws SecurityException if the calling uid doesn't match uid of the package. + */ + private void enforceCallingPackage(String packageName, int callingUid) { + final int userId = UserHandle.getUserId(callingUid); + final int packageUid = getPackageManagerInternalLocked().getPackageUid(packageName, + /*flags=*/ 0, userId); + if (packageUid != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); + } + } + @Override public void setPackageScreenCompatMode(String packageName, int mode) { mActivityTaskManager.setPackageScreenCompatMode(packageName, mode); @@ -4288,20 +4306,24 @@ public void onRemoveCompleted(String packageName, boolean succeeded) finishForceStopPackageLocked(packageName, appInfo.uid); } } - final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, - Uri.fromParts("package", packageName, null)); - intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1); - intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId); - if (isInstantApp) { - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent, - null, null, 0, null, null, permission.ACCESS_INSTANT_APPS, null, - false, false, resolvedUserId, false); - } else { - broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent, - null, null, 0, null, null, null, null, false, false, resolvedUserId, - false); + + if (succeeded) { + final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, + Uri.fromParts("package", packageName, null /* fragment */)); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + intent.putExtra(Intent.EXTRA_UID, + (appInfo != null) ? appInfo.uid : Process.INVALID_UID); + intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId); + if (isInstantApp) { + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + } + + broadcastIntentInPackage("android", null /* featureId */, SYSTEM_UID, + uid, pid, intent, null /* resolvedType */, null /* resultTo */, + 0 /* resultCode */, null /* resultData */, null /* resultExtras */, + isInstantApp ? permission.ACCESS_INSTANT_APPS : null, + null /* bOptions */, false /* serialized */, false /* sticky */, + resolvedUserId, false /* allowBackgroundActivityStarts */); } if (observer != null) { @@ -4357,8 +4379,20 @@ public void killBackgroundProcesses(final String packageName, int userId) { Slog.w(TAG, msg); throw new SecurityException(msg); } + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int callingAppId = UserHandle.getAppId(callingUid); - userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(callingPid); + } + final boolean hasKillAllPermission = PERMISSION_GRANTED == checkPermission( + android.Manifest.permission.FORCE_STOP_PACKAGES, callingPid, callingUid) + || UserHandle.isCore(callingUid) + || (proc != null && proc.info.isSystemApp()); + + userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null); final int[] userIds = mUserController.expandUserId(userId); @@ -4373,7 +4407,7 @@ public void killBackgroundProcesses(final String packageName, int userId) { targetUserId)); } catch (RemoteException e) { } - if (appId == -1) { + if (appId == -1 || (!hasKillAllPermission && appId != callingAppId)) { Slog.w(TAG, "Invalid packageName: " + packageName); return; } @@ -4399,6 +4433,22 @@ public void killAllBackgroundProcesses() { throw new SecurityException(msg); } + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(callingPid); + } + if (callingUid >= FIRST_APPLICATION_UID + && (proc == null || !proc.info.isSystemApp())) { + final String msg = "Permission Denial: killAllBackgroundProcesses() from pid=" + + callingPid + ", uid=" + callingUid + " is not allowed"; + Slog.w(TAG, msg); + // Silently return to avoid existing apps from crashing. + return; + } + final long callingId = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -8297,7 +8347,54 @@ public ParcelFileDescriptor openContentUri(String uriString) throws RemoteExcept Binder token = new Binder(); sCallerIdentity.set(new Identity( token, Binder.getCallingPid(), Binder.getCallingUid())); + boolean handlingSecurityViolation = false; try { + // This method is exposed to the VNDK and to avoid changing its + // signature we just use the first package in the UID. For shared + // UIDs we may blame the wrong app but that is Okay as they are + // in the same security/privacy sandbox. + final int uid = Binder.getCallingUid(); + // Here we handle some of the special UIDs (mediaserver, systemserver, etc) + // Note: This is moved to AppOpsManager.resolvePackageName in future versions. + final String packageName; + if (uid == Process.ROOT_UID) { + packageName = "root"; + } else if (uid == Process.SHELL_UID) { + packageName = "com.android.shell"; + } else if (uid == Process.MEDIA_UID) { + packageName = "media"; + } else if (uid == Process.AUDIOSERVER_UID) { + packageName = "audioserver"; + } else if (uid == Process.CAMERASERVER_UID) { + packageName = "cameraserver"; + } else if (uid == Process.SYSTEM_UID) { + packageName = "android"; + } else { + packageName = null; + } + + final AndroidPackage androidPackage; + if (packageName != null) { + androidPackage = mPackageManagerInt.getPackage(packageName); + } else { + androidPackage = mPackageManagerInt.getPackage(uid); + } + if (androidPackage == null) { + Log.e(TAG, "Cannot find package for uid: " + uid); + handlingSecurityViolation = true; + return null; + } + + final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo( + androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID, + UserHandle.USER_SYSTEM); + if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt() + && !appInfo.isProduct()) { + Log.e(TAG, "openContentUri may only be used by vendor/system/product."); + handlingSecurityViolation = true; + return null; + } + pfd = cph.provider.openFile(null, null, uri, "r", null, token); } catch (FileNotFoundException e) { // do nothing; pfd will be returned null @@ -8305,7 +8402,16 @@ public ParcelFileDescriptor openContentUri(String uriString) throws RemoteExcept // Ensure that whatever happens, we clean up the identity state sCallerIdentity.remove(); // Ensure we're done with the provider. - removeContentProviderExternalUnchecked(name, null, userId); + try { + removeContentProviderExternalUnchecked(name, null, userId); + } catch (SecurityException e) { + // A SecurityException may be thrown from computeOomAdjLocked if the calling + // UID is that of a malicious app accessing this hidden API. In that case + // we're already handling that by returning null, so tolerate this. + if (!handlingSecurityViolation) { + throw e; + } + } } } else { Slog.d(TAG, "Failed to get provider for authority '" + name + "'"); @@ -10511,6 +10617,13 @@ public int getMemoryTrimLevel() { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != ROOT_UID && callingUid != Process.SHELL_UID) { + if (resultReceiver != null) { + resultReceiver.send(-1, null); + } + throw new SecurityException("Shell commands are only callable by root or shell"); + } (new ActivityManagerShellCommand(this, false)).exec( this, in, out, err, args, callback, resultReceiver); } @@ -15360,13 +15473,16 @@ private void clearPendingBackup(int userId) { // A backup agent has just come up @Override public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(agentPackageName, callingUid); + // Resolve the target user id and enforce permissions. - userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null); if (DEBUG_BACKUP) { Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent + " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId - + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid()); + + " callingUid = " + callingUid + " uid = " + Process.myUid()); } synchronized(this) { diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 1997dbd6fc37..52379d5f9ab0 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -286,6 +286,25 @@ public int sendWithResult(int code, Intent intent, String resolvedType, IBinder requiredPermission, null, null, 0, 0, 0, options); } + /** + * Return true if the activity options allows PendingIntent to use caller's BAL permission. + */ + public static boolean isPendingIntentBalAllowedByCaller( + @Nullable ActivityOptions activityOptions) { + if (activityOptions == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return isPendingIntentBalAllowedByCaller(activityOptions.toBundle()); + } + + private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) { + if (options == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT); + } + public int sendInner(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { @@ -398,7 +417,8 @@ public int sendInner(int code, Intent intent, String resolvedType, IBinder white // temporarily allow receivers and services to open activities from background if the // PendingIntent.send() caller was foreground at the time of sendInner() call final boolean allowTrampoline = uid != callingUid - && controller.mAtmInternal.isUidForeground(callingUid); + && controller.mAtmInternal.isUidForeground(callingUid) + && isPendingIntentBalAllowedByCaller(options); // note: we on purpose don't pass in the information about the PendingIntent's creator, // like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java index f6ce2dc68b99..621c9c71df58 100644 --- a/services/core/java/com/android/server/connectivity/PacManager.java +++ b/services/core/java/com/android/server/connectivity/PacManager.java @@ -37,6 +37,7 @@ import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; +import android.webkit.URLUtil; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.TrafficStatsConstants; @@ -215,8 +216,22 @@ synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) { * @throws IOException if the URL is malformed, or the PAC file is too big. */ private static String get(Uri pacUri) throws IOException { - URL url = new URL(pacUri.toString()); - URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY); + if (!URLUtil.isValidUrl(pacUri.toString())) { + throw new IOException("Malformed URL:" + pacUri); + } + + final URL url = new URL(pacUri.toString()); + URLConnection urlConnection; + try { + urlConnection = url.openConnection(java.net.Proxy.NO_PROXY); + // Catch the possible exceptions and rethrow as IOException to not to crash the system + // for illegal input. + } catch (IllegalArgumentException e) { + throw new IOException("Incorrect proxy type for " + pacUri); + } catch (UnsupportedOperationException e) { + throw new IOException("Unsupported URL connection type for " + pacUri); + } + long contentLength = -1; try { contentLength = Long.parseLong(urlConnection.getHeaderField("Content-Length")); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 9edb0e440baa..fd5b46cd0ff7 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -2157,6 +2157,13 @@ public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore, "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400", (profile.mppe ? "+mppe" : "nomppe"), }; + if (profile.mppe) { + // Disallow PAP authentication when MPPE is requested, as MPPE cannot work + // with PAP anyway, and users may not expect PAP (plain text) to be used when + // MPPE was requested. + mtpd = Arrays.copyOf(mtpd, mtpd.length + 1); + mtpd[mtpd.length - 1] = "-pap"; + } break; case VpnProfile.TYPE_L2TP_IPSEC_PSK: case VpnProfile.TYPE_L2TP_IPSEC_RSA: @@ -2516,6 +2523,9 @@ public void onDefaultNetworkChanged(@NonNull Network network) { return; // VPN has been shut down. } + // Clear mInterface to prevent Ikev2VpnRunner being cleared when + // interfaceRemoved() is called. + mInterface = null; // Without MOBIKE, we have no way to seamlessly migrate. Close on old // (non-default) network, and start the new one. resetIkeState(); diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 551df49b550f..57323170b327 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -91,10 +91,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { private boolean mPendingStatusChangeBroadcast; - private static final String[] RECEIVER_PERMISSIONS_FOR_BROADCAST = { - android.Manifest.permission.ACCESS_FINE_LOCATION, - }; - // Called with SyncRoot lock held. public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, @@ -436,8 +432,7 @@ private void handleSendStatusChangeBroadcast() { } // Send protected broadcast about wifi display status to registered receivers. - getContext().createContextAsUser(UserHandle.ALL, 0) - .sendBroadcastWithMultiplePermissions(intent, RECEIVER_PERMISSIONS_FOR_BROADCAST); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3e7437d1ee7e..eca500ec612b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -102,6 +102,7 @@ import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; import android.view.DisplayInfo; import android.view.IWindowManager; @@ -317,6 +318,8 @@ private static final class DebugFlags { final InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; final IWindowManager mIWindowManager; + private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid = + new SparseBooleanArray(0); final WindowManagerInternal mWindowManagerInternal; final PackageManagerInternal mPackageManagerInternal; final InputManagerInternal mInputManagerInternal; @@ -1409,6 +1412,13 @@ public void onFinishPackageChanges() { clearPackageChangeState(); } + @Override + public void onUidRemoved(int uid) { + synchronized (mMethodMap) { + mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid); + } + } + private void clearPackageChangeState() { // No need to lock them because we access these fields only on getRegisteredHandler(). mChangedPackages.clear(); @@ -3185,20 +3195,8 @@ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int } final long ident = Binder.clearCallingIdentity(); try { - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - // We need to check if this is the current client with - // focus in the window manager, to allow this call to - // be made before input is started in it. - final ClientState cs = mClients.get(client.asBinder()); - if (cs == null) { - throw new IllegalArgumentException("unknown client " + client.asBinder()); - } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { - Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); - return false; - } + if (!canInteractWithImeLocked(uid, client, "showSoftInput")) { + return false; } if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(windowToken, flags, resultReceiver, @@ -3979,9 +3977,46 @@ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight()} */ @Override - public int getInputMethodWindowVisibleHeight() { - // TODO(yukawa): Should we verify the display ID? - return mWindowManagerInternal.getInputMethodWindowVisibleHeight(mCurTokenDisplayId); + @Deprecated + public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) { + int callingUid = Binder.getCallingUid(); + return Binder.withCleanCallingIdentity(() -> { + final int curTokenDisplayId; + synchronized (mMethodMap) { + if (!canInteractWithImeLocked(callingUid, client, + "getInputMethodWindowVisibleHeight")) { + if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) { + EventLog.writeEvent(0x534e4554, "204906124", callingUid, ""); + mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true); + } + return 0; + } + // This should probably use the caller's display id, but because this is unsupported + // and maintained only for compatibility, there's no point in fixing it. + curTokenDisplayId = mCurTokenDisplayId; + } + return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); + }); + } + + private boolean canInteractWithImeLocked(int callingUid, IInputMethodClient client, + String method) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + // We need to check if this is the current client with + // focus in the window manager, to allow this call to + // be made before input is started in it. + final ClientState cs = mClients.get(client.asBinder()); + if (cs == null) { + throw new IllegalArgumentException("unknown client " + client.asBinder()); + } + if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, + cs.selfReportedDisplayId)) { + Slog.w(TAG, "Ignoring " + method + " of uid " + callingUid + ": " + client); + return false; + } + } + return true; } @Override diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 19dff9807075..3429e7bb6bba 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1754,7 +1754,7 @@ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] @BinderThread @Override - public int getInputMethodWindowVisibleHeight() { + public int getInputMethodWindowVisibleHeight(IInputMethodClient client) { reportNotSupported(); return 0; } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 348e9c122a9a..5ea5d94120f3 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -18,6 +18,8 @@ import android.annotation.Nullable; import android.app.PendingIntent; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -49,11 +51,13 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.view.KeyEvent; import com.android.server.LocalServices; +import com.android.server.uri.UriGrantsManagerInternal; import java.io.PrintWriter; import java.util.ArrayList; @@ -67,6 +71,10 @@ // TODO(jaewan): Do not call service method directly -- introduce listener instead. public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { private static final String TAG = "MediaSessionRecord"; + private static final String[] ART_URIS = new String[] { + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + MediaMetadata.METADATA_KEY_ART_URI, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI}; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** @@ -120,6 +128,7 @@ private static int getVolumeStream(@Nullable AudioAttributes attr) { private final SessionStub mSession; private final SessionCb mSessionCb; private final MediaSessionService mService; + private final UriGrantsManagerInternal mUgmInternal; private final Context mContext; private final Object mLock = new Object(); @@ -182,6 +191,7 @@ public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPa mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioAttrs = DEFAULT_ATTRIBUTES; mPolicies = policies; + mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -859,21 +869,45 @@ public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException { public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) throws RemoteException { synchronized (mLock) { - MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata) - .build(); - // This is to guarantee that the underlying bundle is unparceled - // before we set it to prevent concurrent reads from throwing an - // exception - if (temp != null) { - temp.size(); - } - mMetadata = temp; mDuration = duration; mMetadataDescription = metadataDescription; + mMetadata = sanitizeMediaMetadata(metadata); } mHandler.post(MessageHandler.MSG_UPDATE_METADATA); } + private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) { + if (metadata == null) { + return null; + } + MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata); + for (String key: ART_URIS) { + String uriString = metadata.getString(key); + if (TextUtils.isEmpty(uriString)) { + continue; + } + Uri uri = Uri.parse(uriString); + if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + continue; + } + try { + mUgmInternal.checkGrantUriPermission(getUid(), + getPackageName(), + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, getUserId())); + } catch (SecurityException e) { + metadataBuilder.putString(key, null); + } + } + MediaMetadata sanitizedMetadata = metadataBuilder.build(); + // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled + // before we set it to prevent concurrent reads from throwing an + // exception + sanitizedMetadata.size(); + return sanitizedMetadata; + } + @Override public void setPlaybackState(PlaybackState state) throws RemoteException { int oldState = mPlaybackState == null diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e967e59ebe0b..a20ba696df94 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -227,6 +227,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.StatsEvent; import android.util.TimeUtils; import android.util.Xml; @@ -258,6 +259,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; +import com.android.internal.widget.LockPatternUtils; import com.android.server.DeviceIdleInternal; import com.android.server.EventLogTags; import com.android.server.IoThread; @@ -595,7 +597,14 @@ public Iterator> descendingIterator() { return mBuffer.descendingIterator(); } - public StatusBarNotification[] getArray(int count, boolean includeSnoozed) { + public StatusBarNotification[] getArray(UserManager um, int count, boolean includeSnoozed) { + ArrayList currentUsers = new ArrayList<>(); + currentUsers.add(UserHandle.USER_ALL); + Binder.withCleanCallingIdentity(() -> { + for (int user : um.getProfileIds(ActivityManager.getCurrentUser(), false)) { + currentUsers.add(user); + } + }); if (count == 0) count = mBufferSize; List a = new ArrayList(); Iterator> iter = descendingIterator(); @@ -603,8 +612,10 @@ public StatusBarNotification[] getArray(int count, boolean includeSnoozed) { while (iter.hasNext() && i < count) { Pair pair = iter.next(); if (pair.second != REASON_SNOOZED || includeSnoozed) { - i++; - a.add(pair.first); + if (currentUsers.contains(pair.first.getUserId())) { + i++; + a.add(pair.first); + } } } return a.toArray(new StatusBarNotification[a.size()]); @@ -1720,6 +1731,58 @@ static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { return out; } + protected class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { + + SparseBooleanArray mUserInLockDownMode = new SparseBooleanArray(); + boolean mIsInLockDownMode = false; + + StrongAuthTracker(Context context) { + super(context); + } + + private boolean containsFlag(int haystack, int needle) { + return (haystack & needle) != 0; + } + + // Return whether the user is in lockdown mode. + // If the flag is not set, we assume the user is not in lockdown. + public boolean isInLockDownMode(int userId) { + return mUserInLockDownMode.get(userId, false); + } + + @Override + public synchronized void onStrongAuthRequiredChanged(int userId) { + boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + // Nothing happens if the lockdown mode of userId keeps the same. + if (userInLockDownModeNext == isInLockDownMode(userId)) { + return; + } + + // When the lockdown mode is changed, we perform the following steps. + // If the userInLockDownModeNext is true, all the function calls to + // notifyPostedLocked and notifyRemovedLocked will not be executed. + // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked + // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. + // So we shall call cancelNotificationsWhenEnterLockDownMode before + // we set mUserInLockDownMode as true. + // On the other hand, if the userInLockDownModeNext is false, we shall call + // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode + if (userInLockDownModeNext) { + cancelNotificationsWhenEnterLockDownMode(userId); + } + + mUserInLockDownMode.put(userId, userInLockDownModeNext); + + if (!userInLockDownModeNext) { + postNotificationsWhenExitLockDownMode(userId); + } + } + } + + private StrongAuthTracker mStrongAuthTracker; + public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), @@ -1745,6 +1808,11 @@ void setAudioManager(AudioManager audioMananger) { mAudioManager = audioMananger; } + @VisibleForTesting + void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) { + mStrongAuthTracker = strongAuthTracker; + } + @VisibleForTesting void setKeyguardManager(KeyguardManager keyguardManager) { mKeyguardManager = keyguardManager; @@ -1932,6 +2000,7 @@ void init(WorkerHandler handler, RankingHandler rankingHandler, ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mUiHandler = new Handler(UiThread.get().getLooper()); + mStrongAuthTracker = new StrongAuthTracker(getContext()); String[] extractorNames; try { extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors); @@ -2128,7 +2197,8 @@ public void onStart() { init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), - new NotificationListeners(AppGlobals.getPackageManager()), + new NotificationListeners(getContext(), mNotificationLock, mUserProfiles, + AppGlobals.getPackageManager()), new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles, AppGlobals.getPackageManager()), new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()), @@ -2378,6 +2448,7 @@ public void onBootPhase(int phase) { bubbsExtractor.setShortcutHelper(mShortcutHelper); } registerNotificationPreferencesPullers(); + new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. @@ -2504,7 +2575,7 @@ private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel pr } } - private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, + void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromApp, boolean fromListener) { Objects.requireNonNull(group); Objects.requireNonNull(pkg); @@ -3537,7 +3608,8 @@ public void deleteNotificationChannelGroup(String pkg, String groupId) { final int callingUid = Binder.getCallingUid(); NotificationChannelGroup groupToDelete = - mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid); + mPreferencesHelper.getNotificationChannelGroupWithChannels( + pkg, callingUid, groupId, false); if (groupToDelete != null) { // Preflight for allowability final int userId = UserHandle.getUserId(callingUid); @@ -3830,22 +3902,32 @@ public StatusBarNotification[] getActiveNotificationsWithAttribution(String call android.Manifest.permission.ACCESS_NOTIFICATIONS, "NotificationManagerService.getActiveNotifications"); - StatusBarNotification[] tmp = null; + ArrayList tmp = new ArrayList<>(); int uid = Binder.getCallingUid(); + ArrayList currentUsers = new ArrayList<>(); + currentUsers.add(UserHandle.USER_ALL); + Binder.withCleanCallingIdentity(() -> { + for (int user : mUm.getProfileIds(ActivityManager.getCurrentUser(), false)) { + currentUsers.add(user); + } + }); + // noteOp will check to make sure the callingPkg matches the uid if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg, callingAttributionTag, null) == AppOpsManager.MODE_ALLOWED) { synchronized (mNotificationLock) { - tmp = new StatusBarNotification[mNotificationList.size()]; final int N = mNotificationList.size(); - for (int i=0; i NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + throw new IllegalArgumentException( + "Component name too long: " + listener.flattenToString()); + } final long identity = Binder.clearCallingIdentity(); try { if (mAllowedManagedServicePackages.test( @@ -5791,8 +5888,10 @@ void enqueueNotificationInternal(final String pkg, final String opPkg, final int // Fix the notification as best we can. try { fixNotification(notification, pkg, tag, id, userId); - } catch (Exception e) { + if (notification.isForegroundService()) { + throw new SecurityException("Invalid FGS notification", e); + } Slog.e(TAG, "Cannot fix notification", e); return; } @@ -6254,6 +6353,7 @@ public void run() { @GuardedBy("mNotificationLock") void snoozeLocked(NotificationRecord r) { + final List recordsToSnooze = new ArrayList<>(); if (r.getSbn().isGroup()) { final List groupNotifications = findCurrentAndSnoozedGroupNotificationsLocked( @@ -6262,8 +6362,8 @@ void snoozeLocked(NotificationRecord r) { if (r.getNotification().isGroupSummary()) { // snooze all children for (int i = 0; i < groupNotifications.size(); i++) { - if (mKey != groupNotifications.get(i).getKey()) { - snoozeNotificationLocked(groupNotifications.get(i)); + if (!mKey.equals(groupNotifications.get(i).getKey())) { + recordsToSnooze.add(groupNotifications.get(i)); } } } else { @@ -6273,8 +6373,8 @@ void snoozeLocked(NotificationRecord r) { if (groupNotifications.size() == 2) { // snooze summary and the one child for (int i = 0; i < groupNotifications.size(); i++) { - if (mKey != groupNotifications.get(i).getKey()) { - snoozeNotificationLocked(groupNotifications.get(i)); + if (!mKey.equals(groupNotifications.get(i).getKey())) { + recordsToSnooze.add(groupNotifications.get(i)); } } } @@ -6282,7 +6382,15 @@ void snoozeLocked(NotificationRecord r) { } } // snooze the notification - snoozeNotificationLocked(r); + recordsToSnooze.add(r); + + if (mSnoozeHelper.canSnooze(recordsToSnooze.size())) { + for (int i = 0; i < recordsToSnooze.size(); i++) { + snoozeNotificationLocked(recordsToSnooze.get(i)); + } + } else { + Log.w(TAG, "Cannot snooze " + r.getKey() + ": too many snoozed notifications"); + } } @@ -6984,7 +7092,8 @@ int buzzBeepBlinkLocked(NotificationRecord record) { && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0; if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN - && !suppressedByDnd) { + && !suppressedByDnd + && isNotificationForCurrentUser(record)) { sendAccessibilityEvent(record); sentAccessibilityEvent = true; } @@ -8605,6 +8714,41 @@ protected void unhideNotificationsForPackages(String[] pkgs) { } } + private void cancelNotificationsWhenEnterLockDownMode(int userId) { + synchronized (mNotificationLock) { + int numNotifications = mNotificationList.size(); + for (int i = 0; i < numNotifications; i++) { + NotificationRecord rec = mNotificationList.get(i); + if (rec.getUser().getIdentifier() != userId) { + continue; + } + mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, + rec.getStats()); + } + + } + } + + private void postNotificationsWhenExitLockDownMode(int userId) { + synchronized (mNotificationLock) { + int numNotifications = mNotificationList.size(); + // Set the delay to spread out the burst of notifications. + long delay = 0; + for (int i = 0; i < numNotifications; i++) { + NotificationRecord rec = mNotificationList.get(i); + if (rec.getUser().getIdentifier() != userId) { + continue; + } + mHandler.postDelayed(() -> { + synchronized (mNotificationLock) { + mListeners.notifyPostedLocked(rec, rec); + } + }, delay); + delay += 20; + } + } + } + private void updateNotificationPulse() { synchronized (mNotificationLock) { updateLightsLocked(); @@ -8616,6 +8760,12 @@ protected boolean isCallingUidSystem() { return uid == Process.SYSTEM_UID; } + protected boolean isCallingAppIdSystem() { + final int uid = Binder.getCallingUid(); + final int appid = UserHandle.getAppId(uid); + return appid == Process.SYSTEM_UID; + } + protected boolean isUidSystemOrPhone(int uid) { final int appid = UserHandle.getAppId(uid); return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID @@ -8802,12 +8952,15 @@ private static String callStateToString(int state) { * notifications visible to the given listener. */ @GuardedBy("mNotificationLock") - private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { + NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { final int N = mNotificationList.size(); final ArrayList rankings = new ArrayList<>(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); + if (isInLockDownMode(record.getUser().getIdentifier())) { + continue; + } if (!isVisibleToListener(record.getSbn(), info)) { continue; } @@ -8846,6 +8999,10 @@ private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo inf rankings.toArray(new NotificationListenerService.Ranking[0])); } + boolean isInLockDownMode(int userId) { + return mStrongAuthTracker.isInLockDownMode(userId); + } + boolean hasCompanionDevice(ManagedServiceInfo info) { if (mCompanionManager == null) { mCompanionManager = getCompanionManager(); @@ -8878,7 +9035,8 @@ protected ICompanionDeviceManager getCompanionManager() { ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)); } - private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { + @VisibleForTesting + boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { if (!listener.enabledAndUserMatches(sbn.getUserId())) { return false; } @@ -9426,8 +9584,9 @@ public class NotificationListeners extends ManagedServices { private final ArraySet mLightTrimListeners = new ArraySet<>(); - public NotificationListeners(IPackageManager pm) { - super(getContext(), mNotificationLock, mUserProfiles, pm); + public NotificationListeners(Context context, Object lock, UserProfiles userProfiles, + IPackageManager pm) { + super(context, lock, userProfiles, pm); } @Override @@ -9561,8 +9720,12 @@ public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) { * targetting <= O_MR1 */ @GuardedBy("mNotificationLock") - private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, + void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { + if (isInLockDownMode(r.getUser().getIdentifier())) { + return; + } + try { // Lazily initialized snapshots of the notification. StatusBarNotification sbn = r.getSbn(); @@ -9659,6 +9822,10 @@ private void updateUriPermissionsForActiveNotificationsLocked( @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { + if (isInLockDownMode(r.getUser().getIdentifier())) { + return; + } + final StatusBarNotification sbn = r.getSbn(); // make a copy in case changes are made to the underlying Notification object diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 5f14e5146bd0..282816079a26 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -93,6 +93,8 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -237,6 +239,7 @@ public void readXml(XmlPullParser parser, boolean forRestore, int userId) } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -289,6 +292,14 @@ public void readXml(XmlPullParser parser, boolean forRestore, int userId) String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -788,6 +799,12 @@ public void createNotificationChannelGroup(String pkg, int uid, NotificationChan if (r == null) { throw new IllegalArgumentException("Invalid package"); } + if (fromTargetApp) { + group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } + } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { group.setChannels(oldGroup.getChannels()); diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index da472be81156..a6cae49c4fd6 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -60,6 +60,11 @@ public class SnoozeHelper { public static final String XML_SNOOZED_NOTIFICATION_VERSION = "1"; + 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"; @@ -132,6 +137,15 @@ void cleanupPersistedContext(String key){ } } + protected boolean canSnooze(int numberToSnooze) { + synchronized (mLock) { + if ((mPackages.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) { + return false; + } + } + return true; + } + @NonNull protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { Long time = null; @@ -139,7 +153,7 @@ protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, Stri ArrayMap snoozed = mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg)); if (snoozed != null) { - time = snoozed.get(key); + time = snoozed.get(getTrimmedString(key)); } } if (time == null) { @@ -153,7 +167,7 @@ protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, ArrayMap snoozed = mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg)); if (snoozed != null) { - return snoozed.get(key); + return snoozed.get(getTrimmedString(key)); } } return null; @@ -238,7 +252,8 @@ protected void snooze(NotificationRecord record, long duration) { 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); } } @@ -249,8 +264,10 @@ protected void snooze(NotificationRecord record, String contextId) { 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); @@ -267,6 +284,13 @@ private void snooze(NotificationRecord record) { } } + private String getTrimmedString(String key) { + if (key != null && key.length() > MAX_STRING_LENGTH) { + return key.substring(0, MAX_STRING_LENGTH); + } + return key; + } + private void storeRecordLocked(String pkg, String key, Integer userId, ArrayMap> targets, T object) { @@ -371,12 +395,14 @@ protected void repost(String key, boolean muteOnReturn) { } 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 records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 4931d3f3e95c..c96aac6e9914 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -102,6 +102,7 @@ public class ZenModeHelper { // The amount of time rules instances can exist without their owning app being installed. private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72; + static final int RULE_LIMIT_PER_PACKAGE = 100; private final Context mContext; private final H mHandler; @@ -302,8 +303,9 @@ public AutomaticZenRule getAutomaticZenRule(String id) { return null; } - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) { - if (!isSystemRule(automaticZenRule)) { + public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, + String reason) { + if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { component = getActivityInfo(automaticZenRule.getConfigurationActivity()); @@ -319,10 +321,11 @@ public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reas int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner()) + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity()) + 1; - if (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount) { + int newPackageRuleCount = getPackageRuleCount(pkg) + 1; + if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE + || (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) { throw new IllegalArgumentException("Rule instance limit exceeded"); } - } ZenModeConfig newConfig; @@ -335,7 +338,7 @@ public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reas } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(automaticZenRule, rule, true); + populateZenRule(pkg, automaticZenRule, rule, true); newConfig.automaticRules.put(rule.id, rule); if (setConfigLocked(newConfig, reason, rule.component, true)) { return rule.id; @@ -371,7 +374,7 @@ public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZ ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED); } - populateZenRule(automaticZenRule, rule, false); + populateZenRule(rule.pkg, automaticZenRule, rule, false); return setConfigLocked(newConfig, reason, rule.component, true); } } @@ -403,7 +406,14 @@ public boolean removeAutomaticZenRules(String packageName, String reason) { newConfig = mConfig.copy(); for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i)); - if (rule.pkg.equals(packageName) && canManageAutomaticZenRule(rule)) { + String pkg = rule.pkg != null + ? rule.pkg + : (rule.component != null) + ? rule.component.getPackageName() + : (rule.configurationActivity != null) + ? rule.configurationActivity.getPackageName() + : null; + if (Objects.equals(pkg, packageName) && canManageAutomaticZenRule(rule)) { newConfig.automaticRules.removeAt(i); } } @@ -486,6 +496,23 @@ public int getCurrentInstanceCount(ComponentName cn) { return count; } + // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific + // package rather than a condition provider service or activity. + private int getPackageRuleCount(String pkg) { + if (pkg == null) { + return 0; + } + int count = 0; + synchronized (mConfig) { + for (ZenRule rule : mConfig.automaticRules.values()) { + if (pkg.equals(rule.pkg)) { + count++; + } + } + } + return count; + } + public boolean canManageAutomaticZenRule(ZenRule rule) { final int callingUid = Binder.getCallingUid(); if (callingUid == 0 || callingUid == Process.SYSTEM_UID) { @@ -527,11 +554,6 @@ protected void updateDefaultZenRules() { } } - private boolean isSystemRule(AutomaticZenRule rule) { - return rule.getOwner() != null - && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName()); - } - private ServiceInfo getServiceInfo(ComponentName owner) { Intent queryIntent = new Intent(); queryIntent.setComponent(owner); @@ -567,15 +589,14 @@ private ActivityInfo getActivityInfo(ComponentName configActivity) { return null; } - private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) { + private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + boolean isNew) { if (isNew) { rule.id = ZenModeConfig.newRuleId(); rule.creationTime = System.currentTimeMillis(); rule.component = automaticZenRule.getOwner(); rule.configurationActivity = automaticZenRule.getConfigurationActivity(); - rule.pkg = (rule.component != null) - ? rule.component.getPackageName() - : rule.configurationActivity.getPackageName(); + rule.pkg = pkg; } if (rule.enabled != automaticZenRule.isEnabled()) { @@ -592,10 +613,13 @@ private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, bo } protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) { - return new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled, rule.creationTime); + azr.setPackageName(rule.pkg); + return azr; } public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) { diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 3e9e45e59b95..83355e704e6e 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -482,16 +482,16 @@ private void setInteractAcrossProfilesAppOpForProfileOrThrow( // this particular app-op to be modified without the broader app-op permissions. mInjector.withCleanCallingIdentity(() -> mInjector.getAppOpsManager() - .setMode(OP_INTERACT_ACROSS_PROFILES, uid, packageName, newMode)); + .setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, newMode)); } else { mInjector.getAppOpsManager() - .setMode(OP_INTERACT_ACROSS_PROFILES, uid, packageName, newMode); + .setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, newMode); } // Kill the UID before sending the broadcast to ensure that apps can be informed when // their app-op has been revoked. maybeKillUid(packageName, uid, hadPermission); - sendCanInteractAcrossProfilesChangedBroadcast(packageName, uid, UserHandle.of(profileId)); - maybeLogSetInteractAcrossProfilesAppOp(packageName, newMode, logMetrics, uid); + sendCanInteractAcrossProfilesChangedBroadcast(packageName, UserHandle.of(profileId)); + maybeLogSetInteractAcrossProfilesAppOp(packageName, newMode, profileId, logMetrics); } /** @@ -509,7 +509,7 @@ private void maybeKillUid(String packageName, int uid, boolean hadPermission) { } private void maybeLogSetInteractAcrossProfilesAppOp( - String packageName, @Mode int newMode, boolean logMetrics, int uid) { + String packageName, @Mode int newMode, @UserIdInt int userId, boolean logMetrics) { if (!logMetrics) { return; } @@ -517,7 +517,7 @@ private void maybeLogSetInteractAcrossProfilesAppOp( .createEvent(DevicePolicyEnums.SET_INTERACT_ACROSS_PROFILES_APP_OP) .setStrings(packageName) .setInt(newMode) - .setBoolean(appDeclaresCrossProfileAttribute(uid)) + .setBoolean(appDeclaresCrossProfileAttribute(packageName)) .write(); } @@ -533,10 +533,10 @@ private boolean currentModeEquals(@Mode int otherMode, String packageName, int u } private void sendCanInteractAcrossProfilesChangedBroadcast( - String packageName, int uid, UserHandle userHandle) { + String packageName, UserHandle userHandle) { final Intent intent = new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED).setPackage(packageName); - if (appDeclaresCrossProfileAttribute(uid)) { + if (appDeclaresCrossProfileAttribute(packageName)) { intent.addFlags( Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND); } else { @@ -553,8 +553,8 @@ private List findBroadcastReceiversForUser(Intent intent, UserHandl .queryBroadcastReceiversAsUser(intent, /* flags= */ 0, userHandle); } - private boolean appDeclaresCrossProfileAttribute(int uid) { - return mInjector.getPackageManagerInternal().getPackage(uid).isCrossProfile(); + private boolean appDeclaresCrossProfileAttribute(String packageName) { + return mInjector.getPackageManagerInternal().getPackage(packageName).isCrossProfile(); } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f8115d39b375..1cd0e8e86fe0 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; @@ -134,6 +135,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; + /** Destroy sessions older than this on storage free request */ + private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS; + /** * Allow verification-skipping if it's a development app installed through ADB with * disable verification flag specified. @@ -295,22 +299,28 @@ && getSession(session.getParentSessionId()) == null) { @GuardedBy("mSessions") private void reconcileStagesLocked(String volumeUuid) { - final File stagingDir = getTmpSessionDir(volumeUuid); - final ArraySet unclaimedStages = newArraySet( - stagingDir.listFiles(sStageFilter)); - - // We also need to clean up orphaned staging directory for staged sessions - final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); - unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles())); - + final ArraySet unclaimedStages = getStagingDirsOnVolume(volumeUuid); // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); unclaimedStages.remove(session.stageDir); } + removeStagingDirs(unclaimedStages); + } + private ArraySet getStagingDirsOnVolume(String volumeUuid) { + final File stagingDir = getTmpSessionDir(volumeUuid); + final ArraySet stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter)); + + // We also need to clean up orphaned staging directory for staged sessions + final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); + stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles())); + return stagingDirs; + } + + private void removeStagingDirs(ArraySet stagingDirsToRemove) { // Clean up orphaned staging directories - for (File stage : unclaimedStages) { + for (File stage : stagingDirsToRemove) { Slog.w(TAG, "Deleting orphan stage " + stage); synchronized (mPm.mInstallLock) { mPm.removeCodePathLI(stage); @@ -318,6 +328,41 @@ private void reconcileStagesLocked(String volumeUuid) { } } + /** + * Called to free up some storage space from obsolete installation files + */ + public void freeStageDirs(String volumeUuid) { + final ArraySet unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid); + final long currentTimeMillis = System.currentTimeMillis(); + synchronized (mSessions) { + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) { + // Only handles sessions stored on the target volume + continue; + } + final long age = currentTimeMillis - session.createdMillis; + if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { + // Aggressively close old sessions because we are running low on storage + // Their staging dirs will be removed too + PackageInstallerSession root = !session.hasParentSessionId() + ? session : mSessions.get(session.getParentSessionId()); + if (root == null) { + Slog.e(TAG, "freeStageDirs: found an orphaned session: " + + session.sessionId + " parent=" + session.getParentSessionId()); + } else if (!root.isDestroyed()) { + root.abandon(); + } + } else { + // Session is new enough, so it deserves to be kept even on low storage + unclaimedStagingDirsOnVolume.remove(session.stageDir); + } + } + } + removeStagingDirs(unclaimedStagingDirsOnVolume); + } + + public void onPrivateVolumeMounted(String volumeUuid) { synchronized (mSessions) { reconcileStagesLocked(volumeUuid); @@ -1104,7 +1149,10 @@ public void onUserActionRequired(Intent intent) { PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } @@ -1129,7 +1177,10 @@ public void onPackageDeleted(String basePackageName, int returnCode, String msg) PackageManager.deleteStatusToString(returnCode, msg)); fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d690ae9e70f1..94a13f9e7340 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -51,6 +51,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyEventLogger; @@ -119,6 +120,7 @@ import android.system.StructStat; import android.text.TextUtils; import android.util.ArraySet; +import android.util.EventLog; import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; @@ -1299,13 +1301,21 @@ public void statusUpdate(Intent intent) { try { intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, PackageInstallerSession.this.sessionId); - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) { try { - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } else { // failure, let's forward and clean up this session. @@ -1313,7 +1323,11 @@ public void statusUpdate(Intent intent) { PackageInstallerSession.this.sessionId); mChildSessionsRemaining.clear(); // we're done. Don't send any more. try { - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } @@ -2223,6 +2237,11 @@ private void validateApkInstallLocked() throws PackageManagerException { resolveInheritedFile(baseDexMetadataFile); } baseApk = existingBase; + } else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { + EventLog.writeEvent(0x534e4554, "219044664"); + + // Installing base.apk. Make sure the app is restarted. + params.setDontKillApp(false); } // Inherit splits if not overridden @@ -2638,14 +2657,8 @@ public void abandon() { List childSessions = getChildSessionsNotLocked(); synchronized (mLock) { - if (params.isStaged && mDestroyed) { - // If a user abandons staged session in an unsafe state, then system will try to - // abandon the destroyed staged session when it is safe on behalf of the user. - assertCallerIsOwnerOrRootOrSystemLocked(); - } else { - assertCallerIsOwnerOrRootLocked(); - } + assertCallerIsOwnerOrRootOrSystemLocked(); if (isStagedAndInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be // removed by PackageInstallerService when the last update time is old enough. @@ -2682,6 +2695,11 @@ public boolean isStaged() { return params.isStaged; } + @Override + public int getInstallFlags() { + return params.installFlags; + } + @Override public DataLoaderParamsParcel getDataLoaderParams() { mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); @@ -3255,7 +3273,10 @@ private static void sendOnUserActionRequired(Context context, IntentSender targe fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -3295,7 +3316,10 @@ private static void sendOnPackageInstalled(Context context, IntentSender target, } } try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -3321,7 +3345,10 @@ private void sendPendingStreaming(@Nullable String cause) { intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready"); } try { - statusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + statusReceiver.sendIntent(mContext, 0, intent, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0a5f961559a8..ca5183567ae7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5127,7 +5127,10 @@ public void freeStorage(final String volumeUuid, final long freeStorageSize, } if (pi != null) { try { - pi.sendIntent(null, success ? 1 : 0, null, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + pi.sendIntent(null, success ? 1 : 0, null /* intent */, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, e); } @@ -5210,6 +5213,10 @@ public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { return; } + + // 12. Clear temp install session files + mInstallerService.freeStageDirs(volumeUuid); + } else { try { mInstaller.freeCache(volumeUuid, bytes, 0, 0); @@ -12601,9 +12608,7 @@ private void commitPackageSettings(AndroidPackage pkg, AsyncTask.execute(() -> { if (hasOldPkg) { - mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, - allPackageNames); - mPermissionManager.revokeStoragePermissionsIfScopeExpanded(pkg, oldPkg); + mPermissionManager.onPackageUpdated(pkg, oldPkg, allPackageNames); } if (hasPermissionDefinitionChanges) { mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged( @@ -13418,7 +13423,10 @@ static void onRestoreComplete(int returnCode, Context context, IntentSender targ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageManager.installStatusToPublicStatus(returnCode)); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } @@ -15069,10 +15077,10 @@ private int installLocationPolicy(PackageInfoLite pkgLite) { // will be null whereas dataOwnerPkg will contain information about the package // which was uninstalled while keeping its data. AndroidPackage dataOwnerPkg = installedPkg; + PackageSetting dataOwnerPs = mSettings.mPackages.get(packageName); if (dataOwnerPkg == null) { - PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps != null) { - dataOwnerPkg = ps.pkg; + if (dataOwnerPs != null) { + dataOwnerPkg = dataOwnerPs.pkg; } } @@ -15096,12 +15104,36 @@ private int installLocationPolicy(PackageInfoLite pkgLite) { if (dataOwnerPkg != null) { if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, dataOwnerPkg.isDebuggable())) { + // Downgrade is not permitted; a lower version of the app will not be + // allowed try { checkDowngrade(dataOwnerPkg, pkgLite); } catch (PackageManagerException e) { Slog.w(TAG, "Downgrade detected: " + e.getMessage()); return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; } + } else if (dataOwnerPs.isSystem()) { + // Downgrade is permitted, but system apps can't be downgraded below + // the version preloaded onto the system image + final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr( + dataOwnerPs); + if (disabledPs != null) { + dataOwnerPkg = disabledPs.pkg; + } + if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) { + // Only restrict non-debuggable builds and non-debuggable version of + // the app + try { + checkDowngrade(dataOwnerPkg, pkgLite); + } catch (PackageManagerException e) { + String errorMsg = "System app: " + packageName + + " cannot be downgraded to" + + " older than its preloaded version on the system image. " + + e.getMessage(); + Slog.w(TAG, errorMsg); + return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; + } + } } } @@ -18288,6 +18320,20 @@ private void deletePackageVersionedInternal(VersionedPackage versionedPackage, final String packageName = versionedPackage.getPackageName(); final long versionCode = versionedPackage.getLongVersionCode(); + + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + mHandler.post(() -> { + try { + Slog.w(TAG, "Attempted to delete protected package: " + packageName); + observer.onPackageDeleted(packageName, + PackageManager.DELETE_FAILED_INTERNAL_ERROR, null); + } catch (RemoteException re) { + } + }); + return; + } + + final String internalPackageName; try { @@ -18649,6 +18695,17 @@ int deletePackageX(String packageName, long versionCode, int userId, int deleteF return PackageManager.DELETE_FAILED_INTERNAL_ERROR; } + if (isSystemApp(uninstalledPs) + && (deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0) { + UserInfo userInfo = mUserManager.getUserInfo(userId); + if (userInfo == null || !userInfo.isAdmin()) { + Slog.w(TAG, "Not removing package " + packageName + + " as only admin user may downgrade system apps"); + EventLog.writeEvent(0x534e4554, "170646036", -1, packageName); + return PackageManager.DELETE_FAILED_USER_RESTRICTED; + } + } + disabledSystemPs = mSettings.getDisabledSystemPkgLPr(packageName); // Save the enabled state before we delete the package. When deleting a stub // application we always set the enabled state to 'disabled'. @@ -21226,6 +21283,9 @@ && checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) } else { Slog.w(TAG, "Failed setComponentEnabledSetting: component class " + className + " does not exist in " + packageName); + // Safetynet logging for b/240936919 + EventLog.writeEvent(0x534e4554, "240936919", callingUid); + return; } } switch (newState) { @@ -25281,6 +25341,11 @@ public void unsuspendForSuspendingPackage(final String packageName, int affected public boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { return PackageManagerService.this.isSuspendingAnyPackages(suspendingPackage, userId); } + + @Override + public int getInstalledSdkVersion(AndroidPackage pkg) { + return PackageManagerService.this.getSettingsVersionForPackage(pkg).sdkVersion; + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 432d7f335ebc..d3f557d18178 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -242,11 +242,20 @@ public boolean isMatch(int flags) { } public boolean setMimeGroup(String mimeGroup, List mimeTypes) { + for (String mimeType : mimeTypes) { + if (mimeType.length() > 255) { + throw new IllegalArgumentException("MIME type length exceeds 255 characters"); + } + } ArraySet oldMimeTypes = getMimeGroupInternal(mimeGroup); if (oldMimeTypes == null) { throw new IllegalArgumentException("Unknown MIME group " + mimeGroup + " for package " + name); } + if (mimeTypes.size() > 500) { + throw new IllegalStateException("Max limit on MIME types for MIME group " + + mimeGroup + " exceeded for package " + name); + } ArraySet newMimeTypes = new ArraySet<>(mimeTypes); boolean hasChanges = !newMimeTypes.equals(oldMimeTypes); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 9e27f65105eb..4bc3cdb730a3 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -68,6 +68,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Package information used by {@link ShortcutService}. @@ -304,6 +305,7 @@ private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) { // Extract Icon and update the icon res ID and the bitmap path. s.saveIconAndFixUpShortcutLocked(newShortcut); s.fixUpShortcutResourceNamesAndValues(newShortcut); + ensureShortcutCountBeforePush(); mShortcuts.put(newShortcut.getId(), newShortcut); } @@ -357,7 +359,7 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); boolean deleted = false; - if (oldShortcut == null) { + if (oldShortcut == null || !oldShortcut.isDynamic()) { final ShortcutService service = mShortcutUser.mService; final int maxShortcuts = service.getMaxActivityShortcuts(); @@ -367,7 +369,6 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) { // Max has reached. Delete the shortcut with lowest rank. - // Sort by isManifestShortcut() and getRank(). Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator); @@ -382,7 +383,8 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, 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); @@ -396,6 +398,30 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, 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(); + final List 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. * @@ -1222,6 +1248,53 @@ private boolean pushOutExcessShortcuts() { return Integer.compare(a.getRank(), b.getRank()); }; + /** + * 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 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 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. @@ -1798,11 +1871,15 @@ public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortc continue; case TAG_SHORTCUT: - final ShortcutInfo si = parseShortcut(parser, packageName, - shortcutUser.getUserId(), fromBackup); - - // Don't use addShortcut(), we don't need to save the icon. - ret.mShortcuts.put(si.getId(), si); + try { + final ShortcutInfo si = parseShortcut(parser, packageName, + shortcutUser.getUserId(), fromBackup); + // Don't use addShortcut(), we don't need to save the icon. + ret.mShortcuts.put(si.getId(), si); + } catch (Exception e) { + // b/246540168 malformed shortcuts should be ignored + Slog.e(TAG, "Failed parsing shortcut.", e); + } continue; case TAG_SHARE_TARGET: ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser)); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 9903c2d993cc..2b00ab5d6669 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -30,6 +30,7 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -171,6 +172,9 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting 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; @@ -245,6 +249,11 @@ interface ConfigConstants { */ 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. */ @@ -302,10 +311,15 @@ public boolean test(PackageInfo pi) { 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. */ @@ -732,6 +746,9 @@ boolean updateConfigurationLocked(String config) { 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, @@ -1625,6 +1642,10 @@ private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); throw new SecurityException("Shortcut package name mismatch"); } + final int callingUid = injectBinderCallingUid(); + if (UserHandle.getUserId(callingUid) != si.getUserId()) { + throw new SecurityException("User-ID in shortcut doesn't match the caller"); + } } private void verifyShortcutInfoPackages( @@ -1661,6 +1682,13 @@ int getMaxActivityShortcuts() { return mMaxShortcuts; } + /** + * 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 @@ -1781,11 +1809,32 @@ private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean f } if (shortcut.getIcon() != null) { ShortcutInfo.validateIcon(shortcut.getIcon()); + validateIconURI(shortcut); } shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); } + // Validates the calling process has permission to access shortcut icon's image uri + private void validateIconURI(@NonNull final ShortcutInfo si) { + final int callingUid = injectBinderCallingUid(); + final Icon icon = si.getIcon(); + if (icon == null) { + // There's no icon in this shortcut, nothing to validate here. + return; + } + int iconType = icon.getType(); + if (iconType != Icon.TYPE_URI && iconType != Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // The icon is not URI-based, nothing to validate. + return; + } + final Uri uri = icon.getUri(); + mUriGrantsManagerInternal.checkGrantUriPermission(callingUid, si.getPackage(), + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(callingUid))); + } + private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); } diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 045a295da965..95482d7c7f1a 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -22,6 +22,7 @@ import android.content.pm.UserInfo; import android.os.Environment; import android.os.FileUtils; +import android.os.RecoverySystem; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.SystemProperties; @@ -115,6 +116,16 @@ private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, in // Try one last time; if we fail again we're really in trouble prepareUserDataLI(volumeUuid, userId, userSerial, flags | StorageManager.FLAG_STORAGE_DE, false); + } else { + try { + Log.wtf(TAG, "prepareUserData failed for user " + userId, e); + if (userId == UserHandle.USER_SYSTEM) { + RecoverySystem.rebootPromptAndWipeUserData(mContext, + "prepareUserData failed for system user"); + } + } catch (IOException e2) { + throw new RuntimeException("error rebooting into recovery", e2); + } } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a768e8e02911..7307cf24a044 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -88,6 +88,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.EventLog; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -198,6 +199,8 @@ public class UserManagerService extends IUserManager.Stub { private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions"; private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL = "lastRequestQuietModeEnabledCall"; + private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS = + "ignorePrepareStorageErrors"; private static final String ATTR_KEY = "key"; private static final String ATTR_VALUE_TYPE = "type"; private static final String ATTR_MULTIPLE = "m"; @@ -298,6 +301,14 @@ static class UserData { private long mLastRequestQuietModeEnabledMillis; + /** + * {@code true} if the system should ignore errors when preparing the + * storage directories for this user. This is {@code false} for all new + * users; it will only be {@code true} for users that already existed + * on-disk from an older version of Android. + */ + private boolean mIgnorePrepareStorageErrors; + void setLastRequestQuietModeEnabledMillis(long millis) { mLastRequestQuietModeEnabledMillis = millis; } @@ -306,6 +317,14 @@ long getLastRequestQuietModeEnabledMillis() { return mLastRequestQuietModeEnabledMillis; } + boolean getIgnorePrepareStorageErrors() { + return mIgnorePrepareStorageErrors; + } + + void setIgnorePrepareStorageErrors() { + mIgnorePrepareStorageErrors = true; + } + void clearSeedAccountData() { seedAccountName = null; seedAccountType = null; @@ -2955,6 +2974,10 @@ void writeUserLP(UserData userData, OutputStream os) serializer.endTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL); } + serializer.startTag(/* namespace */ null, TAG_IGNORE_PREPARE_STORAGE_ERRORS); + serializer.text(String.valueOf(userData.getIgnorePrepareStorageErrors())); + serializer.endTag(/* namespace */ null, TAG_IGNORE_PREPARE_STORAGE_ERRORS); + serializer.endTag(null, TAG_USER); serializer.endDocument(); @@ -3067,6 +3090,7 @@ UserData readUserLP(int id, InputStream is) throws IOException, Bundle legacyLocalRestrictions = null; RestrictionsSet localRestrictions = null; Bundle globalRestrictions = null; + boolean ignorePrepareStorageErrors = true; // default is true for old users XmlPullParser parser = Xml.newPullParser(); parser.setInput(is, StandardCharsets.UTF_8.name()); @@ -3158,6 +3182,11 @@ UserData readUserLP(int id, InputStream is) throws IOException, if (type == XmlPullParser.TEXT) { lastRequestQuietModeEnabledTimestamp = Long.parseLong(parser.getText()); } + } else if (TAG_IGNORE_PREPARE_STORAGE_ERRORS.equals(tag)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + ignorePrepareStorageErrors = Boolean.parseBoolean(parser.getText()); + } } } } @@ -3185,6 +3214,9 @@ UserData readUserLP(int id, InputStream is) throws IOException, userData.persistSeedData = persistSeedData; userData.seedAccountOptions = seedAccountOptions; userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp); + if (ignorePrepareStorageErrors) { + userData.setIgnorePrepareStorageErrors(); + } synchronized (mRestrictionsLock) { if (baseRestrictions != null) { @@ -4092,6 +4124,13 @@ public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt i public void setApplicationRestrictions(String packageName, Bundle restrictions, @UserIdInt int userId) { checkSystemOrRoot("set application restrictions"); + String validationResult = validateName(packageName); + if (validationResult != null) { + if (packageName.contains("../")) { + EventLog.writeEvent(0x534e4554, "239701237", -1, ""); + } + throw new IllegalArgumentException("Invalid package name: " + validationResult); + } if (restrictions != null) { restrictions.setDefusable(true); } @@ -4118,6 +4157,39 @@ public void setApplicationRestrictions(String packageName, Bundle restrictions, mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId)); } + /** + * Check if the given name is valid. + * + * Note: the logic is taken from FrameworkParsingPackageUtils in master, edited to remove + * unnecessary parts. Copied here for a security fix. + * + * @param name The name to check. + * @return null if it's valid, error message if not + */ + @VisibleForTesting + static String validateName(String name) { + final int n = name.length(); + boolean front = true; + for (int i = 0; i < n; i++) { + final char c = name.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + front = false; + continue; + } + if (!front) { + if ((c >= '0' && c <= '9') || c == '_') { + continue; + } + if (c == '.') { + front = true; + continue; + } + } + return "bad character '" + c + "'"; + } + return null; + } + private int getUidForPackage(String packageName) { long ident = Binder.clearCallingIdentity(); try { @@ -4829,6 +4901,9 @@ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(); } } + + pw.println(" Ignore errors preparing storage: " + + userData.getIgnorePrepareStorageErrors()); } } pw.println(); @@ -5254,6 +5329,14 @@ public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) return allInfos; } } + + @Override + public boolean shouldIgnorePrepareStorageErrors(int userId) { + synchronized (mUsersLock) { + UserData userData = mUsers.get(userId); + return userData != null && userData.getIgnorePrepareStorageErrors(); + } + } } /** diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 5e04171a3bca..354f5b9a8bdf 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -409,6 +409,8 @@ static BasePermission createOrUpdate(PackageManagerInternal packageManagerIntern } if (bp.perm != null && Objects.equals(bp.perm.getPackageName(), p.getPackageName()) && Objects.equals(bp.perm.getName(), p.getName())) { + bp.perm.setFlags(bp.perm.getFlags() & ~PermissionInfo.FLAG_INSTALLED); + bp.perm = p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED); bp.protectionLevel = p.getProtectionLevel(); } if (bp.isRuntime() && (ownerChanged || wasNonRuntime)) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 8d2363b6e831..f83c3d5145cc 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -610,8 +610,8 @@ public boolean addPermission(PermissionInfo info, boolean async) { BasePermission bp = mSettings.getPermissionLocked(info.name); added = bp == null; int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); + enforcePermissionCapLocked(info, tree); if (added) { - enforcePermissionCapLocked(info, tree); bp = new BasePermission(info.name, tree.getSourcePackageName(), BasePermission.TYPE_DYNAMIC); } else if (!bp.isDynamic()) { @@ -2321,6 +2321,46 @@ private void revokeStoragePermissionsIfScopeExpanded( } + /** + * If the package was below api 23, got the SYSTEM_ALERT_WINDOW permission automatically, and + * then updated past api 23, and the app does not satisfy any of the other SAW permission flags, + * the permission should be revoked. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + */ + private void revokeSystemAlertWindowIfUpgradedPast23( + @NonNull AndroidPackage newPackage, + @NonNull AndroidPackage oldPackage, + @NonNull PermissionCallback permissionCallback) { + if (oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.M + || newPackage.getTargetSdkVersion() < Build.VERSION_CODES.M + || !newPackage.getRequestedPermissions() + .contains(Manifest.permission.SYSTEM_ALERT_WINDOW)) { + return; + } + + BasePermission saw; + synchronized (mLock) { + saw = mSettings.getPermissionLocked(Manifest.permission.SYSTEM_ALERT_WINDOW); + } + final PackageSetting ps = (PackageSetting) + mPackageManagerInt.getPackageSetting(newPackage.getPackageName()); + if (grantSignaturePermission(Manifest.permission.SYSTEM_ALERT_WINDOW, newPackage, ps, saw, + ps.getPermissionsState(), true)) { + return; + } + for (int userId : mUserManagerInt.getUserIds()) { + try { + revokePermissionFromPackageForUser(newPackage.getPackageName(), + Manifest.permission.SYSTEM_ALERT_WINDOW, false, userId, permissionCallback); + } catch (IllegalStateException | SecurityException e) { + Log.e(TAG, "unable to revoke SYSTEM_ALERT_WINDOW for " + + newPackage.getPackageName() + " user " + userId, e); + } + } + } + /** * We might auto-grant permissions if any permission of the group is already granted. Hence if * the group of a granted permission changes we need to revoke it to avoid having permissions of @@ -2755,7 +2795,7 @@ private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace // uids the original and new state are the same object if (!origPermissions.hasRequestedPermission(permName) && (pkg.getImplicitPermissions().contains(permName) - || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { + || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { if (pkg.getImplicitPermissions().contains(permName)) { // If permName is an implicit permission, try to auto-grant newImplicitPermissions.add(permName); @@ -2771,21 +2811,27 @@ private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace // or has updated its target SDK and AR is no longer implicit to it. // This is a compatibility workaround for apps when AR permission was // split in Q. - final List permissionList = - getSplitPermissions(); - int numSplitPerms = permissionList.size(); - for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum); - String splitPermName = sp.getSplitPermission(); - if (sp.getNewPermissions().contains(permName) - && origPermissions.hasInstallPermission(splitPermName)) { - upgradedActivityRecognitionPermission = splitPermName; - newImplicitPermissions.add(permName); + // b/210065877: Check that the installed version is pre Q to auto-grant in + // case of OS update + if (mPackageManagerInt.getInstalledSdkVersion(pkg) + < Build.VERSION_CODES.Q) { + final List permissionList = + getSplitPermissions(); + int numSplitPerms = permissionList.size(); + for (int splitPermNum = 0; splitPermNum < numSplitPerms; + splitPermNum++) { + SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum); + String splitPermName = sp.getSplitPermission(); + if (sp.getNewPermissions().contains(permName) + && origPermissions.hasInstallPermission(splitPermName)) { + upgradedActivityRecognitionPermission = splitPermName; + newImplicitPermissions.add(permName); - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, permName + " is newly added for " + friendlyName); + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, permName + " is newly added for " + friendlyName); + } + break; } - break; } } } @@ -3550,6 +3596,13 @@ private boolean hasPrivappWhitelistEntry(String perm, AndroidPackage pkg) { private boolean grantSignaturePermission(String perm, AndroidPackage pkg, PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) { + return grantSignaturePermission(perm, pkg, pkgSetting, bp, origPermissions, false); + } + + + private boolean grantSignaturePermission(String perm, AndroidPackage pkg, + PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions, + boolean isApi23Upgrade) { boolean oemPermission = bp.isOEM(); boolean vendorPrivilegedPermission = bp.isVendorPrivileged(); boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged(); @@ -3724,7 +3777,7 @@ && canGrantOemPermission(disabledPs, perm)))) { // Any pre-installed system app is allowed to get this permission. allowed = true; } - if (!allowed && bp.isDevelopment()) { + if (!allowed && bp.isDevelopment() && !(bp.isPre23() && isApi23Upgrade)) { // For development permissions, a development permission // is granted only if it was already granted. allowed = origPermissions.hasInstallPermission(perm); @@ -4791,24 +4844,20 @@ public boolean isPermissionsReviewRequired(@NonNull AndroidPackage pkg, return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId); } /** - * If the app is updated, and has scoped storage permissions, then it is possible that the - * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. + * If the app is updated, then some checks need to be performed to ensure the + * package is not attempting to expoit permission changes across API boundaries. * @param newPackage The new package that was installed * @param oldPackage The old package that was updated + * @param allPackageNames The current packages in the system */ - public void revokeStoragePermissionsIfScopeExpanded( - @NonNull AndroidPackage newPackage, - @NonNull AndroidPackage oldPackage - ) { - PermissionManagerService.this.revokeStoragePermissionsIfScopeExpanded(newPackage, - oldPackage, mDefaultPermissionCallback); - } - - @Override - public void revokeRuntimePermissionsIfGroupChanged( + public void onPackageUpdated( @NonNull AndroidPackage newPackage, @NonNull AndroidPackage oldPackage, @NonNull ArrayList allPackageNames) { + PermissionManagerService.this.revokeStoragePermissionsIfScopeExpanded(newPackage, + oldPackage, mDefaultPermissionCallback); + PermissionManagerService.this.revokeSystemAlertWindowIfUpgradedPast23(newPackage, + oldPackage, mDefaultPermissionCallback); PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage, oldPackage, allPackageNames, mDefaultPermissionCallback); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index df0edfa16924..7003c7a2027e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -239,16 +239,14 @@ public abstract void updatePermissions(@NonNull String packageName, public abstract void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId); - /** - * We might auto-grant permissions if any permission of the group is already granted. Hence if - * the group of a granted permission changes we need to revoke it to avoid having permissions of - * the new group auto-granted. - * - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - * @param allPackageNames All packages - */ - public abstract void revokeRuntimePermissionsIfGroupChanged( + /** + * If the app is updated, then some checks need to be performed to ensure the package is not + * attempting to expoit permission changes across API boundaries. + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + * @param allPackageNames The current packages in the system + */ + public abstract void onPackageUpdated( @NonNull AndroidPackage newPackage, @NonNull AndroidPackage oldPackage, @NonNull ArrayList allPackageNames); @@ -265,17 +263,6 @@ public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged( @NonNull List permissionsToRevoke, @NonNull ArrayList allPackageNames); - /** - * If the app is updated, and has scoped storage permissions, then it is possible that the - * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - */ - public abstract void revokeStoragePermissionsIfScopeExpanded( - @NonNull AndroidPackage newPackage, - @NonNull AndroidPackage oldPackage - ); - /** * Add all permissions in the given package. *

diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index cadbe51c8043..684e2c4d5243 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -56,7 +56,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static android.view.WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; @@ -796,7 +795,6 @@ default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindo // in a higher layer than TYPE_APPLICATION_OVERLAY. return canAddInternalSystemWindow ? 13 : 10; case TYPE_APPLICATION_OVERLAY: - case TYPE_TRUSTED_APPLICATION_OVERLAY: return 12; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 4e848686254a..cf5c587e0494 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -192,6 +192,12 @@ public void onSystemReady() { @Override // Binder interface public void doKeyguardTimeout(Bundle options) { + int userId = mKeyguardStateMonitor.getCurrentUser(); + if (mKeyguardStateMonitor.isSecure(userId)) { + // Preemptively inform the cache that the keyguard will soon be showing, as calls to + // doKeyguardTimeout are a signal to lock the device as soon as possible. + mKeyguardStateMonitor.onShowingStateChanged(true, userId); + } try { mService.doKeyguardTimeout(options); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index e56a4532ef53..d34863f22ea5 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -92,8 +92,14 @@ public boolean hasLockscreenWallpaper() { return mHasLockscreenWallpaper; } + public int getCurrentUser() { + return mCurrentUserId; + } + @Override // Binder interface - public void onShowingStateChanged(boolean showing) { + public void onShowingStateChanged(boolean showing, int userId) { + if (userId != mCurrentUserId) return; + mIsShowing = showing; mCallback.onShowingChanged(); diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index c49720faf379..94201cf01ec9 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -242,6 +242,8 @@ public int checkSlicePermission(Uri uri, String callingPkg, String pkg, int pid, if (autoGrantPermissions != null && callingPkg != null) { // Need to own the Uri to call in with permissions to grant. enforceOwner(callingPkg, uri, userId); + // b/208232850: Needs to verify caller before granting slice access + verifyCaller(callingPkg); for (String perm : autoGrantPermissions) { if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { int providerUser = ContentProvider.getUserIdFromUri(uri, userId); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 323ac7b8806e..4668741aed0b 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2005,10 +2005,9 @@ private void ensureCaptureTvInputPermission() { public void requestChannelBrowsable(Uri channelUri, int userId) throws RemoteException { final String callingPackageName = getCallingPackageName(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "requestChannelBrowsable"); final long identity = Binder.clearCallingIdentity(); - final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "requestChannelBrowsable"); try { Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED); List list = getContext().getPackageManager() diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index e8a0379473da..0871497e4143 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -45,6 +45,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; @@ -70,6 +71,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -1331,6 +1333,46 @@ private boolean checkUriPermissionLocked(GrantUri grantUri, int uid, final int m return false; } + /** + * Check if the targetPkg can be granted permission to access uri by + * the callingUid using the given modeFlags. See {@link #checkGrantUriPermissionUnlocked}. + * + * @param callingUid The uid of the grantor app that has permissions to the uri. + * @param targetPkg The package name of the granted app that needs permissions to the uri. + * @param uri The uri for which permissions should be granted. + * @param modeFlags The modes to grant. See {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc. + * @param userId The userId in which the uri is to be resolved. + * @return uid of the target or -1 if permission grant not required. Returns -1 if the caller + * does not hold INTERACT_ACROSS_USERS_FULL + * @throws SecurityException if the grant is not allowed. + */ + @Override + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + public int checkGrantUriPermission_ignoreNonSystem(int callingUid, String targetPkg, Uri uri, + int modeFlags, int userId) { + if (!isCallerIsSystemOrPrivileged()) { + return Process.INVALID_UID; + } + final long origId = Binder.clearCallingIdentity(); + try { + return checkGrantUriPermissionUnlocked(callingUid, targetPkg, uri, modeFlags, + userId); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private boolean isCallerIsSystemOrPrivileged() { + final int uid = Binder.getCallingUid(); + if (uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) { + return true; + } + return ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + uid, /* owningUid = */-1, /* exported = */ true) + == PackageManager.PERMISSION_GRANTED; + } + @GuardedBy("mLock") private void writeGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()"); diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 45689ce73c9f..7eeba02542e4 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1045,7 +1045,11 @@ private void grantNotificationListenerAccess(String pkg, int userId) { for (ComponentName c : possibleServices) { if (Objects.equals(c.getPackageName(), pkg)) { - nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + try { + nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + } catch (Exception e) { + Slog.w(TAG, "Could not grant NLS access to package " + pkg, e); + } } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 92dbfa4f1733..9bbb090bc6c9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -673,6 +673,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private AppSaturationInfo mLastAppSaturationInfo; + private final ActivityRecordInputSink mActivityRecordInputSink; + + // Activities with this uid are allowed to not create an input sink while being in the same + // task and directly above this ActivityRecord. This field is updated whenever a new activity + // is launched from this ActivityRecord. Touches are always allowed within the same uid. + int mAllowedTouchUid; + private final ColorDisplayService.ColorTransformController mColorTransformController = (matrix, translation) -> mWmService.mH.post(() -> { synchronized (mWmService.mGlobalLock) { @@ -1193,6 +1200,12 @@ void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) { } 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(); + } } } @@ -1650,6 +1663,8 @@ boolean isResolverOrChildActivity() { ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; mHandoverLaunchDisplayId = options.getLaunchDisplayId(); } + + mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord); } /** @@ -3173,6 +3188,7 @@ private void cleanUpActivityServices() { @Override void removeImmediately() { onRemovedFromDisplay(); + mActivityRecordInputSink.releaseSurfaceControl(); super.removeImmediately(); } @@ -6048,6 +6064,9 @@ void prepareSurfaces() { } else if (!show && mLastSurfaceShowing) { getSyncTransaction().hide(mSurfaceControl); } + if (show) { + mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction()); + } } if (mThumbnail != null) { mThumbnail.setShowing(getPendingTransaction(), show); diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java new file mode 100644 index 000000000000..95a6e8b8b88f --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.Process; +import android.view.InputWindowHandle; +import android.view.SurfaceControl; +import android.view.WindowManager; + +/** + * Creates a InputWindowHandle that catches all touches that would otherwise pass through an + * Activity. + */ +class ActivityRecordInputSink { + + private final ActivityRecord mActivityRecord; + private final String mName; + + private InputWindowHandle mInputWindowHandle; + private SurfaceControl mSurfaceControl; + + ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) { + mActivityRecord = activityRecord; + mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink " + + mActivityRecord.mActivityComponent.flattenToShortString(); + if (sourceRecord != null) { + sourceRecord.mAllowedTouchUid = mActivityRecord.getUid(); + } + } + + public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) { + boolean windowHandleChanged = updateInputWindowHandle(); + if (mSurfaceControl == null) { + mSurfaceControl = createSurface(transaction); + } + if (windowHandleChanged) { + transaction.setInputWindowInfo(mSurfaceControl, mInputWindowHandle); + } + } + + private SurfaceControl createSurface(SurfaceControl.Transaction t) { + SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null) + .setName(mName) + .setHidden(false) + .setCallsite("ActivityRecordInputSink.createSurface") + .build(); + // Put layer below all siblings (and the parent surface too) + t.setLayer(surfaceControl, Integer.MIN_VALUE); + return surfaceControl; + } + + private boolean updateInputWindowHandle() { + boolean changed = false; + if (mInputWindowHandle == null) { + mInputWindowHandle = createInputWindowHandle(); + changed = true; + } + // Don't block touches from passing through to an activity below us in the same task, if + // that activity is either from the same uid or if that activity has launched an activity + // in our uid. + final ActivityRecord activityBelowInTask = + mActivityRecord.getTask().getActivityBelow(mActivityRecord); + final boolean allowPassthrough = activityBelowInTask != null && ( + activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() + || activityBelowInTask.isUid(mActivityRecord.getUid())); + boolean notTouchable = (mInputWindowHandle.layoutParamsFlags + & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0; + if (allowPassthrough || mActivityRecord.isAppTransitioning()) { + mInputWindowHandle.layoutParamsFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + changed |= !notTouchable; + } else { + mInputWindowHandle.layoutParamsFlags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + changed |= notTouchable; + } + if (mInputWindowHandle.displayId != mActivityRecord.getDisplayId()) { + mInputWindowHandle.displayId = mActivityRecord.getDisplayId(); + changed = true; + } + return changed; + } + + private InputWindowHandle createInputWindowHandle() { + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, + mActivityRecord.getDisplayId()); + inputWindowHandle.replaceTouchableRegionWithCrop = true; + inputWindowHandle.name = mName; + inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; + inputWindowHandle.ownerUid = Process.myUid(); + inputWindowHandle.ownerPid = Process.myPid(); + inputWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + inputWindowHandle.inputFeatures = + WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + return inputWindowHandle; + } + + void releaseSurfaceControl() { + if (mSurfaceControl != null) { + mSurfaceControl.release(); + mSurfaceControl = null; + } + } + +} diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 6d452c39eccc..60fdbf9aea1b 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -715,7 +715,7 @@ && isActivityTypeStandardOrUndefined()) { : WINDOWING_MODE_FULLSCREEN; } if (currentMode == WINDOWING_MODE_PINNED) { - mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); + mRootWindowContainer.notifyActivityPipModeChanged(this, null); } if (likelyResolvedMode == WINDOWING_MODE_PINNED && taskDisplayArea.getRootPinnedTask() != null) { @@ -2404,7 +2404,23 @@ boolean navigateUpTo(ActivityRecord srec, Intent destIntent, NeededUriGrants des parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - parent.deliverNewIntentLocked(callingUid, destIntent, destGrants, srec.packageName); + boolean abort; + try { + abort = !mStackSupervisor.checkStartAnyActivityPermission(destIntent, + parent.info, null /* resultWho */, -1 /* requestCode */, srec.getPid(), + callingUid, srec.info.packageName, null /* callingFeatureId */, + false /* ignoreTargetSecurity */, false /* launchingInTask */, srec.app, + null /* resultRecord */, null /* resultRootTask */); + } catch (SecurityException e) { + abort = true; + } + if (abort) { + android.util.EventLog.writeEvent(0x534e4554, "238605611", callingUid, ""); + foundParentInTask = false; + } else { + parent.deliverNewIntentLocked(callingUid, destIntent, destGrants, + srec.packageName); + } } else { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 54ad4acaca4c..5d4549631572 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1004,6 +1004,10 @@ private int executeRequest(Request request) { abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage); + // Merge the two options bundles, while realCallerOptions takes precedence. + ActivityOptions checkedOptions = options != null + ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; + boolean restrictedBgActivity = false; if (!abort) { try { @@ -1012,15 +1016,12 @@ private int executeRequest(Request request) { restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, request.originatingPendingIntent, request.allowBackgroundActivityStart, - intent); + intent, checkedOptions); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } - // Merge the two options bundles, while realCallerOptions takes precedence. - ActivityOptions checkedOptions = options != null - ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; if (request.allowPendingRemoteAnimationRegistryLookup) { checkedOptions = mService.getActivityStartController() .getPendingRemoteAnimationRegistry() @@ -1234,7 +1235,7 @@ private void onExecutionComplete() { boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart, Intent intent) { + boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) { // don't abort for the most important UIDs final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID @@ -1278,7 +1279,11 @@ boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, ? isCallingUidPersistentSystemProcess : (realCallingAppId == Process.SYSTEM_UID) || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; - if (realCallingUid != callingUid) { + // Legacy behavior allows to use caller foreground state to bypass BAL restriction. + final boolean balAllowedByPiSender = + PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions); + + if (balAllowedByPiSender && realCallingUid != callingUid) { // don't abort if the realCallingUid has a visible window if (realCallingUidHasAnyVisibleWindow) { if (DEBUG_ACTIVITY_STARTS) { @@ -1346,9 +1351,10 @@ boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, // If we don't have callerApp at this point, no caller was provided to startActivity(). // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. If that's the case, we retrieve the WindowProcessController for the send() - // caller, so that we can make the decision based on its foreground/whitelisted state. + // caller if caller allows, so that we can make the decision + // based on its foreground/whitelisted state. int callerAppUid = callingUid; - if (callerApp == null) { + if (callerApp == null && balAllowedByPiSender) { callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerAppUid = realCallingUid; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ef9a6f27fdde..77a12c1604ef 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -18,6 +18,7 @@ import static android.Manifest.permission.BIND_VOICE_INTERACTION; import static android.Manifest.permission.CHANGE_CONFIGURATION; +import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; @@ -2532,7 +2533,7 @@ void moveTaskToFrontLocked(@Nullable IApplicationThread appThread, final ActivityStarter starter = getActivityStartController().obtainStarter( null /* intent */, "moveTaskToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1, - -1, callerApp, null, false, null)) { + -1, callerApp, null, false, null, null)) { if (!isBackgroundActivityStartsEnabled()) { return; } @@ -3963,6 +3964,7 @@ public void showLockTaskEscapeMessage(IBinder token) { @Override public void keyguardGoingAway(int flags) { + mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, "unlock keyguard"); enforceNotIsolatedCaller("keyguardGoingAway"); final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index dd1d55b2d54d..edd086df78aa 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -84,7 +84,7 @@ public ActivityManager.RecentTaskInfo getTaskInfo() { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } return mService.getRecentTasks().createRecentTaskInfo(task, - false /* stripExtras */); + false /* stripExtras */, true /* getTasksAllowed */); } finally { Binder.restoreCallingIdentity(origId); } @@ -112,7 +112,7 @@ public void moveToFront(IApplicationThread appThread, String callingPackage) { final ActivityStarter starter = mService.getActivityStartController().obtainStarter( null /* intent */, "moveToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, - callingPackage, -1, -1, callerApp, null, false, null)) { + callingPackage, -1, -1, callerApp, null, false, null, null)) { if (!mService.isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 97e4a9595da3..179777a1cf34 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -77,6 +77,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; @@ -99,7 +100,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static android.view.WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; @@ -149,6 +149,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; +import android.gui.DropInputMode; import android.hardware.input.InputManager; import android.hardware.power.V1_0.PowerHint; import android.os.Handler; @@ -991,19 +992,19 @@ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams att } break; } + } - // Check if alternate bars positions were updated. - if (mStatusBarAlt == win) { - mStatusBarAltPosition = getAltBarPosition(attrs); - } - if (mNavigationBarAlt == win) { - mNavigationBarAltPosition = getAltBarPosition(attrs); - } - if (mClimateBarAlt == win) { - mClimateBarAltPosition = getAltBarPosition(attrs); - } - if (mExtraNavBarAlt == win) { - mExtraNavBarAltPosition = getAltBarPosition(attrs); + /** + * Add additional policy if needed to ensure the window or its children should not receive any + * input. + */ + public void setDropInputModePolicy(WindowState win, LayoutParams attrs) { + if (attrs.type == TYPE_TOAST + && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) { + // Toasts should not receive input. These windows should not have any children, so + // force this hierarchy of windows to drop all input. + mService.mTransactionFactory.get() + .setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply(); } } @@ -1038,6 +1039,11 @@ int validateAddingWindowLw(WindowManager.LayoutParams attrs, int callingPid, int android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, "DisplayPolicy"); } + if ((attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) { + mContext.enforcePermission( + android.Manifest.permission.INTERNAL_SYSTEM_WINDOW, callingPid, callingUid, + "DisplayPolicy"); + } switch (attrs.type) { case TYPE_STATUS_BAR: @@ -1083,11 +1089,6 @@ int validateAddingWindowLw(WindowManager.LayoutParams attrs, int callingPid, int android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, "DisplayPolicy"); break; - case TYPE_TRUSTED_APPLICATION_OVERLAY: - mContext.enforcePermission( - android.Manifest.permission.INTERNAL_SYSTEM_WINDOW, callingPid, callingUid, - "DisplayPolicy"); - break; case TYPE_STATUS_BAR_PANEL: return WindowManagerGlobal.ADD_INVALID_TYPE; } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index aa76a07bc9c7..7b7fcc4e8f8b 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -93,7 +93,7 @@ void process(@Nullable ActivityRecord starting, int configChanges, boolean prese // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mContiner.isTopActivityFocusable() + && mContiner.canBeResumed(starting) && (starting == null || !starting.isDescendantOf(mContiner)); final PooledConsumer f = PooledLambda.obtainConsumer( diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index 19185736fc89..6bbaa8f27f2d 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -86,6 +86,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient { mWindowHandle.ownerUid = Process.myUid(); mWindowHandle.inputFeatures = 0; mWindowHandle.scaleFactor = 1.0f; + mWindowHandle.trustedOverlay = true; mInputSurface = mService.makeSurfaceBuilder(mService.mRoot.getDisplayContent(displayId).getSession()) .setContainerLayer() diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8aec9ed08880..80e16edfa2e8 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -27,7 +27,18 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; @@ -66,9 +77,6 @@ final class InputMonitor { private boolean mUpdateInputWindowsPending; private boolean mUpdateInputWindowsImmediately; - // Currently focused input window handle. - private InputWindowHandle mFocusedInputWindowHandle; - private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer; @@ -320,10 +328,6 @@ void populateInputWindowHandle(final InputWindowHandle inputWindowHandle, Slog.d(TAG_WM, "addInputWindowHandle: " + child + ", " + inputWindowHandle); } - - if (hasFocus) { - mFocusedInputWindowHandle = inputWindowHandle; - } } void setUpdateInputWindowsNeededLw() { @@ -580,6 +584,7 @@ private static void populateOverlayInputInfo(final InputWindowHandle inputWindow inputWindowHandle.portalToDisplayId = INVALID_DISPLAY; inputWindowHandle.touchableRegion.setEmpty(); inputWindowHandle.setTouchableRegionCrop(null); + inputWindowHandle.trustedOverlay = isTrustedOverlay(type); } /** @@ -594,4 +599,17 @@ static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transac populateOverlayInputInfo(inputWindowHandle, name, TYPE_SECURE_SYSTEM_OVERLAY, true); t.setInputWindowInfo(sc, inputWindowHandle); } + + static boolean isTrustedOverlay(int type) { + return type == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY + || type == TYPE_INPUT_METHOD || type == TYPE_INPUT_METHOD_DIALOG + || type == TYPE_MAGNIFICATION_OVERLAY || type == TYPE_STATUS_BAR + || type == TYPE_NOTIFICATION_SHADE + || type == TYPE_NAVIGATION_BAR + || type == TYPE_NAVIGATION_BAR_PANEL + || type == TYPE_SECURE_SYSTEM_OVERLAY + || type == TYPE_DOCK_DIVIDER + || type == TYPE_ACCESSIBILITY_OVERLAY + || type == TYPE_INPUT_CONSUMER; + } } diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index a974332fd852..b037e59942cf 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -214,6 +214,9 @@ void saveTask(Task task) { void saveTask(Task task, DisplayContent display) { final ComponentName name = task.realActivity; + if (name == null) { + return; + } final int userId = task.mUserId; PersistableLaunchParams params; ArrayMap map = mLaunchParamsMap.get(userId); diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 3fe75a4ab49e..6151b1892fda 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -962,7 +962,7 @@ private ArrayList getRecentTasksImpl(int maxNum, continue; } - res.add(createRecentTaskInfo(task, true /* stripExtras */)); + res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed)); } return res; } @@ -1834,12 +1834,16 @@ void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { /** * Creates a new RecentTaskInfo from a Task. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras, + boolean getTasksAllowed) { ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); tr.fillTaskInfo(rti, stripExtras); // Fill in some deprecated values rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; + if (!getTasksAllowed) { + Task.trimIneffectiveInfo(tr, rti); + } return rti; } diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 32de699eaae9..bf206a3a6bff 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -148,15 +148,16 @@ private boolean processActivity(ActivityRecord r, boolean isTargetTask) { return false; } else { - mResultActivities.add(r); if (r.resultTo != null) { // If this activity is sending a reply to a previous activity, we can't do // anything with it now until we reach the start of the reply chain. // NOTE: that we are assuming the result is always to the previous activity, // which is almost always the case but we really shouldn't count on. + mResultActivities.add(r); return false; } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null && mTargetTask.affinity.equals(r.taskAffinity)) { + mResultActivities.add(r); // This activity has an affinity for our task. Either remove it if we are // clearing or move it over to our task. Note that we currently punt on the case // where we are resetting a task that is not at the top but who has activities diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index b1d774af7f36..7dcfaf342174 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -571,8 +571,9 @@ private void getWindowsByName(ArrayList output, String name, int ob /** * Returns {@code true} if the callingUid has any non-toast window currently visible to the * user. Also ignores {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_STARTING}, - * since those windows don't belong to apps. - * @see WindowState#isNonToastOrStarting() + * and{@link android.view.WindowManager.LayoutParams#TYPE_PRIVATE_PRESENTATION}, as they + * should not count towards the apps visibility + * @see WindowState#isNonToastOrStartingOrPrivatePresentation() */ boolean isAnyNonToastWindowVisibleForUid(int callingUid) { final PooledPredicate p = PooledLambda.obtainPredicate( @@ -1990,7 +1991,8 @@ private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r, try { if (mStackSupervisor.realStartActivityLocked(r, app, - top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) { + top == r && r.getTask().canBeResumed(r) /*andResume*/, + true /*checkConfig*/)) { mTmpBoolean = true; } } catch (RemoteException e) { @@ -2238,7 +2240,27 @@ void moveActivityToPinnedStack(ActivityRecord r, String reason) { ensureActivitiesVisible(null, 0, false /* preserveWindows */); resumeFocusedStacksTopActivities(); - mService.getTaskChangeNotificationController().notifyActivityPinned(r); + notifyActivityPipModeChanged(r.getTask(), r); + } + + /** + * Notifies when an activity enters or leaves PIP mode. + * + * @param task the task of {@param r} + * @param r indicates the activity currently in PIP, can be null to indicate no activity is + * currently in PIP mode. + */ + void notifyActivityPipModeChanged(@NonNull Task task, @Nullable ActivityRecord r) { + final boolean inPip = r != null; + if (inPip) { + mService.getTaskChangeNotificationController().notifyActivityPinned(r); + } else { + mService.getTaskChangeNotificationController().notifyActivityUnpinned(); + } + mWindowManager.mPolicy.setPipVisibilityLw(inPip); + mWmService.mTransactionFactory.get() + .setTrustedOverlay(task.getSurfaceControl(), inPip) + .apply(); } void executeAppTransitionForAllDisplay() { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 3509ba72d058..3c7917bb05d1 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -129,6 +129,10 @@ private RunningTaskInfo createRunningTaskInfo(Task task) { final RunningTaskInfo rti = task.getTaskInfo(); // Fill in some deprecated values rti.id = rti.taskId; + + if (!mAllowed) { + Task.trimIneffectiveInfo(task, rti); + } return rti; } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index e2258093dcbe..86cbf3e3afe1 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -662,7 +662,7 @@ boolean hasAlertWindowSurfaces(DisplayContent displayContent) { @Override public void grantInputChannel(int displayId, SurfaceControl surface, - IWindow window, IBinder hostInputToken, int flags, int type, + IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, InputChannel outInputChannel) { if (hostInputToken == null && !mCanAddInternalSystemWindow) { // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to @@ -678,7 +678,8 @@ public void grantInputChannel(int displayId, SurfaceControl surface, final long identity = Binder.clearCallingIdentity(); try { mService.grantInputChannel(mUid, mPid, displayId, surface, window, hostInputToken, - flags, mCanAddInternalSystemWindow ? type : 0, outInputChannel); + flags, mCanAddInternalSystemWindow ? privateFlags : 0, + mCanAddInternalSystemWindow ? type : 0, outInputChannel); } finally { Binder.restoreCallingIdentity(identity); } @@ -686,10 +687,11 @@ public void grantInputChannel(int displayId, SurfaceControl surface, @Override public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, Region region) { + int flags, int privateFlags, Region region) { final long identity = Binder.clearCallingIdentity(); try { - mService.updateInputChannel(channelToken, displayId, surface, flags, region); + mService.updateInputChannel(channelToken, displayId, surface, flags, + mCanAddInternalSystemWindow ? privateFlags : 0, region); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b19b8c1ce10b..a72763b97dbd 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -119,6 +119,7 @@ import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -227,6 +228,11 @@ class Task extends WindowContainer { // Do not move the stack as a part of reparenting static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; + /** + * Used to identify if the activity that is installed from device's system image. + */ + boolean mIsEffectivelySystemApp; + String affinity; // The affinity name for this task, or null; may change identity. String rootAffinity; // Initial base affinity, or null; does not change from initial root. String mWindowLayoutAffinity; // Launch param affinity of this task or null. Used when saving @@ -477,11 +483,24 @@ private boolean processActivity(ActivityRecord r, if (r.finishing) return false; - // Set this as the candidate root since it isn't finishing. - mRoot = r; + if (mRoot == null || mRoot.finishing) { + // Set this as the candidate root since it isn't finishing. + mRoot = r; + } + + final int uid = mRoot == r ? effectiveUid : r.info.applicationInfo.uid; + if (ignoreRelinquishIdentity + || (mRoot.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0 + || (mRoot.info.applicationInfo.uid != Process.SYSTEM_UID + && !mRoot.info.applicationInfo.isSystemApp() + && mRoot.info.applicationInfo.uid != uid)) { + // No need to relinquish identity, end search. + return true; + } - // Only end search if we are ignore relinquishing identity or we are not relinquishing. - return ignoreRelinquishIdentity || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; + // Relinquish to next activity + mRoot = r; + return false; } } @@ -929,32 +948,33 @@ void setIntent(ActivityRecord r) { * @param info The activity info which could be different from {@code r.info} if set. */ void setIntent(ActivityRecord r, @Nullable Intent intent, @Nullable ActivityInfo info) { - mCallingUid = r.launchedFromUid; - mCallingPackage = r.launchedFromPackage; - mCallingFeatureId = r.launchedFromFeatureId; - setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); - setLockTaskAuth(r); - - final WindowContainer parent = getParent(); - if (parent != null) { - final Task t = parent.asTask(); - if (t != null) { - t.setIntent(r); - } + boolean updateIdentity = false; + if (this.intent == null) { + updateIdentity = true; + } else if (!mNeverRelinquishIdentity) { + final ActivityInfo activityInfo = info != null ? info : r.info; + updateIdentity = (effectiveUid == Process.SYSTEM_UID || mIsEffectivelySystemApp + || effectiveUid == activityInfo.applicationInfo.uid); + } + if (updateIdentity) { + mCallingUid = r.launchedFromUid; + mCallingPackage = r.launchedFromPackage; + mCallingFeatureId = r.launchedFromFeatureId; + setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); } + setLockTaskAuth(r); } /** Sets the original intent, _without_ updating the calling uid or package. */ private void setIntent(Intent _intent, ActivityInfo info) { - final boolean isLeaf = isLeafTask(); + if (!isLeafTask()) return; if (intent == null) { - mNeverRelinquishIdentity = - (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; - } else if (mNeverRelinquishIdentity && isLeaf) { + mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; + } else if (mNeverRelinquishIdentity) { return; } - affinity = isLeaf ? info.taskAffinity : null; + affinity = info.taskAffinity; if (intent == null) { // If this task already has an intent associated with it, don't set the root // affinity -- we don't want it changing after initially set, but the initially @@ -962,6 +982,7 @@ private void setIntent(Intent _intent, ActivityInfo info) { rootAffinity = affinity; } effectiveUid = info.applicationInfo.uid; + mIsEffectivelySystemApp = info.applicationInfo.isSystemApp(); stringName = null; if (info.targetActivity == null) { @@ -1131,7 +1152,7 @@ void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer ol && (newParent == null || !newParent.inPinnedWindowingMode())) { // Notify if a task from the pinned stack is being removed // (or moved depending on the mode). - mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); + mRootWindowContainer.notifyActivityPipModeChanged(this, null); } } @@ -3632,6 +3653,54 @@ void fillTaskInfo(TaskInfo info, boolean stripExtras) { : SCREEN_ORIENTATION_UNSET; } + /** + * Removes the activity info if the activity belongs to a different uid, which is + * different from the app that hosts the task. + */ + static void trimIneffectiveInfo(Task task, TaskInfo info) { + final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing, + false /* traverseTopToBottom */); + final int baseActivityUid = + baseActivity != null ? baseActivity.getUid() : task.effectiveUid; + + if (info.topActivityInfo != null + && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { + // Making a copy to prevent eliminating the info in the original ActivityRecord. + info.topActivityInfo = new ActivityInfo(info.topActivityInfo); + info.topActivityInfo.applicationInfo = + new ApplicationInfo(info.topActivityInfo.applicationInfo); + + // Strip the sensitive info. + info.topActivity = new ComponentName("", ""); + info.topActivityInfo.packageName = ""; + info.topActivityInfo.taskAffinity = ""; + info.topActivityInfo.processName = ""; + info.topActivityInfo.name = ""; + info.topActivityInfo.parentActivityName = ""; + info.topActivityInfo.targetActivity = ""; + info.topActivityInfo.splitName = ""; + info.topActivityInfo.applicationInfo.className = ""; + info.topActivityInfo.applicationInfo.credentialProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.dataDir = ""; + info.topActivityInfo.applicationInfo.deviceProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.manageSpaceActivityName = ""; + info.topActivityInfo.applicationInfo.nativeLibraryDir = ""; + info.topActivityInfo.applicationInfo.nativeLibraryRootDir = ""; + info.topActivityInfo.applicationInfo.processName = ""; + info.topActivityInfo.applicationInfo.publicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanPublicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanSourceDir = ""; + info.topActivityInfo.applicationInfo.sourceDir = ""; + info.topActivityInfo.applicationInfo.taskAffinity = ""; + info.topActivityInfo.applicationInfo.name = ""; + info.topActivityInfo.applicationInfo.packageName = ""; + } + + if (task.effectiveUid != baseActivityUid) { + info.baseActivity = new ComponentName("", ""); + } + } + /** * Returns a {@link TaskInfo} with information from this task. */ @@ -3660,6 +3729,17 @@ boolean shouldBeVisible(ActivityRecord starting) { return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE; } + /** + * Returns {@code true} is the activity in this Task can be resumed. + * + * @param starting The currently starting activity or {@code null} if there is none. + */ + boolean canBeResumed(@Nullable ActivityRecord starting) { + // No need to resume activity in Task that is not visible. + return isTopActivityFocusable() + && getVisibility(starting) == STACK_VISIBILITY_VISIBLE; + } + /** * Returns true if the task should be visible. * @@ -4617,5 +4697,4 @@ protected boolean isForceHidden() { long getProtoFieldId() { return TASK; } - } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 355a531496f3..565dc710b6bf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -56,10 +56,12 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -1570,6 +1572,7 @@ public int addWindow(Session session, IWindow client, int seq, final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid); + attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid); res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid); if (res != WindowManagerGlobal.ADD_OKAY) { @@ -1653,6 +1656,7 @@ public int addWindow(Session session, IWindow client, int seq, win.mToken.addWindow(win); displayPolicy.addWindowLw(win, attrs); + displayPolicy.setDropInputModePolicy(win, win.mAttrs); if (type == TYPE_INPUT_METHOD) { displayContent.setInputMethodWindowLocked(win); imMayMove = false; @@ -2154,6 +2158,7 @@ public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams if (attrs != null) { displayPolicy.adjustWindowParamsLw(win, attrs, pid, uid); win.mToken.adjustWindowParams(win, attrs); + attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid); // if they don't have the permission, mask out the status bar bits if (seq == win.mSeq) { int systemUiVisibility = attrs.systemUiVisibility @@ -8058,6 +8063,23 @@ void handleTaskFocusChange(Task task) { } } + /** + * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY. + */ + private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) { + if ((flags & FLAG_SLIPPERY) == 0) { + return flags; + } + final int permissionResult = mContext.checkPermission( + android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid); + if (permissionResult != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName + + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission"); + return flags & ~FLAG_SLIPPERY; + } + return flags; + } + /** * Assigns an InputChannel to a SurfaceControl and configures it to receive * touch input according to it's on-screen geometry. @@ -8066,7 +8088,7 @@ void handleTaskFocusChange(Task task) { * views. */ void grantInputChannel(int callingUid, int callingPid, int displayId, SurfaceControl surface, - IWindow window, IBinder hostInputToken, int flags, int type, + IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, InputChannel outInputChannel) { final InputApplicationHandle applicationHandle; final String name; @@ -8082,7 +8104,7 @@ void grantInputChannel(int callingUid, int callingPid, int displayId, SurfaceCon } updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface, - name, applicationHandle, flags, type, null /* region */); + name, applicationHandle, flags, privateFlags, type, null /* region */); clientChannel.transferTo(outInputChannel); clientChannel.dispose(); @@ -8090,13 +8112,15 @@ void grantInputChannel(int callingUid, int callingPid, int displayId, SurfaceCon private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, int displayId, SurfaceControl surface, String name, - InputApplicationHandle applicationHandle, int flags, int type, Region region) { + InputApplicationHandle applicationHandle, int flags, int privateFlags, int type, + Region region) { InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId); h.token = channelToken; h.name = name; - final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_SLIPPERY); + flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid); + + final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY); h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags; h.layoutParamsType = type; h.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; @@ -8118,6 +8142,9 @@ private void updateInputChannel(IBinder channelToken, int callingUid, int callin h.setTouchableRegionCrop(surface); } + // Check private trusted overlay flag to set trustedOverlay field of input window handle. + h.trustedOverlay = (privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0; + SurfaceControl.Transaction t = mTransactionFactory.get(); t.setInputWindowInfo(surface, h); t.apply(); @@ -8131,7 +8158,7 @@ private void updateInputChannel(IBinder channelToken, int callingUid, int callin * is undefined. */ void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, Region region) { + int flags, int privateFlags, Region region) { final InputApplicationHandle applicationHandle; final String name; final EmbeddedWindowController.EmbeddedWindow win; @@ -8146,7 +8173,7 @@ void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surf } updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name, - applicationHandle, flags, win.mWindowType, region); + applicationHandle, flags, privateFlags, win.mWindowType, region); } /** Return whether layer tracing is enabled */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index faa483a48ea4..7360e3677def 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -55,6 +55,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; @@ -947,6 +948,13 @@ public boolean isInteractive() { ? mActivityRecord.getInputApplicationHandle(false /* update */) : null, getDisplayId()); + // Check private trusted overlay flag and window type to set trustedOverlay variable of + // input window handle. + mInputWindowHandle.trustedOverlay = + (mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0 + && mOwnerCanAddInternalSystemWindow; + mInputWindowHandle.trustedOverlay |= InputMonitor.isTrustedOverlay(mAttrs.type); + // Make sure we initial all fields before adding to parentWindow, to prevent exception // during onDisplayChanged. if (mIsChildWindow) { @@ -5812,20 +5820,29 @@ void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, } /** - * Returns {@code true} if this window is not {@link WindowManager.LayoutParams#TYPE_TOAST} - * or {@link WindowManager.LayoutParams#TYPE_APPLICATION_STARTING}, - * since this window doesn't belong to apps. + * Returns {@code true} if this window is not {@link WindowManager.LayoutParams#TYPE_TOAST}, + * {@link WindowManager.LayoutParams#TYPE_APPLICATION_STARTING} or + * {@link WindowManager.LayoutParams#TYPE_PRIVATE_PRESENTATION}, + * since those windows should not count towards the apps visibility. */ - boolean isNonToastOrStarting() { - return mAttrs.type != TYPE_TOAST && mAttrs.type != TYPE_APPLICATION_STARTING; + boolean isNonToastOrStartingOrPrivatePresentation() { + return mAttrs.type != TYPE_TOAST && mAttrs.type != TYPE_APPLICATION_STARTING + && mAttrs.type != TYPE_PRIVATE_PRESENTATION + && !(mAttrs.type == TYPE_PRESENTATION && isOnVirtualDisplay()); } boolean isNonToastWindowVisibleForUid(int callingUid) { - return getOwningUid() == callingUid && isNonToastOrStarting() && isVisibleNow(); + return getOwningUid() == callingUid && isNonToastOrStartingOrPrivatePresentation() + && isVisibleNow(); } boolean isNonToastWindowVisibleForPid(int pid) { - return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow(); + return mSession.mPid == pid && isNonToastOrStartingOrPrivatePresentation() + && isVisibleNow(); + } + + private boolean isOnVirtualDisplay() { + return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL; } void setViewVisibility(int viewVisibility) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index eff222a2051a..900d3e8898ac 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -15,8 +15,10 @@ */ package com.android.server.devicepolicy; +import android.accounts.Account; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; +import android.os.UserHandle; import com.android.server.SystemService; @@ -86,4 +88,9 @@ public long getManagedProfileMaximumTimeOff(ComponentName admin) { public boolean canProfileOwnerResetPasswordWhenLocked(int userId) { return false; } + + public void finalizeWorkProfileProvisioning( + UserHandle managedProfileUser, Account migratedAccount) { + + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e911ada1026a..5835cc53e854 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -23,6 +23,7 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; @@ -50,6 +51,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; +import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION; @@ -319,6 +321,7 @@ import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.time.LocalDate; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -330,6 +333,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -403,6 +407,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572; + // Binary XML serializer doesn't support longer strings + private static final int MAX_POLICY_STRING_LENGTH = 65535; + // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names. + private static final int MAX_PACKAGE_NAME_LENGTH = 223; + + private static final int MAX_LONG_SUPPORT_MESSAGE_LENGTH = 20000; + private static final int MAX_SHORT_SUPPORT_MESSAGE_LENGTH = 200; + private static final int MAX_ORG_NAME_LENGTH = 200; + private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms @@ -4019,7 +4032,9 @@ private void loadSettingsLocked(DevicePolicyData policy, int userHandle) { updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle); - updateUserControlDisabledPackagesLocked(policy.mUserControlDisabledPackages); + if (userHandle == UserHandle.USER_SYSTEM) { + updateUserControlDisabledPackagesLocked(policy.mUserControlDisabledPackages); + } if (policy.mStatusBarDisabled) { setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle); } @@ -4047,6 +4062,8 @@ private void updateLockTaskPackagesLocked(List packages, int userId) { private void updateUserControlDisabledPackagesLocked(List packages) { mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages); + mUsageStatsManagerInternal.setAdminProtectedPackages( + new ArraySet(packages), UserHandle.USER_ALL); } private void updateLockTaskFeaturesLocked(int flags, int userId) { @@ -9424,6 +9441,9 @@ && isProfileOwnerOfOrganizationOwnedDevice(userInfo.id)) { final ComponentName doComponent = mOwners.getDeviceOwnerComponent(); final ComponentName poComponent = mOwners.getProfileOwnerComponent(userHandle.getIdentifier()); + if (supervisorComponent == null) { + return null; + } if (supervisorComponent.equals(doComponent) || supervisorComponent.equals( poComponent)) { return supervisorComponent; @@ -10114,6 +10134,12 @@ public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, } Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); + enforceMaxPackageNameLength(agent.getPackageName()); + final String agentAsString = agent.flattenToString(); + enforceMaxStringLength(agentAsString, "agent name"); + if (args != null) { + enforceMaxStringLength(args, "args"); + } final int userHandle = UserHandle.getCallingUserId(); synchronized (getLockObject()) { ActiveAdmin ap = getActiveAdminForCallerLocked(admin, @@ -10332,6 +10358,10 @@ public boolean setPermittedAccessibilityServices(ComponentName who, List package Objects.requireNonNull(who, "ComponentName is null"); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + int userId = UserHandle.getCallingUserId(); List enabledServices = null; long id = mInjector.binderClearCallingIdentity(); @@ -10483,6 +10513,10 @@ public boolean setPermittedInputMethods(ComponentName who, List packageList) { Objects.requireNonNull(who, "ComponentName is null"); final int callingUserId = mInjector.userHandleGetCallingUserId(); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + List enabledImes = InputMethodManagerInternal.get() .getEnabledInputMethodListAsUser(callingUserId); if (enabledImes != null) { @@ -10677,6 +10711,37 @@ private void maybeSendAdminEnabledBroadcastLocked(int userHandle) { } } + @Override + public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser, + Account migratedAccount) { + if (mContext.checkCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Calling identity is not authorized"); + } + + if (!isManagedProfile(managedProfileUser.getIdentifier())) { + throw new IllegalStateException("Given user is not a managed profile"); + } + ComponentName profileOwnerComponent = + mOwners.getProfileOwnerComponent(managedProfileUser.getIdentifier()); + if (profileOwnerComponent == null) { + throw new IllegalStateException("There is no profile owner on the given profile"); + } + Intent primaryProfileSuccessIntent = new Intent(ACTION_MANAGED_PROFILE_PROVISIONED); + primaryProfileSuccessIntent.setPackage(profileOwnerComponent.getPackageName()); + primaryProfileSuccessIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES + | Intent.FLAG_RECEIVER_FOREGROUND); + primaryProfileSuccessIntent.putExtra(Intent.EXTRA_USER, managedProfileUser); + + if (migratedAccount != null) { + primaryProfileSuccessIntent.putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, + migratedAccount); + } + + mContext.sendBroadcastAsUser(primaryProfileSuccessIntent, + UserHandle.of(getProfileParentId(managedProfileUser.getIdentifier()))); + } + @Override public UserHandle createAndManageUser(ComponentName admin, String name, ComponentName profileOwner, PersistableBundle adminExtras, int flags) { @@ -11454,6 +11519,8 @@ public void setAccountManagementDisabled(ComponentName who, String accountType, return; } Objects.requireNonNull(who, "ComponentName is null"); + enforceMaxStringLength(accountType, "account type"); + synchronized (getLockObject()) { /* * When called on the parent DPM instance (parent == true), affects active admin @@ -11800,6 +11867,9 @@ public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packages, "packages is null"); + for (String pkg : packages) { + enforceMaxPackageNameLength(pkg); + } synchronized (getLockObject()) { enforceCanCallLockTaskLocked(who); @@ -12509,6 +12579,13 @@ public void addOnCrossProfileWidgetProvidersChangeListener( } } + @Override + public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle) { + return DevicePolicyManagerService.this.getProfileOwnerOrDeviceOwnerSupervisionComponent( + userHandle); + } + @Override public boolean isActiveAdminWithPolicy(int uid, int reqPolicy) { synchronized (getLockObject()) { @@ -13638,6 +13715,8 @@ public void setShortSupportMessage(@NonNull ComponentName who, CharSequence mess return; } Objects.requireNonNull(who, "ComponentName is null"); + message = truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); + final int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { ActiveAdmin admin = getActiveAdminForUidLocked(who, mInjector.binderGetCallingUid()); @@ -13669,6 +13748,9 @@ public void setLongSupportMessage(@NonNull ComponentName who, CharSequence messa if (!mHasFeature) { return; } + + message = truncateIfLonger(message, MAX_LONG_SUPPORT_MESSAGE_LENGTH); + Objects.requireNonNull(who, "ComponentName is null"); final int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { @@ -13802,6 +13884,8 @@ public void setOrganizationName(@NonNull ComponentName who, CharSequence text) { Objects.requireNonNull(who, "ComponentName is null"); final int userHandle = mInjector.userHandleGetCallingUserId(); + text = truncateIfLonger(text, MAX_ORG_NAME_LENGTH); + synchronized (getLockObject()) { ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -14040,9 +14124,8 @@ public void setAffiliationIds(ComponentName admin, List ids) { throw new IllegalArgumentException("ids must not be null"); } for (String id : ids) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("ids must not contain empty string"); - } + Preconditions.checkArgument(!TextUtils.isEmpty(id), "ids must not have empty string"); + enforceMaxStringLength(id, "affiliation id"); } final Set affiliationIds = new ArraySet<>(ids); @@ -15169,6 +15252,9 @@ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentNa Objects.requireNonNull(admin, "Admin cannot be null."); Objects.requireNonNull(target, "Target cannot be null."); + if (bundle != null) { + enforceMaxStringLength(bundle, "bundle"); + } enforceProfileOrDeviceOwner(admin); @@ -16455,4 +16541,51 @@ public boolean canProfileOwnerResetPasswordWhenLocked(int userId) { return true; } } + + /** + * Truncates char sequence to maximum length, nulls are ignored. + */ + private static CharSequence truncateIfLonger(CharSequence input, int maxLength) { + return input == null || input.length() <= maxLength + ? input + : input.subSequence(0, maxLength); + } + + /** + * Throw if string argument is too long to be serialized. + */ + private static void enforceMaxStringLength(String str, String argName) { + Preconditions.checkArgument( + str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long"); + } + + private static void enforceMaxPackageNameLength(String pkg) { + Preconditions.checkArgument( + pkg.length() <= MAX_PACKAGE_NAME_LENGTH, "Package name too long"); + } + + /** + * Throw if persistable bundle contains any string that we can't serialize. + */ + private static void enforceMaxStringLength(PersistableBundle bundle, String argName) { + // Persistable bundles can have other persistable bundles as values, traverse with a queue. + Queue queue = new ArrayDeque<>(); + queue.add(bundle); + while (!queue.isEmpty()) { + PersistableBundle current = queue.remove(); + for (String key : current.keySet()) { + enforceMaxStringLength(key, "key in " + argName); + Object value = current.get(key); + if (value instanceof String) { + enforceMaxStringLength((String) value, "string value in " + argName); + } else if (value instanceof String[]) { + for (String str : (String[]) value) { + enforceMaxStringLength(str, "string value in " + argName); + } + } else if (value instanceof PersistableBundle) { + queue.add((PersistableBundle) value); + } + } + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 39a3aae767ea..f7b7a5801b42 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -18,6 +18,7 @@ import static android.database.sqlite.SQLiteDatabase.deleteDatabase; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -36,6 +37,7 @@ import android.accounts.IAccountManagerResponse; import android.app.AppOpsManager; import android.app.INotificationManager; +import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.BroadcastReceiver; @@ -132,6 +134,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { protected void setUp() throws Exception { MockitoAnnotations.initMocks(this); + PropertyInvalidatedCache.disableForTestMode(); + when(mMockPackageManager.checkSignatures(anyInt(), anyInt())) .thenReturn(PackageManager.SIGNATURE_MATCH); final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); @@ -241,6 +245,25 @@ public void testCheckAddAccount() throws Exception { assertEquals(a31, accounts[1]); } + @SmallTest + public void testCheckAddAccountLongName() throws Exception { + unlockSystemUser(); + String longString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaa"; + Account a11 = new Account(longString, AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1); + + mAms.addAccountExplicitly(a11, /* password= */ "p11", /* extras= */ null); + + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); + assertEquals(0, accounts.length); + } + + @SmallTest public void testPasswords() throws Exception { unlockSystemUser(); @@ -669,6 +692,41 @@ public void testStartAddAccountSessionReturnWithValidIntent() throws Exception { assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); } + @SmallTest + public void testStartAddAccountSessionWhereAuthenticatorReturnsIntentWithProhibitedFlags() + throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + Bundle options = createOptionsWithAccountName( + AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE); + int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; + options.putInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, prohibitedFlags); + + mAms.startAddAccountSession( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + options); // optionsIn + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), contains("invalid intent")); + } + @SmallTest public void testStartAddAccountSessionError() throws Exception { unlockSystemUser(); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java index 73f30d9f9e79..b98a6a891d55 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java @@ -17,9 +17,6 @@ import android.accounts.Account; -import java.util.ArrayList; -import java.util.List; - /** * Constants shared between test AccountAuthenticators and AccountManagerServiceTest. */ @@ -31,6 +28,8 @@ public final class AccountManagerServiceTestFixtures { "account_manager_service_test:account_status_token_key"; public static final String KEY_ACCOUNT_PASSWORD = "account_manager_service_test:account_password_key"; + public static final String KEY_INTENT_FLAGS = + "account_manager_service_test:intent_flags_key"; public static final String KEY_OPTIONS_BUNDLE = "account_manager_service_test:option_bundle_key"; public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com"; diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java index 8106364477d9..924443e9d5cf 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java +++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java @@ -24,8 +24,6 @@ import android.content.Intent; import android.os.Bundle; -import com.android.frameworks.servicestests.R; - import java.util.concurrent.atomic.AtomicInteger; /** @@ -270,11 +268,13 @@ public Bundle startAddAccountSession( String accountName = null; Bundle sessionBundle = null; String password = null; + int intentFlags = 0; if (options != null) { accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME); sessionBundle = options.getBundle( AccountManagerServiceTestFixtures.KEY_ACCOUNT_SESSION_BUNDLE); password = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_PASSWORD); + intentFlags = options.getInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, 0); } Bundle result = new Bundle(); @@ -302,6 +302,7 @@ public Bundle startAddAccountSession( intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, eventualActivityResultData); intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + intent.setFlags(intentFlags); result.putParcelable(AccountManager.KEY_INTENT, intent); } else { diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java index cffff66b64f1..02cf971a8076 100644 --- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java @@ -23,7 +23,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.debug.AdbManager; +import android.debug.IAdbManager; +import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; @@ -105,6 +112,7 @@ public void setUp() throws Exception { public void tearDown() throws Exception { mKeyStore.deleteKeyStore(); setAllowedConnectionTime(mOriginalAllowedConnectionTime); + dropShellPermissionIdentity(); } /** @@ -813,6 +821,108 @@ private boolean isValidMdnsServiceName(String name) { return hasAtLeastOneLetter; } + CountDownLatch mAdbActionLatch = new CountDownLatch(1); + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.i(TAG, "Received intent action=" + action); + if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) { + assertEquals("Received broadcast without MANAGE_DEBUGGING permission.", + context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING), + PackageManager.PERMISSION_GRANTED); + Log.i(TAG, "action=" + action + " paired_device=" + intent.getSerializableExtra( + AdbManager.WIRELESS_DEVICES_EXTRA).toString()); + mAdbActionLatch.countDown(); + } else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) { + assertEquals("Received broadcast without MANAGE_DEBUGGING permission.", + context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING), + PackageManager.PERMISSION_GRANTED); + int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA, + AdbManager.WIRELESS_STATUS_DISCONNECTED); + Log.i(TAG, "action=" + action + " status=" + status); + mAdbActionLatch.countDown(); + } else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) { + assertEquals("Received broadcast without MANAGE_DEBUGGING permission.", + context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING), + PackageManager.PERMISSION_GRANTED); + Integer res = intent.getIntExtra( + AdbManager.WIRELESS_STATUS_EXTRA, + AdbManager.WIRELESS_STATUS_FAIL); + Log.i(TAG, "action=" + action + " result=" + res); + + if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) { + String pairingCode = intent.getStringExtra( + AdbManager.WIRELESS_PAIRING_CODE_EXTRA); + Log.i(TAG, "pairingCode=" + pairingCode); + } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) { + int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0); + Log.i(TAG, "port=" + port); + } + mAdbActionLatch.countDown(); + } + } + }; + + private void adoptShellPermissionIdentity() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(android.Manifest.permission.MANAGE_DEBUGGING); + } + + private void dropShellPermissionIdentity() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void testBroadcastReceiverWithPermissions() throws Exception { + adoptShellPermissionIdentity(); + final IAdbManager mAdbManager = IAdbManager.Stub.asInterface( + ServiceManager.getService(Context.ADB_SERVICE)); + IntentFilter intentFilter = + new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION); + intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION); + intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); + assertEquals("Context does not have MANAGE_DEBUGGING permission.", + mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING), + PackageManager.PERMISSION_GRANTED); + try { + mContext.registerReceiver(mReceiver, intentFilter); + mAdbManager.enablePairingByPairingCode(); + if (!mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("Receiver did not receive adb intent action within the timeout duration"); + } + } finally { + mContext.unregisterReceiver(mReceiver); + } + } + + @Test + public void testBroadcastReceiverWithoutPermissions() throws Exception { + adoptShellPermissionIdentity(); + final IAdbManager mAdbManager = IAdbManager.Stub.asInterface( + ServiceManager.getService(Context.ADB_SERVICE)); + IntentFilter intentFilter = + new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION); + intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION); + intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); + mAdbManager.enablePairingByPairingCode(); + + dropShellPermissionIdentity(); + assertEquals("Context has MANAGE_DEBUGGING permission.", + mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING), + PackageManager.PERMISSION_DENIED); + try { + mContext.registerReceiver(mReceiver, intentFilter); + + if (mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("Broadcast receiver received adb action intent without debug permissions"); + } + } finally { + mContext.unregisterReceiver(mReceiver); + } + } + /** * Runs an adb test with the provided configuration. * diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index ca7704950b51..088d693205c2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -53,6 +53,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.util.Collections; import java.util.Locale; /** @@ -223,6 +224,15 @@ public void testShortcutInfoMissingMandatoryFields() { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + String.join("", Collections.nCopies(Short.MAX_VALUE, "s"))).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 6c1c019f504e..658f168b608b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -86,6 +86,13 @@ public void testUserSystemPackageWhitelist() throws Exception { } } + public void testValidateName() { + assertNull(UserManagerService.validateName("android")); + assertNull(UserManagerService.validateName("com.company.myapp")); + assertNotNull(UserManagerService.validateName("/../../data")); + assertNotNull(UserManagerService.validateName("/dir")); + } + private Bundle createBundle() { Bundle result = new Bundle(); // Tests for 6 allowed types: Integer, Boolean, String, String[], Bundle and Parcelable[] diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 1ced467dffa1..8da4fed1ecf0 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -125,6 +125,9 @@ public class AppStandbyControllerTests { private static final String ADMIN_PKG2 = "com.android.admin2"; private static final String ADMIN_PKG3 = "com.android.admin3"; + private static final String ADMIN_PROTECTED_PKG = "com.android.admin.protected"; + private static final String ADMIN_PROTECTED_PKG2 = "com.android.admin.protected2"; + private static final long MINUTE_MS = 60 * 1000; private static final long HOUR_MS = 60 * MINUTE_MS; private static final long DAY_MS = 24 * HOUR_MS; @@ -1369,6 +1372,19 @@ public void isActiveDeviceAdmin() { assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); } + @Test + public void testSetAdminProtectedPackages() { + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + } + @Test public void testUserInteraction_CrossProfile() throws Exception { mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3}; @@ -1593,6 +1609,28 @@ private void setActiveAdmins(int userId, String... admins) { mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId); } + private void setAdminProtectedPackages(int userId, String... packageNames) { + Set adminProtectedPackages = packageNames != null ? new ArraySet<>( + Arrays.asList(packageNames)) : null; + mController.setAdminProtectedPackages(adminProtectedPackages, userId); + } + + private void assertAdminProtectedPackagesForTest(int userId, String... packageNames) { + final Set actualAdminProtectedPackages = + mController.getAdminProtectedPackagesForTest(userId); + if (packageNames == null) { + if (actualAdminProtectedPackages != null && !actualAdminProtectedPackages.isEmpty()) { + fail("Admin protected packages should be null; " + getAdminAppsStr(userId, + actualAdminProtectedPackages)); + } + return; + } + assertEquals(packageNames.length, actualAdminProtectedPackages.size()); + for (String adminProtectedPackage : packageNames) { + assertTrue(actualAdminProtectedPackages.contains(adminProtectedPackage)); + } + } + private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception { rearmLatch(pkg); mController.setAppStandbyBucket(pkg, user, bucket, reason); diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 767857bf2de8..e8e3a8f84f21 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -33,6 +33,7 @@ + diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java index c69ef8d93282..c4e0fa0cc1e3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java @@ -15,14 +15,22 @@ */ package com.android.server.notification; +import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_CURRENT; +import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + import android.app.Notification; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -33,6 +41,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -45,6 +54,8 @@ public class ArchiveTest extends UiServiceTestCase { private static final int SIZE = 5; private NotificationManagerService.Archive mArchive; + @Mock + private UserManager mUm; @Before public void setUp() { @@ -53,6 +64,9 @@ public void setUp() { mArchive = new NotificationManagerService.Archive(SIZE); mArchive.updateHistoryEnabled(USER_SYSTEM, true); mArchive.updateHistoryEnabled(USER_CURRENT, true); + + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn( + new int[] {USER_CURRENT, USER_SYSTEM}); } private StatusBarNotification getNotification(String pkg, int id, UserHandle user) { @@ -64,7 +78,6 @@ private StatusBarNotification getNotification(String pkg, int id, UserHandle use pkg, pkg, id, null, 0, 0, n, user, null, System.currentTimeMillis()); } - @Test public void testRecordAndRead() { List expected = new ArrayList<>(); @@ -75,13 +88,29 @@ public void testRecordAndRead() { mArchive.record(sbn, REASON_CANCEL); } - List actual = Arrays.asList(mArchive.getArray(SIZE, true)); + List actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); assertThat(actual).hasSize(expected.size()); for (StatusBarNotification sbn : actual) { assertThat(expected).contains(sbn.getKey()); } } + @Test + public void testCrossUser() { + mArchive.record(getNotification("pkg", 1, UserHandle.of(USER_SYSTEM)), REASON_CANCEL); + mArchive.record(getNotification("pkg", 2, UserHandle.of(USER_CURRENT)), REASON_CANCEL); + mArchive.record(getNotification("pkg", 3, UserHandle.of(USER_ALL)), REASON_CANCEL); + mArchive.record(getNotification("pkg", 4, UserHandle.of(USER_NULL)), REASON_CANCEL); + + List actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); + assertThat(actual).hasSize(3); + for (StatusBarNotification sbn : actual) { + if (sbn.getUserId() == USER_NULL) { + fail("leaked notification from wrong user"); + } + } + } + @Test public void testRecordAndRead_overLimit() { List expected = new ArrayList<>(); @@ -93,7 +122,8 @@ public void testRecordAndRead_overLimit() { } } - List actual = Arrays.asList(mArchive.getArray((SIZE * 2), true)); + List actual = Arrays.asList( + mArchive.getArray(mUm, (SIZE * 2), true)); assertThat(actual).hasSize(expected.size()); for (StatusBarNotification sbn : actual) { assertThat(expected).contains(sbn.getKey()); @@ -113,7 +143,7 @@ public void testDoesNotRecordIfHistoryDisabled() { } } - List actual = Arrays.asList(mArchive.getArray(SIZE, true)); + List actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); assertThat(actual).hasSize(expected.size()); for (StatusBarNotification sbn : actual) { assertThat(expected).contains(sbn.getKey()); @@ -134,7 +164,7 @@ public void testRemovesEntriesWhenHistoryDisabled() { } mArchive.updateHistoryEnabled(USER_CURRENT, false); - List actual = Arrays.asList(mArchive.getArray(SIZE, true)); + List actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); assertThat(actual).hasSize(expected.size()); for (StatusBarNotification sbn : actual) { assertThat(expected).contains(sbn.getKey()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index a21bfda58529..9bf8758c6a8b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -1281,6 +1281,21 @@ public void testA11yQuietUpdate() throws Exception { verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); } + @Test + public void testA11yCrossUserEventNotSent() throws Exception { + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + int userId = mUser.getIdentifier() + 1; + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, UserHandle.of(userId), null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, + new NotificationChannel("test", "test", IMPORTANCE_HIGH)); + + mService.buzzBeepBlinkLocked(r); + + verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt()); + } + @Test public void testLightsScreenOn() { mService.mScreenOn = true; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java new file mode 100644 index 000000000000..a4727a09570c --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; +import android.testing.TestableContext; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; + +public class NotificationListenersTest extends UiServiceTestCase { + + @Mock + private PackageManager mPm; + @Mock + private IPackageManager miPm; + + @Mock + NotificationManagerService mNm; + @Mock + private INotificationManager mINm; + private TestableContext mContext = spy(getContext()); + + NotificationManagerService.NotificationListeners mListeners; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + getContext().setMockPackageManager(mPm); + doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); + + mListeners = spy(mNm.new NotificationListeners( + mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm)); + when(mNm.getBinderService()).thenReturn(mINm); + } + + + @Test + public void testNotifyPostedLockedInLockdownMode() { + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationRecord old0 = mock(NotificationRecord.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationRecord old1 = mock(NotificationRecord.class); + UserHandle uh1 = mock(UserHandle.class); + + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(old0); + reset(r1); + reset(old1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, never()).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); + } + + @Test + public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationStats rs0 = mock(NotificationStats.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationStats rs1 = mock(NotificationStats.class); + UserHandle uh1 = mock(UserHandle.class); + + StatusBarNotification sbn = mock(StatusBarNotification.class); + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mHandler"), + mock(NotificationManagerService.WorkerHandler.class)); + + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(rs0); + reset(r1); + reset(rs1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, never()).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 1a4cce466c66..025f8a5d368a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -52,9 +52,12 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; +import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -99,6 +102,7 @@ import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -138,6 +142,7 @@ import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; @@ -171,6 +176,7 @@ import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; @@ -179,6 +185,8 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.collect.ImmutableList; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -202,6 +210,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; @@ -245,6 +254,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock Resources mResources; @Mock + ActivityManagerInternal mAmi; + @Mock RankingHandler mRankingHandler; @Mock protected PackageManagerInternal mPackageManagerInternal; @@ -296,10 +307,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarManagerInternal mStatusBar; private final FakeSystemClock mSystemClock = new FakeSystemClock(); + private NotificationManagerService.WorkerHandler mWorkerHandler; + // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { int countSystemChecks = 0; boolean isSystemUid = true; + boolean isSystemAppId = true; int countLogSmartSuggestionsVisible = 0; // If true, don't enqueue the PostNotificationRunnables, just trap them boolean trapEnqueuedNotifications = false; @@ -308,6 +322,9 @@ private static class TestableNotificationManagerService extends NotificationMana @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; + @Nullable + Boolean mIsVisibleToListenerReturnValue = null; + TestableNotificationManagerService( Context context, NotificationRecordLogger logger, @@ -326,6 +343,12 @@ protected boolean isCallingUidSystem() { return isSystemUid; } + @Override + protected boolean isCallingAppIdSystem() { + countSystemChecks++; + return isSystemUid || isSystemAppId; + } + @Override protected boolean isCallerSystemOrPhone() { countSystemChecks++; @@ -393,8 +416,38 @@ private void setNotificationAssistantAccessGrantedCallback( interface NotificationAssistantAccessGrantedCallback { void onGranted(ComponentName assistant, int userId, boolean granted); } + + protected void setIsVisibleToListenerReturnValue(boolean value) { + mIsVisibleToListenerReturnValue = value; + } + + @Override + boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { + if (mIsVisibleToListenerReturnValue != null) { + return mIsVisibleToListenerReturnValue; + } + return super.isVisibleToListener(sbn, listener); + } + + class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { + private int mGetStrongAuthForUserReturnValue = 0; + StrongAuthTrackerFake(Context context) { + super(context); + } + + public void setGetStrongAuthForUserReturnValue(int val) { + mGetStrongAuthForUserReturnValue = val; + } + + @Override + public int getStrongAuthForUser(int userId) { + return mGetStrongAuthForUserReturnValue; + } + } } + TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker; + private class TestableToastCallback extends ITransientNotification.Stub { @Override public void show(IBinder windowToken) { @@ -418,7 +471,6 @@ public void setUp() throws Exception { DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); when(deviceIdleInternal.getNotificationWhitelistDuration()).thenReturn(3000L); - ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); @@ -429,7 +481,7 @@ public void setUp() throws Exception { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal); + LocalServices.addService(ActivityManagerInternal.class, mAmi); mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); @@ -462,6 +514,7 @@ public void setUp() throws Exception { when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG}); when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG}); mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); + when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0}); // write to a test file; the system file isn't readable from tests mFile = new File(mContext.getCacheDir(), "test.xml"); @@ -498,7 +551,8 @@ null, new ComponentName(PKG, "test_class"), when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true); - mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()), + mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); + mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, @@ -506,11 +560,14 @@ null, new ComponentName(PKG, "test_class"), mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class), - mock(ActivityManagerInternal.class)); + mAmi); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mService.setAudioManager(mAudioManager); + mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext); + mService.setStrongAuthTracker(mStrongAuthTracker); + mShortcutHelper = mService.getShortcutHelper(); mShortcutHelper.setLauncherApps(mLauncherApps); mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); @@ -564,6 +621,7 @@ public void tearDown() throws Exception { mService.unregisterDeviceConfigChange(); InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); + mWorkerHandler.removeCallbacksAndMessages(null); } private ArrayMap> generateResetComponentValues() { @@ -2473,7 +2531,8 @@ public void testDeleteChannelGroupNotifyListener() throws Exception { .thenReturn(associations); NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) + when(mPreferencesHelper.getNotificationChannelGroupWithChannels( + eq(PKG), anyInt(), eq(ncg.getId()), anyBoolean())) .thenReturn(ncg); reset(mListeners); mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId()); @@ -2482,6 +2541,56 @@ public void testDeleteChannelGroupNotifyListener() throws Exception { eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); } + @Test + public void testDeleteChannelGroupChecksForFgses() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(associations); + CountDownLatch latch = new CountDownLatch(2); + mService.createNotificationChannelGroup( + PKG, mUid, new NotificationChannelGroup("group", "group"), true, false); + new Thread(() -> { + NotificationChannel notificationChannel = new NotificationChannel("id", "id", + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("group"); + ParceledListSlice pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + latch.countDown(); + }).start(); + new Thread(() -> { + try { + synchronized (this) { + wait(5000); + } + mService.createNotificationChannelGroup(PKG, mUid, + new NotificationChannelGroup("new", "new group"), true, false); + NotificationChannel notificationChannel = + new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("new"); + ParceledListSlice pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + mBinderService.deleteNotificationChannelGroup(PKG, "group"); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } catch (Exception e) { + e.printStackTrace(); + } + latch.countDown(); + }).start(); + + latch.await(); + verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString()); + } + @Test public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); @@ -2715,20 +2824,81 @@ public void testHasCompanionDevice_noService() { assertFalse(mService.hasCompanionDevice(mListener)); } + @Test + public void testSnoozeRunnable_tooManySnoozed_singleNotification() { + final NotificationRecord notification = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + mService.addNotification(notification); + + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); + when(mSnoozeHelper.canSnooze(1)).thenReturn(false); + + NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = + mService.new SnoozeNotificationRunnable( + notification.getKey(), 100, null); + snoozeNotificationRunnable.run(); + + verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong()); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testSnoozeRunnable_tooManySnoozed_singleGroupChildNotification() { + final NotificationRecord notification = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord notificationChild = generateNotificationRecord( + mTestNotificationChannel, 1, "group", false); + mService.addNotification(notification); + mService.addNotification(notificationChild); + + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); + when(mSnoozeHelper.canSnooze(2)).thenReturn(false); + + NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = + mService.new SnoozeNotificationRunnable( + notificationChild.getKey(), 100, null); + snoozeNotificationRunnable.run(); + + verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong()); + assertEquals(2, mService.getNotificationRecordCount()); + } + + @Test + public void testSnoozeRunnable_tooManySnoozed_summaryNotification() { + final NotificationRecord notification = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord notificationChild = generateNotificationRecord( + mTestNotificationChannel, 12, "group", false); + final NotificationRecord notificationChild2 = generateNotificationRecord( + mTestNotificationChannel, 13, "group", false); + mService.addNotification(notification); + mService.addNotification(notificationChild); + mService.addNotification(notificationChild2); + + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); + when(mSnoozeHelper.canSnooze(3)).thenReturn(false); + + NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = + mService.new SnoozeNotificationRunnable( + notification.getKey(), 100, null); + snoozeNotificationRunnable.run(); + + verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong()); + assertEquals(3, mService.getNotificationRecordCount()); + } + @Test public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception { final NotificationRecord notification = generateNotificationRecord( mTestNotificationChannel, 1, null, true); mService.addNotification(notification); when(mSnoozeHelper.getNotification(any())).thenReturn(notification); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( notification.getKey(), 100, null); snoozeNotificationRunnable.run(); - NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 = - mService.new SnoozeNotificationRunnable( - notification.getKey(), 100, null); snoozeNotificationRunnable.run(); // snooze twice @@ -2736,19 +2906,17 @@ mService.new SnoozeNotificationRunnable( } @Test - public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() throws Exception { + public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() { final NotificationRecord notification = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); mService.addNotification(notification); when(mSnoozeHelper.getNotification(any())).thenReturn(notification); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( notification.getKey(), 100, null); snoozeNotificationRunnable.run(); - NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 = - mService.new SnoozeNotificationRunnable( - notification.getKey(), 100, null); snoozeNotificationRunnable.run(); // snooze twice @@ -2766,6 +2934,7 @@ public void testSnoozeRunnable_reSnoozeMultipleNotificationsWithGroupKey() throw when(mSnoozeHelper.getNotification(any())).thenReturn(notification); when(mSnoozeHelper.getNotifications( anyString(), anyString(), anyInt())).thenReturn(new ArrayList<>()); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2775,8 +2944,8 @@ mService.new SnoozeNotificationRunnable( .thenReturn(new ArrayList<>(Arrays.asList(notification, notification2))); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 = mService.new SnoozeNotificationRunnable( - notification.getKey(), 100, null); - snoozeNotificationRunnable.run(); + notification2.getKey(), 100, null); + snoozeNotificationRunnable2.run(); // snooze twice verify(mSnoozeHelper, times(4)).snooze(any(NotificationRecord.class), anyLong()); @@ -2790,6 +2959,7 @@ public void testSnoozeRunnable_snoozeNonGrouped() throws Exception { mTestNotificationChannel, 2, "group", false); mService.addNotification(grouped); mService.addNotification(nonGrouped); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2819,6 +2989,7 @@ public void testSnoozeRunnable_snoozeSummary_withChildren() throws Exception { mService.addNotification(parent); mService.addNotification(child); mService.addNotification(child2); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2840,6 +3011,7 @@ public void testSnoozeRunnable_snoozeGroupChild_fellowChildren() throws Exceptio mService.addNotification(parent); mService.addNotification(child); mService.addNotification(child2); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2865,6 +3037,7 @@ public void testSnoozeRunnable_snoozeGroupChild_onlyChildOfSummary() throws Exce mTestNotificationChannel, 2, "group", false); mService.addNotification(parent); mService.addNotification(child); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2892,6 +3065,7 @@ public void testSnoozeRunnable_snoozeGroupChild_noOthersInGroup() throws Excepti final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); mService.addNotification(child); + when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true); NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = mService.new SnoozeNotificationRunnable( @@ -2992,6 +3166,34 @@ public void testSetListenerAccessForUser() throws Exception { any(), anyInt(), anyBoolean(), anyBoolean()); } + @Test + public void testSetListenerAccessForUser_grantWithNameTooLong_throws() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + try { + mBinderService.setNotificationListenerAccessGrantedForUser(c, user.getIdentifier(), + /* enabled= */ true); + fail("Should've thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Good! + } + } + + @Test + public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ false); + + verify(mListeners).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false); + } + @Test public void testSetAssistantAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); @@ -4094,21 +4296,182 @@ public void updateUriPermissions_posterDoesNotOwnUri() throws Exception { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setSmallIcon(smallIcon) + .setLargeIcon(largeIcon) .addExtras(extras) .build(); + // Serialize and deserialize the notification to make sure nothing breaks in the process, + // since that's what will usually happen before we get to call visitUris. + Parcel parcel = Parcel.obtain(); + n.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + n = new Notification(parcel); + Consumer visitor = (Consumer) spy(Consumer.class); n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(smallIcon.getUri())); + verify(visitor, times(1)).accept(eq(largeIcon.getUri())); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); + } + + @Test + public void testVisitUris_publicVersion() throws Exception { + final Icon smallIconPublic = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIconPrivate = Icon.createWithContentUri("content://media/large/icon"); + + Notification publicVersion = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(smallIconPublic) + .build(); + Notification n = new Notification.Builder(mContext, "a") + .setLargeIcon(largeIconPrivate) + .setPublicVersion(publicVersion) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(smallIconPublic.getUri())); + verify(visitor, times(1)).accept(eq(largeIconPrivate.getUri())); + } + + @Test + public void testVisitUris_messagingStyle() { + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person 1") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("Messaging Person 2") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("Messaging Person 3") + .setIcon(personIcon3) + .build(); + Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut"); + + Notification.Builder builder = new Notification.Builder(mContext, "a") + .setCategory(Notification.CATEGORY_MESSAGE) + .setContentTitle("new message!") + .setContentText("Conversation Notification") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message( + "Marco?", System.currentTimeMillis(), person2); + Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message( + "Polo!", System.currentTimeMillis(), person3); + Notification.MessagingStyle style = new Notification.MessagingStyle(person1) + .addMessage(message1) + .addMessage(message2) + .setShortcutIcon(shortcutIcon); + builder.setStyle(style); + Notification n = builder.build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(shortcutIcon.getUri())); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + } + + @Test + public void testVisitUris_styleExtrasWithoutStyle() { + Notification notification = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle( + personWithIcon("content://user")) + .addHistoricMessage(new Notification.MessagingStyle.Message("Heyhey!", + System.currentTimeMillis(), + personWithIcon("content://historicalMessenger"))) + .addMessage(new Notification.MessagingStyle.Message("Are you there", + System.currentTimeMillis(), + personWithIcon("content://messenger"))) + .setShortcutIcon( + Icon.createWithContentUri("content://conversationShortcut")); + messagingStyle.addExtras(notification.extras); // Instead of Builder.setStyle(style). + + Consumer visitor = (Consumer) spy(Consumer.class); + notification.visitUris(visitor); + + verify(visitor).accept(eq(Uri.parse("content://user"))); + verify(visitor).accept(eq(Uri.parse("content://historicalMessenger"))); + verify(visitor).accept(eq(Uri.parse("content://messenger"))); + verify(visitor).accept(eq(Uri.parse("content://conversationShortcut"))); + } + + private static Person personWithIcon(String iconUri) { + return new Person.Builder() + .setName("Mr " + iconUri) + .setIcon(Icon.createWithContentUri(iconUri)) + .build(); + } + + @Test + public void testVisitUris_wearableExtender() { + Icon actionIcon = Icon.createWithContentUri("content://media/action"); + Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor).accept(eq(actionIcon.getUri())); + verify(visitor).accept(eq(wearActionIcon.getUri())); } @Test @@ -5776,7 +6139,7 @@ public void testAutomaticZenRuleValidation_policyFilterAgreement() throws Except zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); try { - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); fail("Zen policy only applies to priority only mode"); } catch (IllegalArgumentException e) { // yay @@ -5784,11 +6147,70 @@ public void testAutomaticZenRuleValidation_policyFilterAgreement() throws Except rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); + } + + @Test + public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception { + mService.isSystemUid = true; + ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.setZenHelper(mockZenModeHelper); + ComponentName owner = new ComponentName("android", "ProviderName"); + ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build(); + boolean isEnabled = true; + AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); + mBinderService.addAutomaticZenRule(rule, "com.android.settings"); + + // verify that zen mode helper gets passed in a package name of "android" + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString()); + } + + @Test + public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception { + // The multi-user case: where the calling uid doesn't match the system uid, but the calling + // *appid* is the system. + mService.isSystemUid = false; + mService.isSystemAppId = true; + ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.setZenHelper(mockZenModeHelper); + ComponentName owner = new ComponentName("android", "ProviderName"); + ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build(); + boolean isEnabled = true; + AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); + mBinderService.addAutomaticZenRule(rule, "com.android.settings"); + + // verify that zen mode helper gets passed in a package name of "android" + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString()); + } + + @Test + public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception { + mService.isSystemUid = false; + mService.isSystemAppId = false; + ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.setZenHelper(mockZenModeHelper); + ComponentName owner = new ComponentName("android", "ProviderName"); + ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build(); + boolean isEnabled = true; + AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); + mBinderService.addAutomaticZenRule(rule, "another.package"); + + // verify that zen mode helper gets passed in the package name from the arg, not the owner + verify(mockZenModeHelper).addAutomaticZenRule( + eq("another.package"), eq(rule), anyString()); } @Test @@ -6124,8 +6546,9 @@ public void testGrantInlineReplyUriPermission_acrossUsers() throws Exception { waitForIdle(); // A notification exists for the given record - StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG); - assertEquals(1, notifsBefore.length); + List notifsBefore = + mBinderService.getAppActiveNotifications(PKG, nr.getSbn().getUserId()).getList(); + assertEquals(1, notifsBefore.size()); reset(mPackageManager); @@ -7043,4 +7466,108 @@ public void testCannotPostNonFgsWhenOverLimit() throws RemoteException { assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, mService.getNotificationRecordCount()); } -} + + @Test + public void testGetActiveNotification_filtersUsers() throws Exception { + when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0, 10}); + + NotificationRecord nr0 = + generateNotificationRecord(mTestNotificationChannel, 0); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0", + nr0.getSbn().getId(), nr0.getSbn().getNotification(), nr0.getSbn().getUserId()); + + NotificationRecord nr10 = + generateNotificationRecord(mTestNotificationChannel, 10); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag10", + nr10.getSbn().getId(), nr10.getSbn().getNotification(), nr10.getSbn().getUserId()); + + NotificationRecord nr11 = + generateNotificationRecord(mTestNotificationChannel, 11); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag11", + nr11.getSbn().getId(), nr11.getSbn().getNotification(), nr11.getSbn().getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(2, notifs.length); + for (StatusBarNotification sbn : notifs) { + if (sbn.getUserId() == 11) { + fail("leaked data across users"); + } + } + } + + @Test + public void testStrongAuthTracker_isInLockDownMode() { + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); + } + + @Test + public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() { + // post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // when entering the lockdown mode, cancel the 2 notifications. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); + + // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. + ArgumentCaptor captor = ArgumentCaptor.forClass(Integer.class); + verify(mListeners, times(2)).notifyRemovedLocked(any(), captor.capture(), any()); + assertEquals(REASON_CANCEL_ALL, captor.getValue().intValue()); + + // exit lockdown mode. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); + + // the notifyPostedLocked function is called twice. + verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); + } + + @Test + public void testMakeRankingUpdateLockedInLockDownMode() { + // post 2 notifications from a same package + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 1), mTestNotificationChannel); + mService.addNotification(pkgB); + + mService.setIsVisibleToListenerReturnValue(true); + NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); + + // when only user 0 entering the lockdown mode, its notification will be suppressed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(1, nru.getRankingMap().getOrderedKeys().length); + + // User 0 exits lockdown mode. Its notification will be resumed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); + } +} \ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 6aeb40dae20b..ffc12c94871e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -41,6 +41,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -2132,6 +2133,19 @@ public void testIsGroupBlocked_blocked() throws Exception { assertTrue(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); } + @Test + public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + + NotificationChannelGroup group3 = group.clone(); + group3.setBlocked(false); + mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true); + assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId())); + } + @Test public void testIsGroup_appCannotResetBlock() throws Exception { NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); @@ -3061,6 +3075,52 @@ public void testTooManyChannels_xml() throws Exception { assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true)); } + @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "\n" + + "\n" + + "" + + "" + + "" + + ""; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; @@ -3402,7 +3462,7 @@ public void testGetConversations_noConversations() { public void testGetConversations_noDisabledGroups() { NotificationChannelGroup group = new NotificationChannelGroup("a", "a"); group.setBlocked(true); - mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false); NotificationChannel parent = new NotificationChannel("parent", "p", 1); mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); 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 c2ead5f15ceb..2b6fbea5130a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.notification; +import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT; import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; import static junit.framework.Assert.assertEquals; @@ -66,6 +67,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) @@ -244,6 +246,37 @@ public void testScheduleRepostsForPersistedNotifications() throws Exception { assertEquals("key2", captor2.getValue().getIntent().getStringExtra(EXTRA_KEY)); } + @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 captor = ArgumentCaptor.forClass(PendingIntent.class); + verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture()); + assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length()); + + XmlSerializer serializer = Xml.newSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mSnoozeHelper.writeXml(serializer); + serializer.endDocument(); + serializer.flush(); + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser, 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); @@ -278,6 +311,22 @@ public void testSnooze() throws Exception { UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); } + @Test + public void testSnoozeLimit() { + for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++ ) { + NotificationRecord r = getNotificationRecord("pkg", i, i+"", UserHandle.SYSTEM); + + assertTrue("cannot snooze record " + i, mSnoozeHelper.canSnooze(1)); + + if (i % 2 == 0) { + mSnoozeHelper.snooze(r, null); + } else { + mSnoozeHelper.snooze(r, 9000); + } + } + assertFalse(mSnoozeHelper.canSnooze(1)); + } + @Test public void testCancelByApp() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); @@ -576,13 +625,20 @@ public void repostGroupSummary_repostsSummary() throws Exception { 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/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 013a99433041..684d8cfed5b6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -188,7 +188,7 @@ public void testRuleXml() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = new ComponentName("a", "a"); - rule.component = new ComponentName("a", "b"); + rule.component = new ComponentName("b", "b"); rule.conditionId = new Uri.Builder().scheme("hello").build(); rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE); rule.enabled = true; @@ -198,6 +198,7 @@ public void testRuleXml() throws Exception { rule.modified = true; rule.name = "name"; rule.snoozing = true; + rule.pkg = "b"; XmlSerializer out = new FastXmlSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -213,8 +214,7 @@ public void testRuleXml() throws Exception { new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); - // read from backing component - assertEquals("a", fromXml.pkg); + assertEquals("b", fromXml.pkg); // always resets on reboot assertFalse(fromXml.snoozing); //should all match original @@ -230,6 +230,55 @@ public void testRuleXml() throws Exception { assertEquals(rule.zenMode, fromXml.zenMode); } + @Test + public void testRuleXml_pkg_component() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + rule.component = new ComponentName("b", "b"); + + XmlSerializer out = new FastXmlSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertEquals("b", fromXml.pkg); + } + + @Test + public void testRuleXml_pkg_configActivity() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + + XmlSerializer out = new FastXmlSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertNull(fromXml.pkg); + } + private ZenModeConfig getMutedRingerConfig() { ZenModeConfig config = new ZenModeConfig(); // Allow alarms, media diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 3c7206fee9d1..1a1e3ddd968e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -41,9 +41,11 @@ import static com.android.os.AtomsProto.DNDModeProto.ID_FIELD_NUMBER; import static com.android.os.AtomsProto.DNDModeProto.UID_FIELD_NUMBER; import static com.android.os.AtomsProto.DNDModeProto.ZEN_MODE_FIELD_NUMBER; +import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @@ -70,7 +72,9 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.media.AudioAttributes; @@ -102,6 +106,8 @@ import com.android.server.UiServiceTestCase; import com.android.server.notification.ManagedServices.UserProfiles; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,9 +137,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; private static final int ZEN_MODE_FOR_TESTING = 99; + private static final String CUSTOM_PKG_NAME = "not.android"; + private static final int CUSTOM_PKG_UID = 1; ConditionProviders mConditionProviders; @Mock NotificationManager mNotificationManager; + @Mock PackageManager mPackageManager; private Resources mResources; private TestableLooper mTestableLooper; private ZenModeHelper mZenModeHelperSpy; @@ -143,7 +152,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory; @Before - public void setUp() { + public void setUp() throws PackageManager.NameNotFoundException { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); @@ -166,6 +175,16 @@ public void setUp() { mConditionProviders.addSystemProvider(new CountdownConditionProvider()); mZenModeHelperSpy = spy(new ZenModeHelper(mContext, mTestableLooper.getLooper(), mConditionProviders, mStatsEventBuilderFactory)); + + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + when(mPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(ri)); + when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt())) + .thenReturn(CUSTOM_PKG_UID); + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( + new String[] {getContext().getPackageName()}); + mZenModeHelperSpy.mPm = mPackageManager; } private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException { @@ -1543,7 +1562,7 @@ public void testAddAutomaticZenRule() { new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test"); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelperSpy.mConfig.automaticRules.get(id); @@ -1556,6 +1575,96 @@ public void testAddAutomaticZenRule() { assertEquals(zenRule.getName(), ruleInConfig.name); } + @Test + public void testAddAutomaticZenRule_beyondSystemLimit() { + for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) { + ScheduleInfo si = new ScheduleInfo(); + si.startHour = i; + AutomaticZenRule zenRule = new AutomaticZenRule("name" + i, + null, + new ComponentName("android", "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(si), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + // We need the package name to be something that's not "android" so there aren't any + // existing rules under that package. + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + assertNotNull(id); + } + try { + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName("android", "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + fail("allowed too many rules to be created"); + } catch (IllegalArgumentException e) { + // yay + } + } + + @Test + public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() { + // Make sure the system limit is enforced per-package even with different component provider + // names. + for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) { + ScheduleInfo si = new ScheduleInfo(); + si.startHour = i; + AutomaticZenRule zenRule = new AutomaticZenRule("name" + i, + null, + new ComponentName("android", "ScheduleConditionProvider" + i), + ZenModeConfig.toScheduleConditionId(si), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + assertNotNull(id); + } + try { + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName("android", "ScheduleConditionProviderFinal"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + fail("allowed too many rules to be created"); + } catch (IllegalArgumentException e) { + // yay + } + } + + @Test + public void testAddAutomaticZenRule_claimedSystemOwner() { + // Make sure anything that claims to have a "system" owner but not actually part of the + // system package still gets limited on number of rules + for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) { + ScheduleInfo si = new ScheduleInfo(); + si.startHour = i; + AutomaticZenRule zenRule = new AutomaticZenRule("name" + i, + new ComponentName("android", "ScheduleConditionProvider" + i), + null, // configuration activity + ZenModeConfig.toScheduleConditionId(si), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + assertNotNull(id); + } + try { + AutomaticZenRule zenRule = new AutomaticZenRule("name", + new ComponentName("android", "ScheduleConditionProviderFinal"), + null, // configuration activity + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + new ZenPolicy.Builder().build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test"); + fail("allowed too many rules to be created"); + } catch (IllegalArgumentException e) { + // yay + } + } + private void setupZenConfig() { mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index d7eedd990f04..4b7e4ec5809a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -277,4 +277,10 @@ public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc, public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) { return this; } + + @Override + public SurfaceControl.Transaction setTrustedOverlay(SurfaceControl sc, + boolean isTrustedOverlay) { + return this; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index ddaa586fae8a..3be17b8fac19 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -616,6 +616,7 @@ public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { // one above as finishing. final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + task.effectiveUid = activity0.getUid(); final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build(); activity1.finishing = true; new ActivityBuilder(mService).setTask(task).build(); @@ -650,6 +651,7 @@ public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { // Set relinquishTaskIdentity for all activities in the task final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + task.effectiveUid = activity0.getUid(); final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build(); activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; @@ -802,6 +804,7 @@ public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { // Make the current root activity relinquish task identity final ActivityRecord activity0 = task.getBottomMostActivity(); activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + task.effectiveUid = activity0.getUid(); // Add an extra activity on top - this will be the new root final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build(); // Add one more on top @@ -896,6 +899,47 @@ public void testUpdateEffectiveIntent_allFinishing() { verify(task).setIntent(eq(activity0)); } + /** + * Test {@link Task#updateEffectiveIntent()} when activity with relinquishTaskIdentity but + * another with different uid. This should make the task use the root activity when updating the + * intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingWithDifferentUid() { + final ActivityRecord activity0 = new ActivityBuilder(mService) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + + // Add an extra activity on top + new ActivityBuilder(mService).setUid(11).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity0)); + } + + /** + * Test {@link Task#updateEffectiveIntent()} with activities set as relinquishTaskIdentity. + * This should make the task use the topmost activity when updating the intent. + */ + @Test + public void testUpdateEffectiveIntent_relinquishingMultipleActivities() { + final ActivityRecord activity0 = new ActivityBuilder(mService) + .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); + final Task task = activity0.getTask(); + task.effectiveUid = activity0.getUid(); + // Add an extra activity on top + final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build(); + activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + // Add an extra activity on top + final ActivityRecord activity2 = new ActivityBuilder(mService).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity2)); + } + @Test public void testSaveLaunchingStateWhenConfigurationChanged() { LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister; diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index a5b676012ea9..c0b3feeaffc2 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -339,6 +339,7 @@ private void onUserUnlocked(int userId) { if (userId == UserHandle.USER_SYSTEM) { UsageStatsIdleService.scheduleUpdateMappingsJob(getContext()); } + final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId)); synchronized (mLock) { // Create a user unlocked event to report final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime()); @@ -356,7 +357,7 @@ private void onUserUnlocked(int userId) { boolean needToFlush = !pendingEvents.isEmpty(); initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(), - installedPackages); + installedPackages, deleteObsoleteData); mUserUnlockedStates.put(userId, true); final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId); if (userService == null) { @@ -551,13 +552,13 @@ private UserUsageStatsService getUserUsageStatsServiceLocked(int userId) { * when the user is initially unlocked. */ private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis, - HashMap installedPackages) { + HashMap installedPackages, boolean deleteObsoleteData) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this); try { - service.init(currentTimeMillis, installedPackages); + service.init(currentTimeMillis, installedPackages, deleteObsoleteData); mUserState.put(userId, service); } catch (Exception e) { if (mUserManager.isUserUnlocked(userId)) { @@ -1029,6 +1030,10 @@ private boolean pruneUninstalledPackagesData(int userId) { * Called by the Binder stub. */ private boolean updatePackageMappingsData() { + // don't update the mappings if a profile user is defined + if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) { + return true; // return true so job scheduler doesn't reschedule the job + } // fetch the installed packages outside the lock so it doesn't block package manager. final HashMap installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM); synchronized (mLock) { @@ -1154,6 +1159,13 @@ UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime, } } + private boolean shouldDeleteObsoleteData(UserHandle userHandle) { + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); + // If a profile owner is not defined for the given user, obsolete data should be deleted + return dpmInternal == null + || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null; + } + private String buildFullToken(String packageName, String token) { final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1); sb.append(packageName); @@ -2290,6 +2302,11 @@ public void setActiveAdminApps(Set packageNames, int userId) { mAppStandby.setActiveAdminApps(packageNames, userId); } + @Override + public void setAdminProtectedPackages(Set packageNames, int userId) { + mAppStandby.setAdminProtectedPackages(packageNames, userId); + } + @Override public void onAdminDataAvailable() { mAppStandby.onAdminDataAvailable(); @@ -2324,8 +2341,12 @@ public boolean updatePackageMappingsData() { private class MyPackageMonitor extends PackageMonitor { @Override public void onPackageRemoved(String packageName, int uid) { - mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName) - .sendToTarget(); + final int changingUserId = getChangingUserId(); + // Only remove the package's data if a profile owner is not defined for the user + if (shouldDeleteObsoleteData(UserHandle.of(changingUserId))) { + mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName) + .sendToTarget(); + } super.onPackageRemoved(packageName, uid); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 26de11af6f4e..0a5adc8e61d4 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -115,8 +115,9 @@ interface StatsUpdatedListener { mSystemTimeSnapshot = System.currentTimeMillis(); } - void init(final long currentTimeMillis, HashMap installedPackages) { - readPackageMappingsLocked(installedPackages); + void init(final long currentTimeMillis, HashMap installedPackages, + boolean deleteObsoleteData) { + readPackageMappingsLocked(installedPackages, deleteObsoleteData); mDatabase.init(currentTimeMillis); if (mDatabase.wasUpgradePerformed()) { mDatabase.prunePackagesDataOnUpgrade(installedPackages); @@ -180,12 +181,13 @@ int onPackageRemoved(String packageName, long timeRemoved) { return mDatabase.onPackageRemoved(packageName, timeRemoved); } - private void readPackageMappingsLocked(HashMap installedPackages) { + private void readPackageMappingsLocked(HashMap installedPackages, + boolean deleteObsoleteData) { mDatabase.readMappingsLocked(); // Package mappings for the system user are updated after 24 hours via a job scheduled by // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally, // this makes user service initialization a little quicker on subsequent boots. - if (mUserId != UserHandle.USER_SYSTEM) { + if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) { updatePackageMappingsLocked(installedPackages); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 0abab087b6d8..d70166f02ef9 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1094,6 +1094,9 @@ public KeyphraseMetadata getEnrolledKeyphraseMetadata(String keyphrase, @Override public ModuleProperties getDspModuleProperties() { + // Allow the call if it is granted CAPTURE_AUDIO_HOTWORD. + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1110,6 +1113,9 @@ public ModuleProperties getDspModuleProperties() { @Override public int startRecognition(int keyphraseId, String bcp47Locale, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { + // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD. + enforceAlwaysOnHotwordPermissions(); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1145,6 +1151,9 @@ public int startRecognition(int keyphraseId, String bcp47Locale, @Override public int stopRecognition(int keyphraseId, IRecognitionStatusCallback callback) { + // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD. + enforceAlwaysOnHotwordPermissions(); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1160,6 +1169,9 @@ public int stopRecognition(int keyphraseId, IRecognitionStatusCallback callback) @Override public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) { + // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD. + enforceAlwaysOnHotwordPermissions(); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1175,6 +1187,9 @@ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) @Override public int getParameter(int keyphraseId, @ModelParams int modelParam) { + // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD. + enforceAlwaysOnHotwordPermissions(); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1191,6 +1206,9 @@ public int getParameter(int keyphraseId, @ModelParams int modelParam) { @Override @Nullable public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) { + // Allow the call if it is granted RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD. + enforceAlwaysOnHotwordPermissions(); + // Allow the call if this is the current voice interaction service. synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1454,6 +1472,11 @@ private boolean isCallerHoldingPermission(String permission) { == PackageManager.PERMISSION_GRANTED; } + private void enforceAlwaysOnHotwordPermissions() { + enforceCallingPermission(Manifest.permission.RECORD_AUDIO); + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); + } + private void enforceCallingPermission(String permission) { if (!isCallerHoldingPermission(permission)) { throw new SecurityException("Caller does not hold the permission " + permission); diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 1f8aafbca476..77034041f1fd 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -21,12 +21,12 @@ import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telecom.IVideoProvider; + import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.android.internal.telecom.IVideoProvider; - /** * A parcelable representation of a conference connection. * @hide @@ -287,6 +287,14 @@ public int getCallDirection() { return mCallDirection; } + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator () { @Override diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 768c8eebf067..a85c85380fb7 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -483,6 +483,11 @@ public Builder setLabel(CharSequence label) { /** * Sets the address. See {@link PhoneAccount#getAddress}. + *

+ * 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. @@ -516,6 +521,10 @@ public Builder setCapabilities(int value) { /** * Sets the icon. See {@link PhoneAccount#getIcon}. + *

+ * 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. */ @@ -549,6 +558,10 @@ public Builder setShortDescription(CharSequence value) { /** * Specifies an additional URI scheme supported by the {@link PhoneAccount}. * + *

+ * 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. */ @@ -562,6 +575,12 @@ public Builder addSupportedUriScheme(String uriScheme) { /** * Specifies the URI schemes supported by the {@link PhoneAccount}. * + *

+ * 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. */ diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 762c93a49022..b7346331dc60 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -16,14 +16,19 @@ package android.telecom; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Binder; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; import java.util.Objects; @@ -33,7 +38,7 @@ public final class StatusHints implements Parcelable { private final CharSequence mLabel; - private final Icon mIcon; + private Icon mIcon; private final Bundle mExtras; /** @@ -48,10 +53,30 @@ public StatusHints(ComponentName packageName, CharSequence label, int iconResId, public StatusHints(CharSequence label, Icon icon, Bundle extras) { mLabel = label; - mIcon = icon; + mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle()); mExtras = extras; } + /** + * @param icon + * @hide + */ + @VisibleForTesting + public StatusHints(@Nullable Icon icon) { + mLabel = null; + mExtras = null; + mIcon = icon; + } + + /** + * + * @param icon + * @hide + */ + public void setIcon(@Nullable Icon icon) { + mIcon = icon; + } + /** * @return A package used to load the icon. * @@ -112,6 +137,30 @@ public int describeContents() { return 0; } + /** + * Validates the StatusHints image icon to see if it's not in the calling user space. + * Invalidates the icon if so, otherwise returns back the original icon. + * + * @param icon + * @return icon (validated) + * @hide + */ + public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) { + // Refer to Icon#getUriString for context. The URI string is invalid for icons of + // incompatible types. + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { + String encodedUser = icon.getUri().getEncodedUserInfo(); + // If there is no encoded user, the URI is calling into the calling user space + if (encodedUser != null) { + int userId = Integer.parseInt(encodedUser); + // Do not try to save the icon if the user id isn't in the calling user space. + if (userId != callingUserHandle.getIdentifier()) return null; + } + } + return icon; + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeCharSequence(mLabel); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 5bcf3f3158d1..19a88001428d 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1153,7 +1153,7 @@ public List getPhoneAccountsSupportingScheme(String uriSchem try { if (isServiceConnected()) { return getTelecomService().getPhoneAccountsSupportingScheme(uriScheme, - mContext.getOpPackageName()); + mContext.getOpPackageName()).getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getPhoneAccountsSupportingScheme", e); @@ -1196,7 +1196,7 @@ public List getSelfManagedPhoneAccounts() { try { if (isServiceConnected()) { return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(), - mContext.getAttributionTag()); + mContext.getAttributionTag()).getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e); @@ -1222,7 +1222,7 @@ public List getSelfManagedPhoneAccounts() { try { if (isServiceConnected()) { return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts, - mContext.getOpPackageName(), mContext.getAttributionTag()); + mContext.getOpPackageName(), mContext.getAttributionTag()).getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" + @@ -1242,7 +1242,8 @@ public List getSelfManagedPhoneAccounts() { public List getPhoneAccountsForPackage() { try { if (isServiceConnected()) { - return getTelecomService().getPhoneAccountsForPackage(mContext.getPackageName()); + return getTelecomService() + .getPhoneAccountsForPackage(mContext.getPackageName()).getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getPhoneAccountsForPackage", e); @@ -1296,7 +1297,7 @@ public int getAllPhoneAccountsCount() { public List getAllPhoneAccounts() { try { if (isServiceConnected()) { - return getTelecomService().getAllPhoneAccounts(); + return getTelecomService().getAllPhoneAccounts().getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccounts", e); @@ -1314,7 +1315,7 @@ public List getAllPhoneAccounts() { public List getAllPhoneAccountHandles() { try { if (isServiceConnected()) { - return getTelecomService().getAllPhoneAccountHandles(); + return getTelecomService().getAllPhoneAccountHandles().getList(); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccountHandles", e); @@ -1330,9 +1331,14 @@ public List getAllPhoneAccountHandles() { * when placing calls. The user may still need to enable the {@link PhoneAccount} within * the phone app settings before the account is usable. *

+ * Note: Each package is limited to 10 {@link PhoneAccount} registrations. + *

* A {@link SecurityException} will be thrown if an app tries to register a * {@link PhoneAccountHandle} where the package name specified within * {@link PhoneAccountHandle#getComponentName()} does not match the package name of the app. + *

+ * A {@link IllegalArgumentException} will be thrown if an app tries to register a + * {@link PhoneAccount} when the upper bound limit, 10, has already been reached. * * @param account The complete {@link PhoneAccount}. */ diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index dee5a98e33e9..d27093ad0133 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -23,6 +23,7 @@ import android.telecom.PhoneAccountHandle; import android.net.Uri; import android.os.Bundle; import android.telecom.PhoneAccount; +import android.content.pm.ParceledListSlice; /** * Interface used to interact with Telecom. Mostly this is used by TelephonyManager for passing @@ -56,25 +57,25 @@ interface ITelecomService { /** * @see TelecomServiceImpl#getCallCapablePhoneAccounts */ - List getCallCapablePhoneAccounts( + ParceledListSlice getCallCapablePhoneAccounts( boolean includeDisabledAccounts, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getSelfManagedPhoneAccounts */ - List getSelfManagedPhoneAccounts(String callingPackage, + ParceledListSlice getSelfManagedPhoneAccounts(String callingPackage, String callingFeatureId); /** * @see TelecomManager#getPhoneAccountsSupportingScheme */ - List getPhoneAccountsSupportingScheme(in String uriScheme, + ParceledListSlice getPhoneAccountsSupportingScheme(in String uriScheme, String callingPackage); /** * @see TelecomManager#getPhoneAccountsForPackage */ - List getPhoneAccountsForPackage(in String packageName); + ParceledListSlice getPhoneAccountsForPackage(in String packageName); /** * @see TelecomManager#getPhoneAccount @@ -89,12 +90,12 @@ interface ITelecomService { /** * @see TelecomManager#getAllPhoneAccounts */ - List getAllPhoneAccounts(); + ParceledListSlice getAllPhoneAccounts(); /** * @see TelecomManager#getAllPhoneAccountHandles */ - List getAllPhoneAccountHandles(); + ParceledListSlice getAllPhoneAccountHandles(); /** * @see TelecomServiceImpl#getSimCallManager diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 90d7a161767c..a8a7f36525a9 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -693,6 +694,15 @@ public boolean canManageSubscription(Context context, String packageName) { return merged.isEmpty() ? null : merged; } + /** + * @hide + * @return mCarrierConfigAccessRules associated with this subscription. + */ + public @NonNull List getCarrierConfigAccessRules() { + return mCarrierConfigAccessRules == null ? Collections.emptyList() : + Arrays.asList(mCarrierConfigAccessRules); + } + /** * Returns the card string of the SIM card which contains the subscription. * diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index d1d1780a25fd..a6509e3e9fe1 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -60,8 +60,42 @@ public final class PasspointConfiguration implements Parcelable { /** * Maximum bytes for URL string. + * @hide + */ + public static final int MAX_URL_BYTES = 2048; + + /** + * Maximum size for match entry, just to limit the size of the Passpoint config. + * @hide + */ + public static final int MAX_NUMBER_OF_ENTRIES = 16; + + /** + * Maximum size for OI entry. + * The spec allows a string of up to 255 characters, with comma delimited numbers like + * 001122,334455. So with minimum OI size of 7, the maximum amount of OIs is 36. + * @hide + */ + public static final int MAX_NUMBER_OF_OI = 36; + + + /** + * Maximum bytes for a string entry like FQDN and friendly name. + * @hide + */ + public static final int MAX_STRING_LENGTH = 255; + + /** + * HESSID is 48 bit. + * @hide + */ + public static final long MAX_HESSID_VALUE = ((long) 1 << 48) - 1; + + /** + * Organization Identifiers is 3 or 5 Octets. 24 or 36 bit. + * @hide */ - private static final int MAX_URL_BYTES = 1023; + public static final long MAX_OI_VALUE = ((long) 1 << 40) - 1; /** * Integer value used for indicating null value in the Parcel. @@ -760,7 +794,30 @@ private boolean validateForCommonR1andR2() { return false; } + if (mAaaServerTrustedNames != null) { + if (mAaaServerTrustedNames.length > MAX_NUMBER_OF_ENTRIES) { + Log.d(TAG, "Too many AaaServerTrustedNames"); + return false; + } + for (String fqdn : mAaaServerTrustedNames) { + if (fqdn.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.d(TAG, "AaaServerTrustedNames is too long"); + return false; + } + } + } + if (mSubscriptionType != null) { + if (mSubscriptionType.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.d(TAG, "SubscriptionType is too long"); + return false; + } + } + if (mTrustRootCertList != null) { + if (mTrustRootCertList.size() > MAX_NUMBER_OF_ENTRIES) { + Log.d(TAG, "Too many TrustRootCert"); + return false; + } for (Map.Entry entry : mTrustRootCertList.entrySet()) { String url = entry.getKey(); byte[] certFingerprint = entry.getValue(); @@ -785,6 +842,23 @@ private boolean validateForCommonR1andR2() { } } } + if (mServiceFriendlyNames != null) { + if (mServiceFriendlyNames.size() > MAX_NUMBER_OF_ENTRIES) { + Log.e(TAG, "ServiceFriendlyNames exceed the max!"); + return false; + } + for (Map.Entry names : mServiceFriendlyNames.entrySet()) { + if (names.getKey() == null || names.getValue() == null) { + Log.e(TAG, "Service friendly name entry should not be null"); + return false; + } + if (names.getKey().length() > MAX_STRING_LENGTH + || names.getValue().length() > MAX_STRING_LENGTH) { + Log.e(TAG, "Service friendly name is to long"); + return false; + } + } + } return true; } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 282757ac5a14..82ab29e6691b 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -16,6 +16,8 @@ package android.net.wifi.hotspot2.pps; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_STRING_LENGTH; + import android.net.wifi.EAPConstants; import android.net.wifi.ParcelUtil; import android.os.Parcel; @@ -413,7 +415,13 @@ public boolean validate() { + mPassword.getBytes(StandardCharsets.UTF_8).length); return false; } - + if (mSoftTokenApp != null) { + if (mSoftTokenApp.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.d(TAG, "app name exceeding maximum length: " + + mSoftTokenApp.getBytes(StandardCharsets.UTF_8).length); + return false; + } + } // Only supports EAP-TTLS for user credential. if (mEapType != EAPConstants.EAP_TTLS) { Log.d(TAG, "Invalid EAP Type for user credential: " + mEapType); diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java index 8f34579f6a5d..05a0ec913066 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java @@ -16,6 +16,13 @@ package android.net.wifi.hotspot2.pps; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_HESSID_VALUE; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_NUMBER_OF_ENTRIES; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_NUMBER_OF_OI; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_OI_VALUE; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_STRING_LENGTH; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_URL_BYTES; + import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -342,16 +349,86 @@ public boolean validate() { Log.d(TAG, "Missing FQDN"); return false; } + if (mFqdn.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.d(TAG, "FQDN is too long"); + return false; + } if (TextUtils.isEmpty(mFriendlyName)) { Log.d(TAG, "Missing friendly name"); return false; } + if (mFriendlyName.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.d(TAG, "Friendly name is too long"); + return false; + } // Verify SSIDs specified in the NetworkID if (mHomeNetworkIds != null) { + if (mHomeNetworkIds.size() > MAX_NUMBER_OF_ENTRIES) { + Log.d(TAG, "too many SSID in HomeNetworkIDs"); + return false; + } for (Map.Entry entry : mHomeNetworkIds.entrySet()) { if (entry.getKey() == null || entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { - Log.d(TAG, "Invalid SSID in HomeNetworkIDs"); + Log.d(TAG, "SSID is too long in HomeNetworkIDs"); + return false; + } + if (entry.getValue() != null + && (entry.getValue() > MAX_HESSID_VALUE || entry.getValue() < 0)) { + Log.d(TAG, "HESSID is out of range"); + return false; + } + } + } + if (mIconUrl != null && mIconUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) { + Log.d(TAG, "Icon URL is too long"); + return false; + } + if (mMatchAllOis != null) { + if (mMatchAllOis.length > MAX_NUMBER_OF_OI) { + Log.d(TAG, "too many match all Organization Identifiers in the profile"); + return false; + } + for (long oi : mMatchAllOis) { + if (oi > MAX_OI_VALUE || oi < 0) { + Log.d(TAG, "Organization Identifiers is out of range"); + return false; + } + } + } + if (mMatchAnyOis != null) { + if (mMatchAnyOis.length > MAX_NUMBER_OF_OI) { + Log.d(TAG, "too many match any Organization Identifiers in the profile"); + return false; + } + for (long oi : mMatchAnyOis) { + if (oi > MAX_OI_VALUE || oi < 0) { + Log.d(TAG, "Organization Identifiers is out of range"); + return false; + } + } + } + if (mRoamingConsortiumOis != null) { + if (mRoamingConsortiumOis.length > MAX_NUMBER_OF_OI) { + Log.d(TAG, "too many Roaming Consortium Organization Identifiers in the " + + "profile"); + return false; + } + for (long oi : mRoamingConsortiumOis) { + if (oi > MAX_OI_VALUE || oi < 0) { + Log.d(TAG, "Organization Identifiers is out of range"); + return false; + } + } + } + if (mOtherHomePartners != null) { + if (mOtherHomePartners.length > MAX_NUMBER_OF_ENTRIES) { + Log.d(TAG, "too many other home partners in the profile"); + return false; + } + for (String fqdn : mOtherHomePartners) { + if (fqdn.length() > MAX_STRING_LENGTH) { + Log.d(TAG, "FQDN is too long in OtherHomePartners"); return false; } } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java index b0a2cc397c53..4bdacebda060 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java @@ -16,6 +16,9 @@ package android.net.wifi.hotspot2.pps; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_NUMBER_OF_ENTRIES; +import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_STRING_LENGTH; + import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -269,11 +272,19 @@ public String toString() { */ public boolean validate() { if (TextUtils.isEmpty(mFqdn)) { - Log.d(TAG, "Missing FQDN"); + Log.e(TAG, "Missing FQDN"); + return false; + } + if (mFqdn.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.e(TAG, "FQDN is too long"); return false; } if (TextUtils.isEmpty(mCountries)) { - Log.d(TAG, "Missing countries"); + Log.e(TAG, "Missing countries"); + return false; + } + if (mCountries.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { + Log.e(TAG, "country is too long"); return false; } return true; @@ -449,7 +460,7 @@ public boolean validate() { } for (String ssid : mExcludedSsidList) { if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { - Log.d(TAG, "Invalid SSID: " + ssid); + Log.e(TAG, "Invalid SSID: " + ssid); return false; } } @@ -457,15 +468,24 @@ public boolean validate() { // Validate required protocol to port map. if (mRequiredProtoPortMap != null) { for (Map.Entry entry : mRequiredProtoPortMap.entrySet()) { + int protocol = entry.getKey(); + if (protocol < 0 || protocol > 255) { + Log.e(TAG, "Invalid IP protocol: " + protocol); + return false; + } String portNumber = entry.getValue(); if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) { - Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber); + Log.e(TAG, "PortNumber string bytes exceeded the max: " + portNumber); return false; } } } // Validate preferred roaming partner list. if (mPreferredRoamingPartnerList != null) { + if (mPreferredRoamingPartnerList.size() > MAX_NUMBER_OF_ENTRIES) { + Log.e(TAG, "Number of the Preferred Roaming Partner exceed the limit"); + return false; + } for (RoamingPartner partner : mPreferredRoamingPartnerList) { if (!partner.validate()) { return false; diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index 638efb9f14ee..59ffcd5ed169 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -41,7 +41,7 @@ */ @SmallTest public class PasspointConfigurationTest { - private static final int MAX_URL_BYTES = 1023; + private static final int MAX_URL_BYTES = 2048; private static final int CERTIFICATE_FINGERPRINT_BYTES = 32; /**