diff options
403 files changed, 11149 insertions, 4222 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index d4db7373504b..50d23ad2d657 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -190,7 +190,6 @@ droidstubs { droidstubs { name: "module-lib-api", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + module_libs, check_api: { @@ -222,7 +221,6 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + priv_apps + module_libs, } @@ -234,6 +232,9 @@ droidstubs { java_defaults { name: "framework-stubs-default", + libs: [ "stub-annotations" ], + static_libs: [ "private-stub-annotations-jar" ], + sdk_version: "core_current", errorprone: { javacflags: [ "-XepDisableAllChecks", @@ -249,61 +250,25 @@ java_defaults { java_library_static { name: "android_stubs_current", - srcs: [ - ":api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_system_stubs_current", - srcs: [ - ":system-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":system-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_test_stubs_current", - srcs: [ - ":test-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":test-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_module_lib_stubs_current", - srcs: [ - ":module-lib-api-stubs-docs", - ], - libs: [ - "stub-annotations", - "framework-all", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":module-lib-api-stubs-docs" ], defaults: ["framework-stubs-default"], } diff --git a/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java new file mode 100644 index 000000000000..14282bfbd24e --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2020 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.view; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.PathParser; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class CutoutSpecificationBenchmark { + private static final String TAG = "CutoutSpecificationBenchmark"; + + private static final String BOTTOM_MARKER = "@bottom"; + private static final String DP_MARKER = "@dp"; + private static final String RIGHT_MARKER = "@right"; + private static final String LEFT_MARKER = "@left"; + + private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"; + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Context mContext; + private DisplayMetrics mDisplayMetrics; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mDisplayMetrics = new DisplayMetrics(); + mContext.getDisplay().getRealMetrics(mDisplayMetrics); + } + + + private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + final RectF rectF = new RectF(); + p.computeBounds(rectF, false /* unused */); + rectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight, + float density) { + Path p = null; + Rect boundTop = null; + Rect boundBottom = null; + Rect safeInset = new Rect(); + String bottomSpec = null; + if (!TextUtils.isEmpty(spec)) { + spec = spec.trim(); + final float offsetX; + if (spec.endsWith(RIGHT_MARKER)) { + offsetX = displayWidth; + spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); + } else if (spec.endsWith(LEFT_MARKER)) { + offsetX = 0; + spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); + } else { + offsetX = displayWidth / 2f; + } + final boolean inDp = spec.endsWith(DP_MARKER); + if (inDp) { + spec = spec.substring(0, spec.length() - DP_MARKER.length()); + } + + if (spec.contains(BOTTOM_MARKER)) { + String[] splits = spec.split(BOTTOM_MARKER, 2); + spec = splits[0].trim(); + bottomSpec = splits[1].trim(); + } + + final Matrix m = new Matrix(); + final Region r = Region.obtain(); + if (!spec.isEmpty()) { + try { + p = PathParser.createPathFromPathData(spec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate cutout: ", e); + } + + if (p != null) { + if (inDp) { + m.postScale(density, density); + } + m.postTranslate(offsetX, 0); + p.transform(m); + + boundTop = new Rect(); + toRectAndAddToRegion(p, r, boundTop); + safeInset.top = boundTop.bottom; + } + } + + if (bottomSpec != null) { + int bottomInset = 0; + Path bottomPath = null; + try { + bottomPath = PathParser.createPathFromPathData(bottomSpec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate bottom cutout: ", e); + } + + if (bottomPath != null) { + // Keep top transform + m.postTranslate(0, displayHeight); + bottomPath.transform(m); + p.addPath(bottomPath); + boundBottom = new Rect(); + toRectAndAddToRegion(bottomPath, r, boundBottom); + bottomInset = displayHeight - boundBottom.top; + } + safeInset.bottom = bottomInset; + } + } + } + + @Test + public void parseByOldMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, mDisplayMetrics.density); + } + } + + @Test + public void parseByNewMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels) + .parse(DOUBLE_CUTOUT_SPEC); + } + } + + @Test + public void parseLongEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H -48\n" + + "V 48\n" + + "H 48\n" + + "Z\n" + + "@right\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } + + @Test + public void parseShortEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "H 48\n" + + "V -48\n" + + "H -48\n" + + "Z\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } +} diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index c458d1190e51..661f32f35cb2 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -670,7 +670,7 @@ public class UserLifecycleTests { private void startApp(int userId, String packageName) throws RemoteException { final Context context = InstrumentationRegistry.getContext(); final WaitResult result = ActivityTaskManager.getService().startActivityAndWait(null, - context.getPackageName(), context.getFeatureId(), + context.getPackageName(), context.getPackageManager().getLaunchIntentForPackage(packageName), null, null, null, 0, 0, null, null, userId); attestTrue("User " + userId + " failed to start " + packageName, diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 8cea645f4e71..821305e18240 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -142,6 +142,9 @@ public class BlobStoreManager { /** @hide */ public static final int COMMIT_RESULT_ERROR = 1; + /** @hide */ + public static final int INVALID_RES_ID = -1; + private final Context mContext; private final IBlobStoreManager mService; @@ -285,11 +288,65 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { try { - mService.acquireLease(blobHandle, descriptionResId, leaseExpiryTimeMillis, + mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis, + mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + * <p> This is variant of {@link #acquireLease(BlobHandle, int, long)} taking a + * {@link CharSequence} for {@code description}. It is highly recommended that callers only + * use this when a valid resource ID for {@code description} could not be provided. Otherwise, + * apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow + * {@code description} to be localized. + * + * <p> Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + * <p> This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to + * acquire a lease for. + * @param description a short description string that can be surfaced + * to the user explaining what the blob is used for. + * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be + * automatically released, in {@link System#currentTimeMillis()} + * timebase. If its value is {@code 0}, then the behavior of this + * API is identical to {@link #acquireLease(BlobHandle, int)} + * where clients have to explicitly call + * {@link #releaseLease(BlobHandle)} when they don't + * need the blob anymore. + * + * @throws IOException when there is an I/O error while acquiring a lease to the blob. + * @throws SecurityException when the blob represented by the {@code blobHandle} does not + * exist or the caller does not have access to it. + * @throws IllegalArgumentException when {@code blobHandle} is invalid or + * if the {@code leaseExpiryTimeMillis} is greater than the + * {@link BlobHandle#getExpiryTimeMillis()}. + * @throws IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description, + @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { + try { + mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); @@ -327,6 +384,7 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId) throws IOException { @@ -334,6 +392,47 @@ public class BlobStoreManager { } /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + * <p> This is variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence} + * for {@code description}. It is highly recommended that callers only use this when a valid + * resource ID for {@code description} could not be provided. Otherwise, apps should prefer + * using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be + * localized. + * + * <p> This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients + * don't have to specify the lease expiry time upfront using this API and need to explicitly + * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep + * a blob around. + * + * <p> Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + * <p> This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to + * acquire a lease for. + * @param description a short description string that can be surfaced + * to the user explaining what the blob is used for. + * + * @throws IOException when there is an I/O error while acquiring a lease to the blob. + * @throws SecurityException when the blob represented by the {@code blobHandle} does not + * exist or the caller does not have access to it. + * @throws IllegalArgumentException when {@code blobHandle} is invalid. + * @throws IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description) + throws IOException { + acquireLease(blobHandle, description, 0); + } + + /** * Release all active leases to the blob represented by {@code blobHandle} which are * currently held by the caller. * diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index e2128b421746..a85a25c9c4ad 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -26,8 +26,8 @@ interface IBlobStoreManager { ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName); void deleteSession(long sessionId, in String packageName); - void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, - in String packageName); + void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description, + long leaseTimeoutMillis, in String packageName); void releaseLease(in BlobHandle handle, in String packageName); void waitForIdle(in RemoteCallback callback); diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java index 803c9a40e5ea..9834d7477838 100644 --- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java +++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java @@ -52,4 +52,5 @@ public final class XmlTags { // For leasee public static final String TAG_LEASEE = "l"; public static final String ATTR_DESCRIPTION_RES_ID = "rid"; + public static final String ATTR_DESCRIPTION = "d"; } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index c12e0ec8aec9..c7d803c88410 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,6 +15,7 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_ID; import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; import static android.app.blob.XmlTags.ATTR_ID; @@ -28,12 +29,14 @@ import static android.app.blob.XmlTags.TAG_LEASEE; import static android.system.OsConstants.O_RDONLY; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.ResourceId; import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.RevocableFileDescriptor; @@ -141,11 +144,11 @@ class BlobMetadata { } } - void addLeasee(String callingPackage, int callingUid, - int descriptionResId, long leaseExpiryTimeMillis) { + void addLeasee(String callingPackage, int callingUid, int descriptionResId, + CharSequence description, long leaseExpiryTimeMillis) { synchronized (mMetadataLock) { mLeasees.add(new Leasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis)); + descriptionResId, description, leaseExpiryTimeMillis)); } } @@ -308,7 +311,7 @@ class BlobMetadata { } @Nullable - static BlobMetadata createFromXml(Context context, XmlPullParser in) + static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) throws XmlPullParserException, IOException { final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); @@ -321,12 +324,12 @@ class BlobMetadata { if (TAG_BLOB_HANDLE.equals(in.getName())) { blobHandle = BlobHandle.createFromXml(in); } else if (TAG_COMMITTER.equals(in.getName())) { - final Committer committer = Committer.createFromXml(in); + final Committer committer = Committer.createFromXml(in, version); if (committer != null) { committers.add(committer); } } else if (TAG_LEASEE.equals(in.getName())) { - leasees.add(Leasee.createFromXml(in)); + leasees.add(Leasee.createFromXml(in, version)); } } @@ -366,7 +369,7 @@ class BlobMetadata { } @Nullable - static Committer createFromXml(@NonNull XmlPullParser in) + static Committer createFromXml(@NonNull XmlPullParser in, int version) throws XmlPullParserException, IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); @@ -388,12 +391,15 @@ class BlobMetadata { static final class Leasee extends Accessor { public final int descriptionResId; + public final CharSequence description; public final long expiryTimeMillis; - Leasee(String packageName, int uid, int descriptionResId, long expiryTimeMillis) { + Leasee(String packageName, int uid, int descriptionResId, CharSequence description, + long expiryTimeMillis) { super(packageName, uid); this.descriptionResId = descriptionResId; this.expiryTimeMillis = expiryTimeMillis; + this.description = description; } boolean isStillValid() { @@ -401,18 +407,27 @@ class BlobMetadata { } void dump(Context context, IndentingPrintWriter fout) { + fout.println("desc: " + getDescriptionToDump(context)); + fout.println("expiryMs: " + expiryTimeMillis); + } + + private String getDescriptionToDump(Context context) { String desc = null; - try { - final Resources leaseeRes = context.getPackageManager() - .getResourcesForApplicationAsUser(packageName, UserHandle.getUserId(uid)); - desc = leaseeRes.getString(descriptionResId); - } catch (PackageManager.NameNotFoundException e) { - Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " - + packageName, e); - desc = "<none>"; + if (ResourceId.isValid(descriptionResId)) { + try { + final Resources leaseeRes = context.getPackageManager() + .getResourcesForApplicationAsUser( + packageName, UserHandle.getUserId(uid)); + desc = leaseeRes.getString(descriptionResId); + } catch (PackageManager.NameNotFoundException e) { + Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " + + packageName, e); + desc = "<none>"; + } + } else { + desc = description.toString(); } - fout.println("desc: " + desc); - fout.println("expiryMs: " + expiryTimeMillis); + return desc; } void writeToXml(@NonNull XmlSerializer out) throws IOException { @@ -420,16 +435,23 @@ class BlobMetadata { XmlUtils.writeIntAttribute(out, ATTR_UID, uid); XmlUtils.writeIntAttribute(out, ATTR_DESCRIPTION_RES_ID, descriptionResId); XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); + XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); } @NonNull - static Leasee createFromXml(@NonNull XmlPullParser in) throws IOException { + static Leasee createFromXml(@NonNull XmlPullParser in, int version) throws IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); final int descriptionResId = XmlUtils.readIntAttribute(in, ATTR_DESCRIPTION_RES_ID); final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); + final CharSequence description; + if (version >= XML_VERSION_ADD_STRING_DESC) { + description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); + } else { + description = null; + } - return new Leasee(packageName, uid, descriptionResId, expiryTimeMillis); + return new Leasee(packageName, uid, descriptionResId, description, expiryTimeMillis); } } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index ba2e559afdab..bcc1610435d9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -28,7 +28,12 @@ class BlobStoreConfig { public static final String TAG = "BlobStore"; public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); - public static final int CURRENT_XML_VERSION = 1; + // Initial version. + public static final int XML_VERSION_INIT = 1; + // Added a string variant of lease description. + public static final int XML_VERSION_ADD_STRING_DESC = 2; + + public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_STRING_DESC; private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 0ba34cab6560..1efdbda97fe5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -27,10 +27,10 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_NULL; -import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; @@ -324,7 +324,8 @@ public class BlobStoreManagerService extends SystemService { } private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, - long leaseExpiryTimeMillis, int callingUid, String callingPackage) { + CharSequence description, long leaseExpiryTimeMillis, + int callingUid, String callingPackage) { synchronized (mBlobsLock) { final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) .get(blobHandle); @@ -338,7 +339,7 @@ public class BlobStoreManagerService extends SystemService { "Lease expiry cannot be later than blobs expiry time"); } blobMetadata.addLeasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis); + descriptionResId, description, leaseExpiryTimeMillis); if (LOGV) { Slog.v(TAG, "Acquired lease on " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); @@ -450,7 +451,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_SESSIONS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { final LongSparseArray<BlobStoreSession> userSessions = @@ -491,6 +492,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_SESSIONS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -499,7 +501,7 @@ public class BlobStoreManagerService extends SystemService { if (TAG_SESSION.equals(in.getName())) { final BlobStoreSession session = BlobStoreSession.createFromXml( - in, mContext, mSessionStateChangeListener); + in, version, mContext, mSessionStateChangeListener); if (session == null) { continue; } @@ -539,7 +541,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_BLOBS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); @@ -579,6 +581,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_BLOBS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -586,7 +589,8 @@ public class BlobStoreManagerService extends SystemService { } if (TAG_BLOB.equals(in.getName())) { - final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); + final BlobMetadata blobMetadata = BlobMetadata.createFromXml( + in, version, mContext); final SparseArray<String> userPackages = allPackages.get( blobMetadata.getUserId()); if (userPackages == null) { @@ -1032,11 +1036,14 @@ public class BlobStoreManagerService extends SystemService { @Override public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, + @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { Objects.requireNonNull(blobHandle, "blobHandle must not be null"); blobHandle.assertIsValid(); - Preconditions.checkArgument(ResourceId.isValid(descriptionResId), - "descriptionResId is not valid"); + Preconditions.checkArgument( + ResourceId.isValid(descriptionResId) || description != null, + "Description must be valid; descriptionId=" + descriptionResId + + ", description=" + description); Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, "leaseExpiryTimeMillis must not be negative"); Objects.requireNonNull(packageName, "packageName must not be null"); @@ -1044,7 +1051,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis, + acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis, callingUid, packageName); } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index bd35b86babd8..80b42355ef7c 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -511,7 +511,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } @Nullable - static BlobStoreSession createFromXml(@NonNull XmlPullParser in, + static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version, @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) throws IOException, XmlPullParserException { final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID); diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index d515a8535180..80def475efb9 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -12,11 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +genrule { + name: "statslog-statsd-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + + " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", + out: ["com/android/internal/util/StatsdStatsLog.java"], +} + +java_library_static { + name: "statslog-statsd", + srcs: [ + ":statslog-statsd-java-gen", + ], +} + filegroup { name: "framework-statsd-sources", srcs: [ "java/**/*.java", ":statsd_java_aidl", + ":statslog-statsd-java-gen", ], } diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index 411482b88326..526d17ff0d71 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -32,7 +32,7 @@ import android.os.IStatsd; import android.os.RemoteException; import android.os.StatsFrameworkInitializer; import android.util.AndroidException; -import android.util.Slog; +import android.util.Log; import android.util.StatsEvent; import android.util.StatsEventParcel; @@ -155,7 +155,7 @@ public final class StatsManager { // can throw IllegalArgumentException service.addConfiguration(configKey, config, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding configuration"); + Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -191,7 +191,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); service.removeConfiguration(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when removing configuration"); + Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -258,7 +258,7 @@ public final class StatsManager { mContext.getOpPackageName()); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", + Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", e); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -311,7 +311,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when registering data listener."); + Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -348,7 +348,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager " + Log.e(TAG, "Failed to connect to statsmanager " + "when registering active configs listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -387,7 +387,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getData(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting data"); + Log.e(TAG, "Failed to connect to statsmanager when getting data"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -424,7 +424,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getMetadata(mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting metadata"); + Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -464,7 +464,7 @@ public final class StatsManager { return service.getRegisteredExperimentIds(); } catch (RemoteException e) { if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Failed to connect to StatsManagerService when getting " + "registered experiment IDs"); } @@ -555,7 +555,7 @@ public final class StatsManager { try { resultReceiver.pullFinished(atomTag, success, parcels); } catch (RemoteException e) { - Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); + Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); } }); } finally { diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java index 71d43599fc82..35273da96413 100644 --- a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java +++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java @@ -16,7 +16,7 @@ package android.os; import android.annotation.SystemApi; -import android.util.Slog; +import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -129,7 +129,7 @@ public final class StatsDimensionsValue implements Parcelable { mValue = values; break; default: - Slog.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType); + Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType); mValue = null; break; } @@ -155,7 +155,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == STRING_VALUE_TYPE) return (String) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return null; } @@ -169,7 +169,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -183,7 +183,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -198,7 +198,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return false; } @@ -212,7 +212,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -238,7 +238,7 @@ public final class StatsDimensionsValue implements Parcelable { } return copy; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); return null; } } @@ -297,7 +297,7 @@ public final class StatsDimensionsValue implements Parcelable { } return sb.toString(); } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return ""; } @@ -357,11 +357,11 @@ public final class StatsDimensionsValue implements Parcelable { return true; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return false; } } catch (ClassCastException e) { - Slog.w(TAG, "writeValue cast failed", e); + Log.w(TAG, "writeValue cast failed", e); return false; } } @@ -388,7 +388,7 @@ public final class StatsDimensionsValue implements Parcelable { return values; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return null; } } diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index e7659d88dd81..511bc01f521e 100644 --- a/apex/statsd/framework/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -26,10 +26,10 @@ import android.annotation.SystemApi; import android.content.Context; import android.os.IStatsd; import android.os.RemoteException; -import android.os.ServiceManager; +import android.os.StatsFrameworkInitializer; import android.util.proto.ProtoOutputStream; -import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.StatsdStatsLog; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to @@ -59,17 +59,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging start"); + Log.d(TAG, "Failed to find statsd when logging start"); } return false; } service.sendAppBreadcrumbAtom(label, - FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging start"); + Log.d(TAG, "Failed to connect to statsd when logging start"); } return false; } @@ -88,17 +88,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging stop"); + Log.d(TAG, "Failed to find statsd when logging stop"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging stop"); + Log.d(TAG, "Failed to connect to statsd when logging stop"); } return false; } @@ -117,17 +117,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); + Log.d(TAG, "Failed to find statsd when logging event"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging event"); + Log.d(TAG, "Failed to connect to statsd when logging event"); } return false; } @@ -162,7 +162,7 @@ public final class StatsLog { | EXPERIMENT_IDS_FIELD_ID, id); } - FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED, + StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED, trainName, trainVersionCode, (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, @@ -180,7 +180,10 @@ public final class StatsLog { if (sService != null) { return sService; } - sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats")); + sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer + .getStatsServiceManager() + .getStatsdServiceRegisterer() + .get()); return sService; } diff --git a/api/current.txt b/api/current.txt index 826d409b0a2d..475506a317bc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7574,7 +7574,9 @@ package android.app.blob { public class BlobStoreManager { method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int, long) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence, long) throws java.io.IOException; method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence) throws java.io.IOException; method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; method public void deleteSession(@IntRange(from=1) long) throws java.io.IOException; method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; @@ -12575,6 +12577,7 @@ package android.content.res { method public int getLayoutDirection(); method @NonNull public android.os.LocaleList getLocales(); method public boolean isLayoutSizeAtLeast(int); + method public boolean isNightModeActive(); method public boolean isScreenHdr(); method public boolean isScreenRound(); method public boolean isScreenWideColorGamut(); @@ -17032,6 +17035,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc + field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf field public static final int BIOMETRIC_SUCCESS = 0; // 0x0 } @@ -17066,6 +17070,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; // 0xe field public static final int BIOMETRIC_ERROR_NO_SPACE = 4; // 0x4 + field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf field public static final int BIOMETRIC_ERROR_TIMEOUT = 3; // 0x3 field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa @@ -31280,6 +31285,7 @@ package android.net.wifi { method public int getWifiState(); method public boolean is5GHzBandSupported(); method public boolean is6GHzBandSupported(); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isAutoWakeupEnabled(); method @Deprecated public boolean isDeviceToApRttSupported(); method public boolean isEasyConnectSupported(); method public boolean isEnhancedOpenSupported(); @@ -42599,6 +42605,7 @@ package android.security.keystore { method @NonNull public String getKeystoreAlias(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42633,9 +42640,10 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); - method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); + method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserPresenceRequired(boolean); } @@ -42652,6 +42660,7 @@ package android.security.keystore { method public int getOrigin(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isInsideSecureHardware(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42675,6 +42684,8 @@ package android.security.keystore { } public abstract class KeyProperties { + field public static final int AUTH_BIOMETRIC_STRONG = 2; // 0x2 + field public static final int AUTH_DEVICE_CREDENTIAL = 1; // 0x1 field public static final String BLOCK_MODE_CBC = "CBC"; field public static final String BLOCK_MODE_CTR = "CTR"; field public static final String BLOCK_MODE_ECB = "ECB"; @@ -42721,6 +42732,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42746,9 +42758,10 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); + method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int); method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); - method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); + method @Deprecated @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); method @NonNull public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean); } @@ -45426,7 +45439,6 @@ package android.telecom { field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 - field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800 field public static final int PROPERTY_RTT = 1024; // 0x400 @@ -45504,7 +45516,6 @@ package android.telecom { public abstract class Conference extends android.telecom.Conferenceable { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); - method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle); method public final void destroy(); method public final android.telecom.CallAudioState getCallAudioState(); method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); @@ -45519,8 +45530,6 @@ package android.telecom { method public final android.telecom.StatusHints getStatusHints(); method public android.telecom.Connection.VideoProvider getVideoProvider(); method public int getVideoState(); - method public final boolean isRingbackRequested(); - method public void onAnswer(int); method public void onCallAudioStateChanged(android.telecom.CallAudioState); method public void onConnectionAdded(android.telecom.Connection); method public void onDisconnect(); @@ -45529,7 +45538,6 @@ package android.telecom { method public void onMerge(android.telecom.Connection); method public void onMerge(); method public void onPlayDtmfTone(char); - method public void onReject(); method public void onSeparate(android.telecom.Connection); method public void onStopDtmfTone(); method public void onSwap(); @@ -45549,8 +45557,6 @@ package android.telecom { method public final void setDisconnected(android.telecom.DisconnectCause); method public final void setExtras(@Nullable android.os.Bundle); method public final void setOnHold(); - method public final void setRingbackRequested(boolean); - method public final void setRinging(); method public final void setStatusHints(android.telecom.StatusHints); method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider); method public final void setVideoState(android.telecom.Connection, int); @@ -45709,7 +45715,6 @@ package android.telecom { field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4 - field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400 @@ -46125,7 +46130,6 @@ package android.telecom { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); - method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); @@ -46153,7 +46157,6 @@ package android.telecom { method public void registerPhoneAccount(android.telecom.PhoneAccount); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); - method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"; field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -46680,7 +46683,6 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; - field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; diff --git a/api/system-current.txt b/api/system-current.txt index 24936d5784f8..a428d13fc6d9 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1451,16 +1451,16 @@ package android.app.usage { package android.bluetooth { public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile { - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothDevice getActiveDevice(); - method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@Nullable android.bluetooth.BluetoothDevice); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int getOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothCodecConfig); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsSupported(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothCodecConfig); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int supportsOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice, int); field public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; // 0x1 @@ -1471,10 +1471,10 @@ package android.bluetooth { public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile { method public void finalize(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); - field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; } public final class BluetoothAdapter { @@ -1647,9 +1647,9 @@ package android.bluetooth { } public class BluetoothPbap implements android.bluetooth.BluetoothProfile { - method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); - field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; } public interface BluetoothProfile { @@ -1922,18 +1922,22 @@ package android.content.integrity { public abstract class IntegrityFormula { method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...); method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long); method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula); - field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED; - field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE; + } + + public static final class IntegrityFormula.Application { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long); + } + + public static final class IntegrityFormula.Installer { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } public final class Rule implements android.os.Parcelable { @@ -2007,18 +2011,15 @@ package android.content.pm { } public final class InstallationFile implements android.os.Parcelable { - ctor public InstallationFile(@NonNull String, long, @Nullable byte[]); + ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]); method public int describeContents(); - method public int getFileType(); + method public long getLengthBytes(); + method public int getLocation(); method @Nullable public byte[] getMetadata(); method @NonNull public String getName(); - method public long getSize(); + method @Nullable public byte[] getSignature(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR; - field public static final int FILE_TYPE_APK = 0; // 0x0 - field public static final int FILE_TYPE_LIB = 1; // 0x1 - field public static final int FILE_TYPE_OBB = 2; // 0x2 - field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff } public final class InstantAppInfo implements android.os.Parcelable { @@ -4854,6 +4855,8 @@ package android.media.tv.tuner { public class Tuner implements java.lang.AutoCloseable { ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int); @@ -4874,8 +4877,6 @@ package android.media.tv.tuner { method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan(); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int); } @@ -7717,6 +7718,7 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData(); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int); @@ -8242,8 +8244,8 @@ package android.net.wifi.wificond { public final class NativeScanResult implements android.os.Parcelable { ctor public NativeScanResult(); method public int describeContents(); - method @NonNull public byte[] getBssid(); - method @NonNull public int getCapabilities(); + method @Nullable public android.net.MacAddress getBssid(); + method public int getCapabilities(); method public int getFrequencyMhz(); method @NonNull public byte[] getInformationElements(); method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos(); @@ -8252,15 +8254,31 @@ package android.net.wifi.wificond { method public long getTsf(); method public boolean isAssociated(); method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int BSS_CAPABILITY_APSD = 2048; // 0x800 + field public static final int BSS_CAPABILITY_CF_POLLABLE = 4; // 0x4 + field public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 8; // 0x8 + field public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 128; // 0x80 + field public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 16384; // 0x4000 + field public static final int BSS_CAPABILITY_DSSS_OFDM = 8192; // 0x2000 + field public static final int BSS_CAPABILITY_ESS = 1; // 0x1 + field public static final int BSS_CAPABILITY_IBSS = 2; // 0x2 + field public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 32768; // 0x8000 + field public static final int BSS_CAPABILITY_PBCC = 64; // 0x40 + field public static final int BSS_CAPABILITY_PRIVACY = 16; // 0x10 + field public static final int BSS_CAPABILITY_QOS = 512; // 0x200 + field public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 4096; // 0x1000 + field public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 32; // 0x20 + field public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 1024; // 0x400 + field public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 256; // 0x100 field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeScanResult> CREATOR; } public final class NativeWifiClient implements android.os.Parcelable { - ctor public NativeWifiClient(@NonNull byte[]); + ctor public NativeWifiClient(@Nullable android.net.MacAddress); method public int describeContents(); + method @Nullable public android.net.MacAddress getMacAddress(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeWifiClient> CREATOR; - field @NonNull public final byte[] macAddress; } public final class PnoNetwork implements android.os.Parcelable { @@ -8305,7 +8323,7 @@ package android.net.wifi.wificond { public class WifiCondManager { method public void abortScan(@NonNull String); method public void enableVerboseLogging(boolean); - method @NonNull public java.util.List<java.lang.Integer> getChannelsMhzForBand(int); + method @NonNull public int[] getChannelsMhzForBand(int); method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String); @@ -9680,7 +9698,6 @@ package android.provider { field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis"; - field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update"; field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled"; @@ -9695,7 +9712,7 @@ package android.provider { field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled"; field public static final String WIFI_SCORE_PARAMS = "wifi_score_params"; field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; - field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; + field @Deprecated public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { @@ -10317,7 +10334,7 @@ package android.service.dataloader { public abstract class DataLoaderService extends android.app.Service { ctor public DataLoaderService(); - method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(); + method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams); } public static interface DataLoaderService.DataLoader { diff --git a/api/test-current.txt b/api/test-current.txt index e352cb65156e..78a0b0a2046c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -812,18 +812,22 @@ package android.content.integrity { public abstract class IntegrityFormula { method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...); method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long); method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula); - field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED; - field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE; + } + + public static final class IntegrityFormula.Application { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long); + } + + public static final class IntegrityFormula.Installer { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } public final class Rule implements android.os.Parcelable { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5e2dbf37fb27..23f9f22c94f2 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -127,7 +127,7 @@ message Atom { WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; AnomalyDetected anomaly_detected = 46; AppBreadcrumbReported app_breadcrumb_reported = - 47 [(allow_from_any_uid) = true, (module) = "framework"]; + 47 [(allow_from_any_uid) = true, (module) = "statsd"]; AppStartOccurred app_start_occurred = 48 [(module) = "framework"]; AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; @@ -188,7 +188,7 @@ message Atom { ServiceStateChanged service_state_changed = 99 [(module) = "framework"]; ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"]; FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"]; - BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "framework"]; + BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"]; DevicePolicyEvent device_policy_event = 103 [(module) = "framework"]; DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"]; DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported = @@ -391,6 +391,7 @@ message Atom { WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"]; WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"]; WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"]; + AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; } @@ -7518,6 +7519,9 @@ message GrantPermissionsActivityButtonActions { // Button clicked by user - same as bit flags in buttons_presented with only single bit set optional int32 button_clicked = 5; + + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 6; } /** @@ -7956,6 +7960,10 @@ message SurfaceflingerStatsLayerInfo { // presentation, until the buffer was ready to be presented. optional FrameTimingHistogram post_to_acquire = 9 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Frames missed latch because the acquire fence didn't fire + optional int64 late_acquire_frames = 10; + // Frames latched early because the desired present time was bad + optional int64 bad_desired_present_frames = 11; } /** @@ -8366,3 +8374,28 @@ message SdkExtensionStatus { // "Failed" here can mean a symbol that wasn't meant to be visible was, or the other way around. optional int32 failed_call_symbol = 3; } + +/** + * Logs when an app is frozen or unfrozen. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java + */ +message AppFreezeChanged { + // The type of event. + enum Action { + UNKNOWN = 0; + FREEZE_APP = 1; + UNFREEZE_APP = 2; + } + optional Action action = 1; + + // Pid of the process being frozen. + optional int32 pid = 2; + + // Name of the process being frozen. + optional string process_name = 3; + + // Time since last unfrozen. + optional int64 time_unfrozen_millis = 4; +} diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java index d79740b49b3d..9912d2b1cc8b 100644 --- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java @@ -76,6 +76,16 @@ public final class AccessibilityShortcutInfo { private final int mDescriptionResId; /** + * Resource id of the animated image of the accessibility shortcut target. + */ + private final int mAnimatedImageRes; + + /** + * Resource id of the html description of the accessibility shortcut target. + */ + private final int mHtmlDescriptionRes; + + /** * Creates a new instance. * * @param context Context for accessing resources. @@ -119,6 +129,14 @@ public final class AccessibilityShortcutInfo { // Gets summary mSummaryResId = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0); + // Gets animated image + mAnimatedImageRes = asAttributes.getResourceId( + com.android.internal.R.styleable + .AccessibilityShortcutTarget_animatedImageDrawable, 0); + // Gets html description + mHtmlDescriptionRes = asAttributes.getResourceId( + com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription, + 0); asAttributes.recycle(); if (mDescriptionResId == 0 || mSummaryResId == 0) { @@ -172,6 +190,25 @@ public final class AccessibilityShortcutInfo { } /** + * The animated image resource id of the accessibility shortcut target. + * + * @return The animated image resource id. + */ + public int getAnimatedImageRes() { + return mAnimatedImageRes; + } + + /** + * The localized html description of the accessibility shortcut target. + * + * @return The localized html description. + */ + @Nullable + public String loadHtmlDescription(@NonNull PackageManager packageManager) { + return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes); + } + + /** * Gets string resource by the given activity and resource id. */ @Nullable diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 2319dd2c168f..642f51b6bb63 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5810,9 +5810,9 @@ public class Activity extends ContextThemeWrapper intent.prepareToLeaveProcess(this); result = ActivityTaskManager.getService() .startActivity(mMainThread.getApplicationThread(), getBasePackageName(), - getFeatureId(), intent, - intent.resolveTypeIfNeeded(getContentResolver()), mToken, mEmbeddedID, - requestCode, ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, options); + intent, intent.resolveTypeIfNeeded(getContentResolver()), mToken, + mEmbeddedID, requestCode, ActivityManager.START_FLAG_ONLY_IF_NEEDED, + null, options); } catch (RemoteException e) { // Empty } @@ -6606,8 +6606,8 @@ public class Activity extends ContextThemeWrapper try { data.prepareToLeaveProcess(this); IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null, getUserId()); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2838ad829f6b..7ee44053d4d5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4316,8 +4316,8 @@ public class ActivityManager { */ public static void broadcastStickyIntent(Intent intent, int appOp, int userId) { try { - getService().broadcastIntentWithFeature( - null, null, intent, null, null, Activity.RESULT_OK, null, null, + getService().broadcastIntent( + null, intent, null, null, Activity.RESULT_OK, null, null, null /*permission*/, appOp, null, false, true, userId); } catch (RemoteException ex) { } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ec110435d95c..c60f7bd29ce8 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -269,16 +269,13 @@ public abstract class ActivityManagerInternal { public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, long duration, String tag); - - public abstract int broadcastIntentInPackage(String packageName, @Nullable String featureId, - int uid, int realCallingUid, int realCallingPid, Intent intent, String resolvedType, - IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, - String requiredPermission, Bundle bOptions, boolean serialized, boolean sticky, - @UserIdInt int userId, boolean allowBackgroundActivityStarts); - + public abstract int broadcastIntentInPackage(String packageName, int uid, int realCallingUid, + int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle resultExtras, String requiredPermission, + Bundle bOptions, boolean serialized, boolean sticky, @UserIdInt int userId, + boolean allowBackgroundActivityStarts); public abstract ComponentName startServiceInPackage(int uid, Intent service, - String resolvedType, boolean fgRequired, String callingPackage, - @Nullable String callingFeatureId, @UserIdInt int userId, + String resolvedType, boolean fgRequired, String callingPackage, @UserIdInt int userId, boolean allowBackgroundActivityStarts) throws TransactionTooLargeException; public abstract void disconnectActivityFromServices(Object connectionHolder); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 6b5bfda92cd0..57cd8941a398 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1020,7 +1020,7 @@ class ContextImpl extends Context { public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { try { ActivityTaskManager.getService().startActivityAsUser( - mMainThread.getApplicationThread(), getBasePackageName(), getFeatureId(), intent, + mMainThread.getApplicationThread(), getBasePackageName(), intent, intent.resolveTypeIfNeeded(getContentResolver()), null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, options, user.getIdentifier()); @@ -1102,8 +1102,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { @@ -1119,8 +1119,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { @@ -1134,8 +1134,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { @@ -1149,8 +1149,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, null, false, false, user.getIdentifier()); } catch (RemoteException e) { @@ -1166,8 +1166,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, options, false, false, getUserId()); } catch (RemoteException e) { @@ -1183,8 +1183,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false, false, getUserId()); } catch (RemoteException e) { @@ -1200,8 +1200,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, null, true, false, getUserId()); } catch (RemoteException e) { @@ -1263,8 +1263,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, rd, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermissions, appOp, options, true, false, getUserId()); } catch (RemoteException e) { @@ -1277,10 +1277,9 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false, - user.getIdentifier()); + ActivityManager.getService().broadcastIntent(mMainThread.getApplicationThread(), + intent, resolvedType, null, Activity.RESULT_OK, null, null, null, + AppOpsManager.OP_NONE, null, false, false, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1300,8 +1299,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, options, false, false, user.getIdentifier()); } catch (RemoteException e) { @@ -1317,8 +1316,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false, false, user.getIdentifier()); } catch (RemoteException e) { @@ -1368,8 +1367,8 @@ class ContextImpl extends Context { : new String[] {receiverPermission}; try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, rd, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermissions, appOp, options, true, false, user.getIdentifier()); } catch (RemoteException e) { @@ -1409,8 +1408,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, getUserId()); } catch (RemoteException e) { @@ -1445,8 +1444,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, rd, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null, true, true, getUserId()); } catch (RemoteException e) { @@ -1477,8 +1476,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier()); } catch (RemoteException e) { @@ -1492,8 +1491,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, null, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, false, true, user.getIdentifier()); } catch (RemoteException e) { @@ -1527,8 +1526,8 @@ class ContextImpl extends Context { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(this); - ActivityManager.getService().broadcastIntentWithFeature( - mMainThread.getApplicationThread(), getFeatureId(), intent, resolvedType, rd, + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null, true, true, user.getIdentifier()); } catch (RemoteException e) { @@ -1613,9 +1612,9 @@ class ContextImpl extends Context { } } try { - final Intent intent = ActivityManager.getService().registerReceiverWithFeature( - mMainThread.getApplicationThread(), mBasePackageName, getFeatureId(), rd, - filter, broadcastPermission, userId, flags); + final Intent intent = ActivityManager.getService().registerReceiver( + mMainThread.getApplicationThread(), mBasePackageName, rd, filter, + broadcastPermission, userId, flags); if (intent != null) { intent.setExtrasClassLoader(getClassLoader()); intent.prepareToEnterProcess(); @@ -1688,9 +1687,9 @@ class ContextImpl extends Context { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( - mMainThread.getApplicationThread(), service, - service.resolveTypeIfNeeded(getContentResolver()), requireForeground, - getOpPackageName(), getFeatureId(), user.getIdentifier()); + mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( + getContentResolver()), requireForeground, + getOpPackageName(), user.getIdentifier()); if (cn != null) { if (cn.getPackageName().equals("!")) { throw new SecurityException( diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 83fa9d7ea796..cb6a476fb617 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -104,38 +104,25 @@ interface IActivityManager { // Special low-level communication with activity manager. void handleApplicationCrash(in IBinder app, in ApplicationErrorReport.ParcelableCrashInfo crashInfo); - /** @deprecated Use {@link #startActivityWithFeature} instead */ - @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#startActivity(android.content.Intent)} instead") + @UnsupportedAppUsage int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options); - int startActivityWithFeature(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, int flags, - in ProfilerInfo profilerInfo, in Bundle options); @UnsupportedAppUsage void unhandledBack(); @UnsupportedAppUsage boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask); - @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter)} instead") + @UnsupportedAppUsage Intent registerReceiver(in IApplicationThread caller, in String callerPackage, in IIntentReceiver receiver, in IntentFilter filter, in String requiredPermission, int userId, int flags); - Intent registerReceiverWithFeature(in IApplicationThread caller, in String callerPackage, - in String callingFeatureId, in IIntentReceiver receiver, in IntentFilter filter, - in String requiredPermission, int userId, int flags); @UnsupportedAppUsage void unregisterReceiver(in IIntentReceiver receiver); - /** @deprecated Use {@link #broadcastIntentWithFeature} instead */ - @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#sendBroadcast(android.content.Intent)} instead") + @UnsupportedAppUsage int broadcastIntent(in IApplicationThread caller, in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, in String resultData, in Bundle map, in String[] requiredPermissions, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); - int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId, - in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, - in String resultData, in Bundle map, in String[] requiredPermissions, - int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId); @UnsupportedAppUsage oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map, @@ -158,8 +145,7 @@ interface IActivityManager { boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta); PendingIntent getRunningServiceControlPanel(in ComponentName service); ComponentName startService(in IApplicationThread caller, in Intent service, - in String resolvedType, boolean requireForeground, in String callingPackage, - in String callingFeatureId, int userId); + in String resolvedType, boolean requireForeground, in String callingPackage, int userId); @UnsupportedAppUsage int stopService(in IApplicationThread caller, in Intent service, in String resolvedType, int userId); @@ -240,14 +226,10 @@ interface IActivityManager { ParceledListSlice getRecentTasks(int maxNum, int flags, int userId); @UnsupportedAppUsage oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res); - /** @deprecated Use {@link #getIntentSenderWithFeature} instead */ - @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link PendingIntent#getIntentSender()} instead") + @UnsupportedAppUsage IIntentSender getIntentSender(int type, in String packageName, in IBinder token, in String resultWho, int requestCode, in Intent[] intents, in String[] resolvedTypes, int flags, in Bundle options, int userId); - IIntentSender getIntentSenderWithFeature(int type, in String packageName, in String featureId, - in IBinder token, in String resultWho, int requestCode, in Intent[] intents, - in String[] resolvedTypes, int flags, in Bundle options, int userId); void cancelIntentSender(in IIntentSender sender); String getPackageForIntentSender(in IIntentSender sender); void registerIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); @@ -373,16 +355,11 @@ interface IActivityManager { boolean isIntentSenderAnActivity(in IIntentSender sender); boolean isIntentSenderAForegroundService(in IIntentSender sender); boolean isIntentSenderABroadcast(in IIntentSender sender); - /** @deprecated Use {@link startActivityAsUserWithFeature} instead */ - @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@code android.content.Context#createContextAsUser(android.os.UserHandle, int)} and {@link android.content.Context#startActivity(android.content.Intent)} instead") + @UnsupportedAppUsage int startActivityAsUser(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, int userId); - int startActivityAsUserWithFeature(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, int flags, - in ProfilerInfo profilerInfo, in Bundle options, int userId); @UnsupportedAppUsage int stopUser(int userid, boolean force, in IStopUserCallback callback); /** diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 180507cd7e9c..be2f144c2fe8 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -85,17 +85,16 @@ import java.util.List; * {@hide} */ interface IActivityTaskManager { - int startActivity(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, + int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent, + in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options); int startActivities(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent[] intents, in String[] resolvedTypes, - in IBinder resultTo, in Bundle options, int userId); + in Intent[] intents, in String[] resolvedTypes, in IBinder resultTo, + in Bundle options, int userId); int startActivityAsUser(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, int flags, - in ProfilerInfo profilerInfo, in Bundle options, int userId); + in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, + int requestCode, int flags, in ProfilerInfo profilerInfo, + in Bundle options, int userId); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); int startActivityIntentSender(in IApplicationThread caller, @@ -103,19 +102,19 @@ interface IActivityTaskManager { in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flagsMask, int flagsValues, in Bundle options); WaitResult startActivityAndWait(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, int flags, - in ProfilerInfo profilerInfo, in Bundle options, int userId); + in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, + int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, + int userId); int startActivityWithConfig(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, in String resolvedType, - in IBinder resultTo, in String resultWho, int requestCode, int startFlags, - in Configuration newConfig, in Bundle options, int userId); - int startVoiceActivity(in String callingPackage, in String callingFeatureId, int callingPid, - int callingUid, in Intent intent, in String resolvedType, - in IVoiceInteractionSession session, in IVoiceInteractor interactor, int flags, - in ProfilerInfo profilerInfo, in Bundle options, int userId); - int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid, - int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); + in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, + int requestCode, int startFlags, in Configuration newConfig, + in Bundle options, int userId); + int startVoiceActivity(in String callingPackage, int callingPid, int callingUid, + in Intent intent, in String resolvedType, in IVoiceInteractionSession session, + in IVoiceInteractor interactor, int flags, in ProfilerInfo profilerInfo, + in Bundle options, int userId); + int startAssistantActivity(in String callingPackage, int callingPid, int callingUid, + in Intent intent, in String resolvedType, in Bundle options, int userId); void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver, in IRecentsAnimationRunner recentsAnimationRunner); int startActivityFromRecents(int taskId, in Bundle options); diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl index f41d705e2ba7..3ce71908bbfd 100644 --- a/core/java/android/app/IAppTask.aidl +++ b/core/java/android/app/IAppTask.aidl @@ -27,7 +27,7 @@ interface IAppTask { @UnsupportedAppUsage ActivityManager.RecentTaskInfo getTaskInfo(); void moveToFront(in IApplicationThread appThread, in String callingPackage); - int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, + int startActivity(IBinder whoThread, String callingPackage, in Intent intent, String resolvedType, in Bundle options); void setExcludeFromRecents(boolean exclude); } diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 5d5956e4dca4..9d6c3d64aaf2 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -52,7 +52,11 @@ interface ITaskOrganizerController { boolean deleteRootTask(IWindowContainer task); /** Gets direct child tasks (ordered from top-to-bottom) */ - List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent); + List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent, + in int[] activityTypes); + + /** Gets all root tasks on a display (ordered from top-to-bottom) */ + List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes); /** Get the root task which contains the current ime target */ IWindowContainer getImeTarget(int display); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 18932c6b0784..62c905d8c2c6 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1721,7 +1721,7 @@ public class Instrumentation { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService() - .startActivity(whoThread, who.getBasePackageName(), who.getFeatureId(), intent, + .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); @@ -1794,8 +1794,8 @@ public class Instrumentation { resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); } int result = ActivityTaskManager.getService() - .startActivities(whoThread, who.getBasePackageName(), who.getFeatureId(), intents, - resolvedTypes, token, options, userId); + .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes, + token, options, userId); checkStartActivityResult(result, intents[0]); return result; } catch (RemoteException e) { @@ -1861,7 +1861,7 @@ public class Instrumentation { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService() - .startActivity(whoThread, who.getBasePackageName(), who.getFeatureId(), intent, + .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target, requestCode, 0, null, options); checkStartActivityResult(result, intent); @@ -1928,8 +1928,8 @@ public class Instrumentation { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService() - .startActivityAsUser(whoThread, who.getBasePackageName(), who.getFeatureId(), - intent, intent.resolveTypeIfNeeded(who.getContentResolver()), + .startActivityAsUser(whoThread, who.getBasePackageName(), intent, + intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho, requestCode, 0, null, options, user.getIdentifier()); checkStartActivityResult(result, intent); @@ -2022,8 +2022,7 @@ public class Instrumentation { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = appTask.startActivity(whoThread.asBinder(), who.getBasePackageName(), - who.getFeatureId(), intent, - intent.resolveTypeIfNeeded(who.getContentResolver()), options); + intent, intent.resolveTypeIfNeeded(who.getContentResolver()), options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index f68c929144c8..b8348c7d4f22 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -354,8 +354,8 @@ public final class PendingIntent implements Parcelable { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(context); IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, options, context.getUserId()); @@ -380,8 +380,8 @@ public final class PendingIntent implements Parcelable { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(context); IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, options, user.getIdentifier()); @@ -497,8 +497,8 @@ public final class PendingIntent implements Parcelable { } try { IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, intents, resolvedTypes, flags, options, context.getUserId()); return target != null ? new PendingIntent(target) : null; @@ -523,8 +523,8 @@ public final class PendingIntent implements Parcelable { } try { IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, intents, resolvedTypes, flags, options, user.getIdentifier()); return target != null ? new PendingIntent(target) : null; @@ -575,8 +575,8 @@ public final class PendingIntent implements Parcelable { try { intent.prepareToLeaveProcess(context); IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_BROADCAST, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + ActivityManager.INTENT_SENDER_BROADCAST, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, null, userHandle.getIdentifier()); @@ -654,8 +654,8 @@ public final class PendingIntent implements Parcelable { try { intent.prepareToLeaveProcess(context); IIntentSender target = - ActivityManager.getService().getIntentSenderWithFeature( - serviceKind, packageName, context.getFeatureId(), + ActivityManager.getService().getIntentSender( + serviceKind, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, null, context.getUserId()); diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index bfd966c575a9..67d94dec88c5 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -247,6 +247,14 @@ public final class PictureInPictureParams implements Parcelable { return mSourceRectHint != null && !mSourceRectHint.isEmpty(); } + /** + * @return True if no parameters are set + * @hide + */ + public boolean empty() { + return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio(); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 662ca6eb2c19..f7d712dbd4fb 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -149,6 +149,13 @@ public class TaskInfo { public IWindowContainer token; /** + * The PictureInPictureParams for the Task, if set. + * @hide + */ + @Nullable + public PictureInPictureParams pictureInPictureParams; + + /** * The activity type of the top activity in this task. * @hide */ @@ -209,6 +216,9 @@ public class TaskInfo { configuration.readFromParcel(source); token = IWindowContainer.Stub.asInterface(source.readStrongBinder()); topActivityType = source.readInt(); + pictureInPictureParams = source.readInt() != 0 + ? PictureInPictureParams.CREATOR.createFromParcel(source) + : null; } /** @@ -246,6 +256,12 @@ public class TaskInfo { configuration.writeToParcel(dest, flags); dest.writeStrongInterface(token); dest.writeInt(topActivityType); + if (pictureInPictureParams == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + pictureInPictureParams.writeToParcel(dest, flags); + } } @Override @@ -261,6 +277,7 @@ public class TaskInfo { + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow + " resizeMode=" + resizeMode + " token=" + token - + " topActivityType=" + topActivityType; + + " topActivityType=" + topActivityType + + " pictureInPictureParams=" + pictureInPictureParams; } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 759979100483..dc15b51a442c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8951,7 +8951,8 @@ public class DevicePolicyManager { * * <strong>Note: Starting from Android R, apps should no longer call this method with the * setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead, - * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. + * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. This will be + * enforced for all apps targeting Android R or above. * </strong> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -8961,6 +8962,7 @@ public class DevicePolicyManager { */ public void setSecureSetting(@NonNull ComponentName admin, String setting, String value) { throwIfParentInstance("setSecureSetting"); + if (mService != null) { try { mService.setSecureSetting(admin, setting, value); @@ -9462,16 +9464,6 @@ public class DevicePolicyManager { * {@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. * - * NOTE: Starting from Android R, location-related permissions cannot be granted by the - * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the - * following permissions will return false: - * - * <ul> - * <li>{@code ACCESS_FINE_LOCATION}</li> - * <li>{@code ACCESS_BACKGROUND_LOCATION}</li> - * <li>{@code ACCESS_COARSE_LOCATION}</li> - * </ul> - * * @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. diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 5b367894ce69..e289a2775b79 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.Compatibility; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -70,11 +71,14 @@ public final class CompatChanges { @NonNull UserHandle user) { IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + final long token = Binder.clearCallingIdentity(); try { return platformCompat.isChangeEnabledByPackageName(changeId, packageName, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(token); } } @@ -99,10 +103,13 @@ public final class CompatChanges { public static boolean isChangeEnabled(long changeId, int uid) { IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + final long token = Binder.clearCallingIdentity(); try { return platformCompat.isChangeEnabledByUid(changeId, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/core/java/android/app/compat/TEST_MAPPING b/core/java/android/app/compat/TEST_MAPPING new file mode 100644 index 000000000000..c047df514e8d --- /dev/null +++ b/core/java/android/app/compat/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/services/compat" + } + ] +}
\ No newline at end of file diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java index e5e06f859ac6..876bafdfb7d1 100644 --- a/core/java/android/app/prediction/AppPredictionSessionId.java +++ b/core/java/android/app/prediction/AppPredictionSessionId.java @@ -22,6 +22,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The id for an app prediction session. See {@link AppPredictor}. * @@ -32,18 +34,28 @@ import android.os.Parcelable; public final class AppPredictionSessionId implements Parcelable { private final String mId; + private final int mUserId; /** * Creates a new id for a prediction session. * * @hide */ - public AppPredictionSessionId(@NonNull String id) { + public AppPredictionSessionId(@NonNull final String id, final int userId) { mId = id; + mUserId = userId; } private AppPredictionSessionId(Parcel p) { mId = p.readString(); + mUserId = p.readInt(); + } + + /** + * @hide + */ + public int getUserId() { + return mUserId; } @Override @@ -51,17 +63,17 @@ public final class AppPredictionSessionId implements Parcelable { if (!getClass().equals(o != null ? o.getClass() : null)) return false; AppPredictionSessionId other = (AppPredictionSessionId) o; - return mId.equals(other.mId); + return mId.equals(other.mId) && mUserId == other.mUserId; } @Override public @NonNull String toString() { - return mId; + return mId + "," + mUserId; } @Override public int hashCode() { - return mId.hashCode(); + return Objects.hash(mId, mUserId); } @Override @@ -72,6 +84,7 @@ public final class AppPredictionSessionId implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mId); + dest.writeInt(mUserId); } public static final @android.annotation.NonNull Parcelable.Creator<AppPredictionSessionId> CREATOR = diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java index cd635d635ce1..f0eedf3d18f2 100644 --- a/core/java/android/app/prediction/AppPredictor.java +++ b/core/java/android/app/prediction/AppPredictor.java @@ -96,7 +96,7 @@ public final class AppPredictor { IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE); mPredictionManager = IPredictionManager.Stub.asInterface(b); mSessionId = new AppPredictionSessionId( - context.getPackageName() + ":" + UUID.randomUUID().toString()); + context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId()); try { mPredictionManager.createPredictionSession(predictionContext, mSessionId); } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index d8c653c6d0d5..b672a0857cca 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -643,8 +643,9 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @Nullable @RequiresPermission(Manifest.permission.BLUETOOTH) - public BluetoothCodecStatus getCodecStatus(@Nullable BluetoothDevice device) { + public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); + verifyDeviceNotNull(device, "getCodecStatus"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { @@ -670,9 +671,14 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void setCodecConfigPreference(@Nullable BluetoothDevice device, - @Nullable BluetoothCodecConfig codecConfig) { + public void setCodecConfigPreference(@NonNull BluetoothDevice device, + @NonNull BluetoothCodecConfig codecConfig) { if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); + verifyDeviceNotNull(device, "setCodecConfigPreference"); + if (codecConfig == null) { + Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); + throw new IllegalArgumentException("codecConfig cannot be null"); + } try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { @@ -695,8 +701,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void enableOptionalCodecs(@Nullable BluetoothDevice device) { + public void enableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); + verifyDeviceNotNull(device, "enableOptionalCodecs"); enableDisableOptionalCodecs(device, true); } @@ -709,8 +716,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void disableOptionalCodecs(@Nullable BluetoothDevice device) { + public void disableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); + verifyDeviceNotNull(device, "disableOptionalCodecs"); enableDisableOptionalCodecs(device, false); } @@ -750,7 +758,8 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) @OptionalCodecsSupportStatus - public int supportsOptionalCodecs(@Nullable BluetoothDevice device) { + public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { + verifyDeviceNotNull(device, "isOptionalCodecsSupported"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -775,7 +784,8 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) @OptionalCodecsPreferenceStatus - public int getOptionalCodecsEnabled(@Nullable BluetoothDevice device) { + public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { + verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -800,8 +810,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void setOptionalCodecsEnabled(@Nullable BluetoothDevice device, + public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) { + verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); try { if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED @@ -854,6 +865,13 @@ public final class BluetoothA2dp implements BluetoothProfile { return false; } + private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { + if (device == null) { + Log.e(TAG, methodName + ": device param is null"); + throw new IllegalArgumentException("Device cannot be null"); + } + } + private boolean isValidDevice(BluetoothDevice device) { if (device == null) return false; diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index ee2cc6d14712..ab492309ba3d 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -66,7 +66,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ @SystemApi @SuppressLint("ActionValue") - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; @@ -296,7 +296,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -345,7 +345,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothA2dpSink service = getService(); @@ -370,7 +370,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable BluetoothDevice device) { final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 01ccb86fb129..66bfcbd27ca6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -26,6 +26,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.ActivityThread; +import android.app.PropertyInvalidatedCache; import android.bluetooth.BluetoothProfile.ConnectionPolicy; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; @@ -994,6 +995,37 @@ public final class BluetoothAdapter { return false; } + private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state"; + + private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache = + new PropertyInvalidatedCache<Void, Integer>( + 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { + @Override + protected Integer recompute(Void query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getState(); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + return BluetoothAdapter.STATE_OFF; + } + }; + + /** @hide */ + public void disableBluetoothGetStateCache() { + mBluetoothGetStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateBluetoothGetStateCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY); + } + /** * Get the current state of the local Bluetooth adapter. * <p>Possible return values are @@ -1007,18 +1039,7 @@ public final class BluetoothAdapter { @RequiresPermission(Manifest.permission.BLUETOOTH) @AdapterState public int getState() { - int state = BluetoothAdapter.STATE_OFF; - - try { - mServiceLock.readLock().lock(); - if (mService != null) { - state = mService.getState(); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } + int state = mBluetoothGetStateCache.query(null); // Consider all internal states as OFF if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON @@ -1056,18 +1077,7 @@ public final class BluetoothAdapter { @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " + "whether you can use BLE & BT classic.") public int getLeState() { - int state = BluetoothAdapter.STATE_OFF; - - try { - mServiceLock.readLock().lock(); - if (mService != null) { - state = mService.getState(); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } + int state = mBluetoothGetStateCache.query(null); if (VDBG) { Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state)); @@ -1960,6 +1970,38 @@ public final class BluetoothAdapter { } } + private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY = + "cache_key.bluetooth.is_offloaded_filtering_supported"; + private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache = + new PropertyInvalidatedCache<Void, Boolean>( + 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { + @Override + protected Boolean recompute(Void query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.isOffloadedFilteringSupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + + } + }; + + /** @hide */ + public void disableIsOffloadedFilteringSupportedCache() { + mBluetoothFilteringCache.disableLocal(); + } + + /** @hide */ + public static void invalidateIsOffloadedFilteringSupportedCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY); + } + /** * Return true if offloaded filters are supported * @@ -1969,17 +2011,7 @@ public final class BluetoothAdapter { if (!getLeAccess()) { return false; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; + return mBluetoothFilteringCache.query(null); } /** @@ -2351,6 +2383,43 @@ public final class BluetoothAdapter { return BluetoothAdapter.STATE_DISCONNECTED; } + private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY = + "cache_key.bluetooth.get_profile_connection_state"; + private final PropertyInvalidatedCache<Integer, Integer> + mGetProfileConnectionStateCache = + new PropertyInvalidatedCache<Integer, Integer>( + 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { + @Override + protected Integer recompute(Integer query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getProfileConnectionState(query); + } + } catch (RemoteException e) { + Log.e(TAG, "getProfileConnectionState:", e); + } finally { + mServiceLock.readLock().unlock(); + } + return BluetoothProfile.STATE_DISCONNECTED; + } + @Override + public String queryToString(Integer query) { + return String.format("getProfileConnectionState(profile=\"%d\")", + query); + } + }; + + /** @hide */ + public void disableGetProfileConnectionStateCache() { + mGetProfileConnectionStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateGetProfileConnectionStateCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY); + } + /** * Get the current connection state of a profile. * This function can be used to check whether the local Bluetooth adapter @@ -2368,17 +2437,7 @@ public final class BluetoothAdapter { if (getState() != STATE_ON) { return BluetoothProfile.STATE_DISCONNECTED; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getProfileConnectionState(profile); - } - } catch (RemoteException e) { - Log.e(TAG, "getProfileConnectionState:", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; + return mGetProfileConnectionStateCache.query(new Integer(profile)); } /** diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index e07ca521e77d..1f89ddf0afc7 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -38,9 +38,6 @@ import java.util.Arrays; import java.util.List; /** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * * Public API for controlling the Bluetooth Pbap Service. This includes * Bluetooth Phone book Access profile. * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap @@ -56,6 +53,11 @@ import java.util.List; * notification when it is bound, this is especially important if you wish to * immediately call methods on BluetoothPbap after construction. * + * To get an instance of the BluetoothPbap class, you can call + * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param + * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of + * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. + * * Android only supports one connected Bluetooth Pce at a time. * * @hide @@ -87,6 +89,7 @@ public class BluetoothPbap implements BluetoothProfile { */ @SuppressLint("ActionValue") @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; @@ -235,7 +238,8 @@ public class BluetoothPbap implements BluetoothProfile { */ @SystemApi @Override - public int getConnectionState(@Nullable BluetoothDevice device) { + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public @BtProfileState int getConnectionState(@Nullable BluetoothDevice device) { log("getConnectionState: device=" + device); try { final IBluetoothPbap service = mService; @@ -287,7 +291,7 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0e0161ff4e9f..f32a4ab43357 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -700,27 +700,6 @@ public abstract class ContentResolver implements ContentInterface { /** @hide */ public static final String REMOTE_CALLBACK_RESULT = "result"; - /** - * How long we wait for an attached process to publish its content providers - * before we decide it must be hung. - * @hide - */ - public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000; - - /** - * How long we wait for an provider to be published. Should be longer than - * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}. - * @hide - */ - public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS = - CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000; - - // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how - // long ActivityManagerService is giving a content provider to get published if a new process - // needs to be started for that. - private static final int GET_TYPE_TIMEOUT_MILLIS = - CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000; - public ContentResolver(@Nullable Context context) { this(context, null); } @@ -870,6 +849,8 @@ public abstract class ContentResolver implements ContentInterface { } } + private static final int GET_TYPE_TIMEOUT_MILLIS = 3000; + private static class GetTypeResultListener implements RemoteCallback.OnResultListener { @GuardedBy("this") public boolean done; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6f8a99fce897..0f88c9040d71 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4289,7 +4289,9 @@ public class Intent implements Parcelable, Cloneable { * intent filter in their manifests, so that they can be looked up and bound to by * {@code DataLoaderManagerService}. * - * Data loader service providers must be privileged apps. + * <p class="note">This is a protected intent that can only be sent by the system. + * + * Data loader service providers must be privileged apps. * See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such * data loader service provider. * @@ -4970,11 +4972,14 @@ public class Intent implements Parcelable, Cloneable { * <pre> * <accessibility-shortcut-target * android:description="@string/shortcut_target_description" - * android:summary="@string/shortcut_target_summary" /> + * android:summary="@string/shortcut_target_summary" + * android:animatedImageDrawable="@drawable/shortcut_target_animated_image" + * android:htmlDescription="@string/shortcut_target_html_description" /> * </pre> * <p> * Both description and summary are necessary. The system will ignore the accessibility - * shortcut target if they are missing. + * shortcut target if they are missing. The animated image and html description are supported + * to help users understand how to use the shortcut target. * </p> */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index cd5117be6123..4be7e6df46e0 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -18,7 +18,9 @@ package android.content.integrity; import android.annotation.NonNull; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -40,6 +42,7 @@ public final class AppInstallMetadata { private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; + private final Map<String, String> mAllowedInstallersAndCertificates; private AppInstallMetadata(Builder builder) { this.mPackageName = builder.mPackageName; @@ -48,6 +51,7 @@ public final class AppInstallMetadata { this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; + this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates; } @NonNull @@ -80,6 +84,13 @@ public final class AppInstallMetadata { return mIsPreInstalled; } + /** + * Get the allowed installers and their corresponding cert. + */ + public Map<String, String> getAllowedInstallersAndCertificates() { + return mAllowedInstallersAndCertificates; + } + @Override public String toString() { return String.format( @@ -101,6 +112,23 @@ public final class AppInstallMetadata { private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; + private Map<String, String> mAllowedInstallersAndCertificates; + + public Builder() { + mAllowedInstallersAndCertificates = new HashMap<>(); + } + + /** + * Add allowed installers and cert. + * + * @see AppInstallMetadata#getAllowedInstallersAndCertificates() + */ + @NonNull + public Builder setAllowedInstallersAndCert( + @NonNull Map<String, String> allowedInstallersAndCertificates) { + this.mAllowedInstallersAndCertificates = allowedInstallersAndCertificates; + return this; + } /** * Set package name of the app to be installed. diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index 42459779e212..d911eabc3b83 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -55,14 +55,12 @@ public abstract class AtomicFormula extends IntegrityFormula { PRE_INSTALLED, }) @Retention(RetentionPolicy.SOURCE) - public @interface Key { - } + public @interface Key {} /** @hide */ @IntDef(value = {EQ, GT, GTE}) @Retention(RetentionPolicy.SOURCE) - public @interface Operator { - } + public @interface Operator {} /** * Package name of the app. @@ -354,7 +352,8 @@ public abstract class AtomicFormula extends IntegrityFormula { "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); mIsHashedValue = - key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE + key == APP_CERTIFICATE + || key == INSTALLER_CERTIFICATE ? true : !mValue.equals(value); } diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java new file mode 100644 index 000000000000..475f019e7b26 --- /dev/null +++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 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.content.integrity; + +import java.util.Map; + +/** + * An atomic formula that evaluates to true if the installer of the current install is specified in + * the "allowed installer" field in the android manifest. Note that an empty "allowed installer" by + * default means containing all possible installers. + * + * @hide + */ +public class InstallerAllowedByManifestFormula extends IntegrityFormula { + + @Override + public int getTag() { + return IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG; + } + + @Override + public boolean matches(AppInstallMetadata appInstallMetadata) { + Map<String, String> allowedInstallersAndCertificates = + appInstallMetadata.getAllowedInstallersAndCertificates(); + return allowedInstallersAndCertificates.isEmpty() + || installerInAllowedInstallersFromManifest( + appInstallMetadata, allowedInstallersAndCertificates); + } + + @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return true; + } + + private static boolean installerInAllowedInstallersFromManifest( + AppInstallMetadata appInstallMetadata, + Map<String, String> allowedInstallersAndCertificates) { + return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName()) + && appInstallMetadata.getInstallerCertificates() + .contains( + allowedInstallersAndCertificates + .get(appInstallMetadata.getInstallerName())); + } +} diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java index a2d937e4df31..ac4c9071f755 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -42,66 +42,88 @@ import java.util.Arrays; @VisibleForTesting public abstract class IntegrityFormula { - /** - * A static formula base for package name formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula PACKAGE_NAME = - new StringAtomicFormula(AtomicFormula.PACKAGE_NAME); + /** Factory class for creating integrity formulas based on the app being installed. */ + public static final class Application { + /** Returns an integrity formula that checks the equality to a package name. */ + @NonNull + public static IntegrityFormula packageNameEquals(@NonNull String packageName) { + return new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, packageName); + } - /** - * A static formula base for app certificate formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula APP_CERTIFICATE = - new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE); + /** + * Returns an integrity formula that checks if the app certificates contain {@code + * appCertificate}. + */ + @NonNull + public static IntegrityFormula certificatesContain(@NonNull String appCertificate) { + return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate); + } - /** - * A static formula base for installer name formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula INSTALLER_NAME = - new StringAtomicFormula(AtomicFormula.INSTALLER_NAME); + /** Returns an integrity formula that checks the equality to a version code. */ + @NonNull + public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) { + return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode); + } - /** - * A static formula base for installer certificate formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula INSTALLER_CERTIFICATE = - new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE); + /** + * Returns an integrity formula that checks the app's version code is greater than the + * provided value. + */ + @NonNull + public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) { + return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode); + } - /** - * A static formula base for version code name formulas. - * - * This formulation is incomplete and should always be used with {@code equals}, - * {@code greaterThan} and {@code greaterThanEquals} formulation. Evaluates to false when used - * directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula VERSION_CODE = - new LongAtomicFormula(AtomicFormula.VERSION_CODE); + /** + * Returns an integrity formula that checks the app's version code is greater than or equal + * to the provided value. + */ + @NonNull + public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) { + return new LongAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode); + } - /** - * A static formula base for pre-installed status formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula PRE_INSTALLED = - new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED); + /** Returns an integrity formula that is valid when app is pre-installed. */ + @NonNull + public static IntegrityFormula isPreInstalled() { + return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + } + + private Application() { + } + } + + /** Factory class for creating integrity formulas based on installer. */ + public static final class Installer { + /** Returns an integrity formula that checks the equality to an installer name. */ + @NonNull + public static IntegrityFormula packageNameEquals(@NonNull String installerName) { + return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName); + } + + /** + * An static formula that evaluates to true if the installer is NOT allowed according to the + * "allowed installer" field in the android manifest. + */ + @NonNull + public static IntegrityFormula notAllowedByManifest() { + return not(new InstallerAllowedByManifestFormula()); + } + + /** + * Returns an integrity formula that checks if the installer certificates contain {@code + * installerCertificate}. + */ + @NonNull + public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) { + return new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, + installerCertificate); + } + + private Installer() { + } + } /** @hide */ @IntDef( @@ -109,10 +131,12 @@ public abstract class IntegrityFormula { COMPOUND_FORMULA_TAG, STRING_ATOMIC_FORMULA_TAG, LONG_ATOMIC_FORMULA_TAG, - BOOLEAN_ATOMIC_FORMULA_TAG + BOOLEAN_ATOMIC_FORMULA_TAG, + INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG }) @Retention(RetentionPolicy.SOURCE) - @interface Tag {} + @interface Tag { + } /** @hide */ public static final int COMPOUND_FORMULA_TAG = 0; @@ -122,6 +146,8 @@ public abstract class IntegrityFormula { public static final int LONG_ATOMIC_FORMULA_TAG = 2; /** @hide */ public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3; + /** @hide */ + public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4; /** * Returns the tag that identifies the current class. @@ -135,14 +161,14 @@ public abstract class IntegrityFormula { * * @hide */ - public abstract @Tag boolean matches(AppInstallMetadata appInstallMetadata); + public abstract boolean matches(AppInstallMetadata appInstallMetadata); /** * Returns true when the formula (or one of its atomic formulas) has app certificate as key. * * @hide */ - public abstract @Tag boolean isAppCertificateFormula(); + public abstract boolean isAppCertificateFormula(); /** * Returns true when the formula (or one of its atomic formulas) has installer package name @@ -150,7 +176,7 @@ public abstract class IntegrityFormula { * * @hide */ - public abstract @Tag boolean isInstallerFormula(); + public abstract boolean isInstallerFormula(); /** * Write an {@link IntegrityFormula} to {@link android.os.Parcel}. @@ -159,7 +185,6 @@ public abstract class IntegrityFormula { * {@link Parcelable}. * * @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass - * * @hide */ public static void writeToParcel( @@ -195,70 +220,6 @@ public abstract class IntegrityFormula { } /** - * Returns an integrity formula that evaluates to true when value of the key matches to the - * provided string value. - * - * <p>The value will be hashed with SHA256 and the hex digest will be computed; for - * all cases except when the key is PACKAGE_NAME or INSTALLER_NAME and the value is less than - * 32 characters. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not string typed. - */ - @NonNull - public IntegrityFormula equalTo(@NonNull String value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.StringAtomicFormula(baseFormula.getKey(), value); - } - - /** - * Returns an integrity formula that evaluates to true when the boolean value of the key matches - * the provided boolean value. It can only be used with the boolean comparison keys. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not boolean typed. - */ - @NonNull - public IntegrityFormula equalTo(boolean value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.BooleanAtomicFormula(baseFormula.getKey(), value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is equal to {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula equalTo(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.EQ, value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is greater than {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula greaterThan(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GT, value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is greater than or equals to the {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula greaterThanOrEquals(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GTE, value); - } - - /** * Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to * true. * diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 2ba2840d8c70..edc20d9f65ad 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -87,7 +87,6 @@ public class CrossProfileApps { mService.startActivityAsUser( mContext.getIApplicationThread(), mContext.getPackageName(), - mContext.getFeatureId(), component, targetUser.getIdentifier(), true); @@ -115,7 +114,6 @@ public class CrossProfileApps { mService.startActivityAsUserByIntent( mContext.getIApplicationThread(), mContext.getPackageName(), - mContext.getFeatureId(), intent, targetUser.getIdentifier()); } catch (RemoteException ex) { @@ -141,8 +139,7 @@ public class CrossProfileApps { public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) { try { mService.startActivityAsUser(mContext.getIApplicationThread(), - mContext.getPackageName(), mContext.getFeatureId(), component, - targetUser.getIdentifier(), false); + mContext.getPackageName(), component, targetUser.getIdentifier(), false); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index 98bf2ddd0e1b..a69b9881aa01 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -28,14 +28,13 @@ import android.os.UserHandle; */ interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in ComponentName component, int userId, - boolean launchMainActivity); + in ComponentName component, int userId, boolean launchMainActivity); void startActivityAsUserByIntent(in IApplicationThread caller, in String callingPackage, - in String callingFeatureId, in Intent intent, int userId); + in Intent intent, int userId); List<UserHandle> getTargetUserProfiles(in String callingPackage); boolean canInteractAcrossProfiles(in String callingPackage); boolean canRequestInteractAcrossProfiles(in String callingPackage); void setInteractAcrossProfilesAppOp(in String packageName, int newMode); boolean canConfigureInteractAcrossProfiles(in String packageName); void resetInteractAcrossProfilesAppOps(in List<String> packageNames); -} +}
\ No newline at end of file diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index b5f4f806d244..38a9ac4a0d05 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -49,13 +49,13 @@ interface ILauncherApps { ActivityInfo resolveActivity( String callingPackage, in ComponentName component, in UserHandle user); void startSessionDetailsActivityAsUser(in IApplicationThread caller, String callingPackage, - String callingFeatureId, in PackageInstaller.SessionInfo sessionInfo, - in Rect sourceBounds, in Bundle opts, in UserHandle user); + in PackageInstaller.SessionInfo sessionInfo, in Rect sourceBounds, in Bundle opts, + in UserHandle user); void startActivityAsUser(in IApplicationThread caller, String callingPackage, - String callingFeatureId, in ComponentName component, in Rect sourceBounds, + in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); - void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage, - String callingFeatureId, in ComponentName component, in Rect sourceBounds, + void showAppDetailsAsUser(in IApplicationThread caller, + String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); boolean isPackageEnabled(String callingPackage, String packageName, in UserHandle user); Bundle getSuspendedPackageLauncherExtras(String packageName, in UserHandle user); @@ -72,7 +72,7 @@ interface ILauncherApps { int flags, in UserHandle user); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, in UserHandle user); - boolean startShortcut(String callingPackage, String packageName, String featureId, String id, + boolean startShortcut(String callingPackage, String packageName, String id, in Rect sourceBounds, in Bundle startActivityOptions, int userId); int getShortcutIconResId(String callingPackage, String packageName, String id, diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java index 111ad32d1e41..b449945628d2 100644 --- a/core/java/android/content/pm/InstallationFile.java +++ b/core/java/android/content/pm/InstallationFile.java @@ -16,82 +16,59 @@ package android.content.pm; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Defines the properties of a file in an installation session. - * TODO(b/136132412): update with new APIs. - * * @hide */ @SystemApi public final class InstallationFile implements Parcelable { - public static final int FILE_TYPE_UNKNOWN = -1; - public static final int FILE_TYPE_APK = 0; - public static final int FILE_TYPE_LIB = 1; - public static final int FILE_TYPE_OBB = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"FILE_TYPE_"}, value = { - FILE_TYPE_APK, - FILE_TYPE_LIB, - FILE_TYPE_OBB, - }) - public @interface FileType { - } - - private String mFileName; - private @FileType int mFileType; - private long mFileSize; - private byte[] mMetadata; - - public InstallationFile(@NonNull String fileName, long fileSize, - @Nullable byte[] metadata) { - mFileName = fileName; - mFileSize = fileSize; + private final @PackageInstaller.FileLocation int mLocation; + private final @NonNull String mName; + private final long mLengthBytes; + private final @Nullable byte[] mMetadata; + private final @Nullable byte[] mSignature; + + public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name, + long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) { + mLocation = location; + mName = name; + mLengthBytes = lengthBytes; mMetadata = metadata; - if (fileName.toLowerCase().endsWith(".apk")) { - mFileType = FILE_TYPE_APK; - } else if (fileName.toLowerCase().endsWith(".obb")) { - mFileType = FILE_TYPE_OBB; - } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith( - "lib/")) { - mFileType = FILE_TYPE_LIB; - } else { - mFileType = FILE_TYPE_UNKNOWN; - } + mSignature = signature; } - public @FileType int getFileType() { - return mFileType; + public @PackageInstaller.FileLocation int getLocation() { + return mLocation; } public @NonNull String getName() { - return mFileName; + return mName; } - public long getSize() { - return mFileSize; + public long getLengthBytes() { + return mLengthBytes; } public @Nullable byte[] getMetadata() { return mMetadata; } + public @Nullable byte[] getSignature() { + return mSignature; + } + private InstallationFile(Parcel source) { - mFileName = source.readString(); - mFileType = source.readInt(); - mFileSize = source.readLong(); + mLocation = source.readInt(); + mName = source.readString(); + mLengthBytes = source.readLong(); mMetadata = source.createByteArray(); + mSignature = source.createByteArray(); } @Override @@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mFileName); - dest.writeInt(mFileType); - dest.writeLong(mFileSize); + dest.writeInt(mLocation); + dest.writeString(mName); + dest.writeLong(mLengthBytes); dest.writeByteArray(mMetadata); + dest.writeByteArray(mSignature); } public static final @NonNull Creator<InstallationFile> CREATOR = diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java index 84f5021f0538..f692db1f0443 100644 --- a/core/java/android/content/pm/InstantAppRequest.java +++ b/core/java/android/content/pm/InstantAppRequest.java @@ -35,8 +35,6 @@ public final class InstantAppRequest { public final String resolvedType; /** The name of the package requesting the instant application */ public final String callingPackage; - /** The feature in the package requesting the instant application */ - public final String callingFeatureId; /** Whether or not the requesting package was an instant app */ public final boolean isRequesterInstantApp; /** ID of the user requesting the instant application */ @@ -59,15 +57,13 @@ public final class InstantAppRequest { public final String token; public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent, - String resolvedType, String callingPackage, @Nullable String callingFeatureId, - boolean isRequesterInstantApp, int userId, Bundle verificationBundle, - boolean resolveForStart, @Nullable int[] hostDigestPrefixSecure, - @NonNull String token) { + String resolvedType, String callingPackage, boolean isRequesterInstantApp, + int userId, Bundle verificationBundle, boolean resolveForStart, + @Nullable int[] hostDigestPrefixSecure, @NonNull String token) { this.responseObj = responseObj; this.origIntent = origIntent; this.resolvedType = resolvedType; this.callingPackage = callingPackage; - this.callingFeatureId = callingFeatureId; this.isRequesterInstantApp = isRequesterInstantApp; this.userId = userId; this.verificationBundle = verificationBundle; diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 271d5e44d5db..70603b472551 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,6 +16,7 @@ package android.content.pm; +import static android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -601,11 +602,15 @@ public class LauncherApps { } /** - * Show an error log on logcat, when the calling user is a managed profile, and the target - * user is different from the calling user, in order to help developers to detect it. + * Show an error log on logcat, when the calling user is a managed profile, the target + * user is different from the calling user, and it is not called from a package that has the + * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help + * developers to detect it. */ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) { - if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) { + if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile() + && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed."); } } @@ -715,7 +720,7 @@ public class LauncherApps { } try { mService.startActivityAsUser(mContext.getIApplicationThread(), - mContext.getPackageName(), mContext.getFeatureId(), + mContext.getPackageName(), component, sourceBounds, opts, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); @@ -733,8 +738,8 @@ public class LauncherApps { @Nullable Rect sourceBounds, @Nullable Bundle opts) { try { mService.startSessionDetailsActivityAsUser(mContext.getIApplicationThread(), - mContext.getPackageName(), mContext.getFeatureId(), sessionInfo, sourceBounds, - opts, sessionInfo.getUser()); + mContext.getPackageName(), sessionInfo, sourceBounds, opts, + sessionInfo.getUser()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -754,7 +759,7 @@ public class LauncherApps { logErrorForInvalidProfileAccess(user); try { mService.showAppDetailsAsUser(mContext.getIApplicationThread(), - mContext.getPackageName(), mContext.getFeatureId(), + mContext.getPackageName(), component, sourceBounds, opts, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); @@ -1346,9 +1351,9 @@ public class LauncherApps { @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions, int userId) { try { - final boolean success = mService.startShortcut(mContext.getPackageName(), packageName, - null /* default featureId */, shortcutId, sourceBounds, startActivityOptions, - userId); + final boolean success = + mService.startShortcut(mContext.getPackageName(), packageName, shortcutId, + sourceBounds, startActivityOptions, userId); if (!success) { throw new ActivityNotFoundException("Shortcut could not be started"); } diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java index fbe5a48ad61e..da17ff3cdefc 100644 --- a/core/java/android/content/pm/parsing/AndroidPackage.java +++ b/core/java/android/content/pm/parsing/AndroidPackage.java @@ -286,6 +286,8 @@ public interface AndroidPackage extends Parcelable { List<String> getQueriesPackages(); + Set<String> getQueriesProviders(); + String getRealPackage(); // TODO(b/135203078): Rename to getRequiredFeatures? Somewhat ambiguous whether "Req" is diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 5c8c9a41a520..548d82a6ab76 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -96,6 +96,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringTokenizer; /** @hide */ public class ApkParseUtils { @@ -1817,6 +1818,25 @@ public class ApkParseUtils { ); } parsingPackage.addQueriesPackage(packageName.intern()); + } else if (parser.getName().equals("provider")) { + final TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestQueriesProvider); + try { + final String authorities = + sa.getString(R.styleable.AndroidManifestQueriesProvider_authorities); + if (TextUtils.isEmpty(authorities)) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Authority missing from provider tag." + ); + } + StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";"); + while (authoritiesTokenizer.hasMoreElements()) { + parsingPackage.addQueriesProvider(authoritiesTokenizer.nextToken()); + } + } finally { + sa.recycle(); + } } } return parseInput.success(parsingPackage); diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index fe8307c7c8cd..0df950006f43 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -216,6 +216,9 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android private ArrayList<String> queriesPackages; @Nullable + private ArraySet<String> queriesProviders; + + @Nullable private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes; private String[] splitClassLoaderNames; @@ -957,6 +960,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public ParsingPackage addQueriesProvider(String authority) { + this.queriesProviders = ArrayUtils.add(this.queriesProviders, authority); + return this; + } + + @Override public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) { this.processes = processes; return this; @@ -2975,6 +2984,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android return queriesPackages; } + @Override + public Set<String> getQueriesProviders() { + return queriesProviders; + } + private static void internStringArrayList(List<String> list) { if (list != null) { final int N = list.size(); diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 9ddcc0995fd4..a2fe064b66c3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -100,6 +100,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage addQueriesPackage(String packageName); + ParsingPackage addQueriesProvider(String authority); + ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes); ParsingPackage asSplit( diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8c358cc522d6..6a9e0aa047d1 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -45,6 +45,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.UiModeManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.LocaleProto; @@ -1975,6 +1976,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration readFromParcel(source); } + + /** + * Retuns whether the configuration is in night mode + * @return true if night mode is active and false otherwise + */ + public boolean isNightModeActive() { + return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; + } + public int compareTo(Configuration that) { int n; float a = this.fontScale; diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 5a1365169eee..add67aa436c6 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -132,6 +132,14 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + */ + int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index bae0fd3ad3b9..eafcf529de62 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.app.KeyguardManager; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.face.FaceManager; /** @@ -36,12 +37,12 @@ public interface BiometricFaceConstants { * authentication. Note this is to accommodate people who have limited * vision. */ - public static final int FEATURE_REQUIRE_ATTENTION = 1; + int FEATURE_REQUIRE_ATTENTION = 1; /** * Require a diverse set of poses during enrollment. Note this is to * accommodate people with limited mobility. */ - public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; + int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; // // Error messages from face authentication hardware during initialization, enrollment, @@ -50,32 +51,32 @@ public interface BiometricFaceConstants { /** * The hardware is unavailable. Try again later. */ - public static final int FACE_ERROR_HW_UNAVAILABLE = 1; + int FACE_ERROR_HW_UNAVAILABLE = 1; /** * Error state returned when the sensor was unable to process the current image. */ - public static final int FACE_ERROR_UNABLE_TO_PROCESS = 2; + int FACE_ERROR_UNABLE_TO_PROCESS = 2; /** * Error state returned when the current request has been running too long. This is intended to * prevent programs from waiting for the face authentication sensor indefinitely. The timeout is * platform and sensor-specific, but is generally on the order of 30 seconds. */ - public static final int FACE_ERROR_TIMEOUT = 3; + int FACE_ERROR_TIMEOUT = 3; /** * Error state returned for operations like enrollment; the operation cannot be completed * because there's not enough storage remaining to complete the operation. */ - public static final int FACE_ERROR_NO_SPACE = 4; + int FACE_ERROR_NO_SPACE = 4; /** * The operation was canceled because the face authentication sensor is unavailable. For * example, this may happen when the user is switched, the device is locked or another pending * operation prevents or disables it. */ - public static final int FACE_ERROR_CANCELED = 5; + int FACE_ERROR_CANCELED = 5; /** * The {@link FaceManager#remove} call failed. Typically this will happen when the @@ -83,13 +84,13 @@ public interface BiometricFaceConstants { * * @hide */ - public static final int FACE_ERROR_UNABLE_TO_REMOVE = 6; + int FACE_ERROR_UNABLE_TO_REMOVE = 6; /** * The operation was canceled because the API is locked out due to too many attempts. * This occurs after 5 failed attempts, and lasts for 30 seconds. */ - public static final int FACE_ERROR_LOCKOUT = 7; + int FACE_ERROR_LOCKOUT = 7; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -99,52 +100,62 @@ public interface BiometricFaceConstants { * expected to show the error message string if they happen, but are advised not to rely on the * message id since they will be device and vendor-specific */ - public static final int FACE_ERROR_VENDOR = 8; + int FACE_ERROR_VENDOR = 8; /** * The operation was canceled because FACE_ERROR_LOCKOUT occurred too many times. * Face authentication is disabled until the user unlocks with strong authentication * (PIN/Pattern/Password) */ - public static final int FACE_ERROR_LOCKOUT_PERMANENT = 9; + int FACE_ERROR_LOCKOUT_PERMANENT = 9; /** * The user canceled the operation. Upon receiving this, applications should use alternate * authentication (e.g. a password). The application should also provide the means to return * to face authentication, such as a "use face authentication" button. */ - public static final int FACE_ERROR_USER_CANCELED = 10; + int FACE_ERROR_USER_CANCELED = 10; /** * The user does not have a face enrolled. */ - public static final int FACE_ERROR_NOT_ENROLLED = 11; + int FACE_ERROR_NOT_ENROLLED = 11; /** * The device does not have a face sensor. This message will propagate if the calling app * ignores the result from PackageManager.hasFeature(FEATURE_FACE) and calls * this API anyway. Apps should always check for the feature before calling this API. */ - public static final int FACE_ERROR_HW_NOT_PRESENT = 12; + int FACE_ERROR_HW_NOT_PRESENT = 12; /** * The user pressed the negative button. This is a placeholder that is currently only used * by the support library. + * * @hide */ - public static final int FACE_ERROR_NEGATIVE_BUTTON = 13; + int FACE_ERROR_NEGATIVE_BUTTON = 13; /** * The device does not have pin, pattern, or password set up. See * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and * {@link KeyguardManager#isDeviceSecure()} */ - public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + * @hide + */ + int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** * @hide */ - public static final int FACE_ERROR_VENDOR_BASE = 1000; + int FACE_ERROR_VENDOR_BASE = 1000; // // Image acquisition messages. These will not be sent to the user, since they conflict with @@ -154,13 +165,13 @@ public interface BiometricFaceConstants { /** * The image acquired was good. */ - public static final int FACE_ACQUIRED_GOOD = 0; + int FACE_ACQUIRED_GOOD = 0; /** * The face image was not good enough to process due to a detected condition. * (See {@link #FACE_ACQUIRED_TOO_BRIGHT or @link #FACE_ACQUIRED_TOO_DARK}). */ - public static final int FACE_ACQUIRED_INSUFFICIENT = 1; + int FACE_ACQUIRED_INSUFFICIENT = 1; /** * The face image was too bright due to too much ambient light. @@ -169,7 +180,7 @@ public interface BiometricFaceConstants { * The user is expected to take action to retry in better lighting conditions * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_BRIGHT = 2; + int FACE_ACQUIRED_TOO_BRIGHT = 2; /** * The face image was too dark due to illumination light obscured. @@ -178,65 +189,65 @@ public interface BiometricFaceConstants { * The user is expected to take action to retry in better lighting conditions * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_DARK = 3; + int FACE_ACQUIRED_TOO_DARK = 3; /** * The detected face is too close to the sensor, and the image can't be processed. * The user should be informed to move farther from the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_CLOSE = 4; + int FACE_ACQUIRED_TOO_CLOSE = 4; /** * The detected face is too small, as the user might be too far from the sensor. * The user should be informed to move closer to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_FAR = 5; + int FACE_ACQUIRED_TOO_FAR = 5; /** * Only the upper part of the face was detected. The sensor field of view is too high. * The user should be informed to move up with respect to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_HIGH = 6; + int FACE_ACQUIRED_TOO_HIGH = 6; /** * Only the lower part of the face was detected. The sensor field of view is too low. * The user should be informed to move down with respect to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_LOW = 7; + int FACE_ACQUIRED_TOO_LOW = 7; /** * Only the right part of the face was detected. The sensor field of view is too far right. * The user should be informed to move to the right with respect to the sensor * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_RIGHT = 8; + int FACE_ACQUIRED_TOO_RIGHT = 8; /** * Only the left part of the face was detected. The sensor field of view is too far left. * The user should be informed to move to the left with respect to the sensor * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_LEFT = 9; + int FACE_ACQUIRED_TOO_LEFT = 9; /** * The user's eyes have strayed away from the sensor. If this message is sent, the user should * be informed to look at the device. If the user can't be found in the frame, one of the other * acquisition messages should be sent, e.g. FACE_ACQUIRED_NOT_DETECTED. */ - public static final int FACE_ACQUIRED_POOR_GAZE = 10; + int FACE_ACQUIRED_POOR_GAZE = 10; /** * No face was detected in front of the sensor. * The user should be informed to point the sensor to a face when this is returned. */ - public static final int FACE_ACQUIRED_NOT_DETECTED = 11; + int FACE_ACQUIRED_NOT_DETECTED = 11; /** * Too much motion was detected. * The user should be informed to keep their face steady relative to the * sensor. */ - public static final int FACE_ACQUIRED_TOO_MUCH_MOTION = 12; + int FACE_ACQUIRED_TOO_MUCH_MOTION = 12; /** * The sensor needs to be re-calibrated. This is an unexpected condition, and should only be @@ -244,20 +255,20 @@ public interface BiometricFaceConstants { * requires user intervention, e.g. re-enrolling. The expected response to this message is to * direct the user to re-enroll. */ - public static final int FACE_ACQUIRED_RECALIBRATE = 13; + int FACE_ACQUIRED_RECALIBRATE = 13; /** * The face is too different from a previous acquisition. This condition * only applies to enrollment. This can happen if the user passes the * device to someone else in the middle of enrollment. */ - public static final int FACE_ACQUIRED_TOO_DIFFERENT = 14; + int FACE_ACQUIRED_TOO_DIFFERENT = 14; /** * The face is too similar to a previous acquisition. This condition only * applies to enrollment. The user should change their pose. */ - public static final int FACE_ACQUIRED_TOO_SIMILAR = 15; + int FACE_ACQUIRED_TOO_SIMILAR = 15; /** * The magnitude of the pan angle of the user’s face with respect to the sensor’s @@ -269,7 +280,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; + int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; /** * The magnitude of the tilt angle of the user’s face with respect to the sensor’s @@ -280,7 +291,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; + int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; /** * The magnitude of the roll angle of the user’s face with respect to the sensor’s @@ -292,7 +303,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; + int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; /** * The user’s face has been obscured by some object. @@ -300,7 +311,7 @@ public interface BiometricFaceConstants { * The user should be informed to remove any objects from the line of sight from * the sensor to the user’s face. */ - public static final int FACE_ACQUIRED_FACE_OBSCURED = 19; + int FACE_ACQUIRED_FACE_OBSCURED = 19; /** * This message represents the earliest message sent at the beginning of the authentication @@ -310,12 +321,12 @@ public interface BiometricFaceConstants { * The framework will measure latency based on the time between the last START message and the * onAuthenticated callback. */ - public static final int FACE_ACQUIRED_START = 20; + int FACE_ACQUIRED_START = 20; /** * The sensor is dirty. The user should be informed to clean the sensor. */ - public static final int FACE_ACQUIRED_SENSOR_DIRTY = 21; + int FACE_ACQUIRED_SENSOR_DIRTY = 21; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -323,10 +334,10 @@ public interface BiometricFaceConstants { * * @hide */ - public static final int FACE_ACQUIRED_VENDOR = 22; + int FACE_ACQUIRED_VENDOR = 22; /** * @hide */ - public static final int FACE_ACQUIRED_VENDOR_BASE = 1000; + int FACE_ACQUIRED_VENDOR_BASE = 1000; } diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 5c74456eb60a..46e8cc036809 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.app.KeyguardManager; import android.compat.annotation.UnsupportedAppUsage; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.fingerprint.FingerprintManager; /** @@ -37,32 +38,32 @@ public interface BiometricFingerprintConstants { /** * The hardware is unavailable. Try again later. */ - public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; /** * Error state returned when the sensor was unable to process the current image. */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; + int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; /** * Error state returned when the current request has been running too long. This is intended to * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is * platform and sensor-specific, but is generally on the order of 30 seconds. */ - public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + int FINGERPRINT_ERROR_TIMEOUT = 3; /** * Error state returned for operations like enrollment; the operation cannot be completed * because there's not enough storage remaining to complete the operation. */ - public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + int FINGERPRINT_ERROR_NO_SPACE = 4; /** * The operation was canceled because the fingerprint sensor is unavailable. For example, * this may happen when the user is switched, the device is locked or another pending operation * prevents or disables it. */ - public static final int FINGERPRINT_ERROR_CANCELED = 5; + int FINGERPRINT_ERROR_CANCELED = 5; /** * The {@link FingerprintManager#remove} call failed. Typically this will happen when the @@ -70,13 +71,13 @@ public interface BiometricFingerprintConstants { * * @hide */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; /** * The operation was canceled because the API is locked out due to too many attempts. * This occurs after 5 failed attempts, and lasts for 30 seconds. */ - public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + int FINGERPRINT_ERROR_LOCKOUT = 7; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -86,52 +87,63 @@ public interface BiometricFingerprintConstants { * expected to show the error message string if they happen, but are advised not to rely on the * message id since they will be device and vendor-specific */ - public static final int FINGERPRINT_ERROR_VENDOR = 8; + int FINGERPRINT_ERROR_VENDOR = 8; /** * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. * Fingerprint authentication is disabled until the user unlocks with strong authentication * (PIN/Pattern/Password) */ - public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; + int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; /** * The user canceled the operation. Upon receiving this, applications should use alternate * authentication (e.g. a password). The application should also provide the means to return * to fingerprint authentication, such as a "use fingerprint" button. */ - public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; + int FINGERPRINT_ERROR_USER_CANCELED = 10; /** * The user does not have any fingerprints enrolled. */ - public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; + int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; /** * The device does not have a fingerprint sensor. */ - public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; + int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; /** * The user pressed the negative button. This is a placeholder that is currently only used * by the support library. + * * @hide */ - public static final int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13; + int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13; /** * The device does not have pin, pattern, or password set up. See * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and * {@link KeyguardManager#isDeviceSecure()} + * * @hide */ - public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + * @hide + */ + public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** * @hide */ @UnsupportedAppUsage - public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; + int FINGERPRINT_ERROR_VENDOR_BASE = 1000; // // Image acquisition messages. Must agree with those in fingerprint.h @@ -140,19 +152,19 @@ public interface BiometricFingerprintConstants { /** * The image acquired was good. */ - public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + int FINGERPRINT_ACQUIRED_GOOD = 0; /** * Only a partial fingerprint image was detected. During enrollment, the user should be * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." */ - public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + int FINGERPRINT_ACQUIRED_PARTIAL = 1; /** * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). */ - public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; /** * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. @@ -161,13 +173,13 @@ public interface BiometricFingerprintConstants { * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor * when this is returned. */ - public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; /** * The fingerprint image was unreadable due to lack of motion. This is most appropriate for * linear array sensors that require a swipe motion. */ - public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; /** * The fingerprint image was incomplete due to quick motion. While mostly appropriate for @@ -175,16 +187,29 @@ public interface BiometricFingerprintConstants { * The user should be asked to move the finger slower (linear) or leave the finger on the sensor * longer. */ - public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + int FINGERPRINT_ACQUIRED_TOO_FAST = 5; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. + * * @hide */ - public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; + int FINGERPRINT_ACQUIRED_VENDOR = 6; + + /** + * This message represents the earliest message sent at the beginning of the authentication + * pipeline. It is expected to be used to measure latency. Note this should be sent whenever + * authentication is restarted. + * The framework will measure latency based on the time between the last START message and the + * onAuthenticated callback. + * + * @hide + */ + int FINGERPRINT_ACQUIRED_START = 7; + /** * @hide */ - public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; + int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 125b676e14f7..7d66cae4c845 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -61,10 +61,20 @@ public class BiometricManager { public static final int BIOMETRIC_ERROR_NO_HARDWARE = BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + */ + public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = + BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + @IntDef({BIOMETRIC_SUCCESS, BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, - BIOMETRIC_ERROR_NO_HARDWARE}) + BIOMETRIC_ERROR_NO_HARDWARE, + BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED}) @interface BiometricError {} /** diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index f6717c77f90e..ea576bc569d2 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -918,7 +918,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing if (mEnrollmentCallback != null) { mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) { + mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + } } } @@ -1050,6 +1052,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return msgArray[vendorCode]; } } + break; + case FINGERPRINT_ACQUIRED_START: + return null; } Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode); return null; diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 987a53e337a0..25fb3e0ed907 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -29,6 +29,8 @@ package android.os.incremental; * @throws IllegalStateException the session is not an Incremental installation session. */ +import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -85,7 +87,7 @@ public final class IncrementalFileStorages { try { result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams); for (InstallationFile file : addedFiles) { - if (file.getFileType() == InstallationFile.FILE_TYPE_APK) { + if (file.getLocation() == LOCATION_DATA_APP) { try { result.addApkFile(file); } catch (IOException e) { @@ -95,7 +97,7 @@ public final class IncrementalFileStorages { e); } } else { - throw new IOException("Unknown file type: " + file.getFileType()); + throw new IOException("Unknown file location: " + file.getLocation()); } } @@ -147,8 +149,8 @@ public final class IncrementalFileStorages { String apkName = apk.getName(); File targetFile = Paths.get(stageDirPath, apkName).toFile(); if (!targetFile.exists()) { - mDefaultStorage.makeFile(apkName, apk.getSize(), null, - apk.getMetadata(), 0, null, null, null); + mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(), + apk.getSignature()); } } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index ba38268949b5..d2d8f85b1b35 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -299,7 +299,16 @@ public final class IncrementalManager { return nativeIsIncrementalPath(path); } + /** + * Returns raw signature for file if it's on Incremental File System. + * Unsafe, use only if you are sure what you are doing. + */ + public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) { + return nativeUnsafeGetFileSignature(path); + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); + private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 5df44ff49059..f4e1f967dca8 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -169,10 +171,11 @@ public final class IncrementalStorage { * @param path Relative path of the new file. * @param size Size of the new file in bytes. * @param metadata Metadata bytes. + * @param v4signatureBytes Serialized V4SignatureProto. */ public void makeFile(@NonNull String path, long size, @Nullable UUID id, - @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash, - @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException { + @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes) + throws IOException { try { if (id == null && metadata == null) { throw new IOException("File ID and metadata cannot both be null"); @@ -181,13 +184,7 @@ public final class IncrementalStorage { params.size = size; params.metadata = (metadata == null ? new byte[0] : metadata); params.fileId = idToBytes(id); - if (hashAlgorithm != 0 || signature != null) { - params.signature = new IncrementalSignature(); - params.signature.hashAlgorithm = hashAlgorithm; - params.signature.rootHash = rootHash; - params.signature.additionalData = additionalData; - params.signature.signature = signature; - } + params.signature = parseV4Signature(v4signatureBytes); int res = mService.makeFile(mId, path, params); if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); @@ -197,6 +194,7 @@ public final class IncrementalStorage { } } + /** * Creates a file in Incremental storage. The content of the file is mapped from a range inside * a source file in the same storage. @@ -349,6 +347,37 @@ public final class IncrementalStorage { } } + /** + * Returns the metadata object of an IncFs File. + * + * @param id The file id. + * @return Byte array that contains metadata bytes. + */ + @Nullable + public byte[] getFileMetadata(@NonNull UUID id) { + try { + final byte[] rawId = idToBytes(id); + return mService.getMetadataById(mId, rawId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Informs the data loader service associated with the current storage to start data loader + * + * @return True if data loader is successfully started. + */ + public boolean startLoading() { + try { + return mService.startLoading(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + private static final int UUID_BYTE_SIZE = 16; /** @@ -386,35 +415,44 @@ public final class IncrementalStorage { return new UUID(msb, lsb); } + private static final int INCFS_HASH_SHA256 = 1; + private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 + private static final int INCFS_MAX_ADD_DATA_SIZE = 128; + /** - * Returns the metadata object of an IncFs File. - * - * @param id The file id. - * @return Byte array that contains metadata bytes. + * Deserialize and validate v4 signature bytes. */ - @Nullable - public byte[] getFileMetadata(@NonNull UUID id) { - try { - final byte[] rawId = idToBytes(id); - return mService.getMetadataById(mId, rawId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes) + throws IOException { + if (v4signatureBytes == null) { return null; } - } - /** - * Informs the data loader service associated with the current storage to start data loader - * - * @return True if data loader is successfully started. - */ - public boolean startLoading() { - try { - return mService.startLoading(mId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + final V4Signature signature; + try (DataInputStream input = new DataInputStream( + new ByteArrayInputStream(v4signatureBytes))) { + signature = V4Signature.readFrom(input); + } + + final byte[] rootHash = signature.verityRootHash; + final byte[] additionalData = signature.v3Digest; + final byte[] pkcs7Signature = signature.pkcs7SignatureBlock; + + if (rootHash.length != INCFS_MAX_HASH_SIZE) { + throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + } + if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { + throw new IOException( + "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes"); } + + IncrementalSignature result = new IncrementalSignature(); + result.hashAlgorithm = INCFS_HASH_SHA256; + result.rootHash = rootHash; + result.additionalData = additionalData; + result.signature = pkcs7Signature; + + return result; } /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java new file mode 100644 index 000000000000..5fadee4355b3 --- /dev/null +++ b/core/java/android/os/incremental/V4Signature.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 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.os.incremental; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * V4 signature fields. + * Keep in sync with APKSig master copy. + * @hide + */ +public class V4Signature { + public final byte[] verityRootHash; + public final byte[] v3Digest; + public final byte[] pkcs7SignatureBlock; + + V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + this.verityRootHash = verityRootHash; + this.v3Digest = v3Digest; + this.pkcs7SignatureBlock = pkcs7SignatureBlock; + } + + static byte[] readBytes(DataInputStream stream) throws IOException { + byte[] result = new byte[stream.readInt()]; + stream.read(result); + return result; + } + + static V4Signature readFrom(DataInputStream stream) throws IOException { + byte[] verityRootHash = readBytes(stream); + byte[] v3Digest = readBytes(stream); + byte[] pkcs7SignatureBlock = readBytes(stream); + return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock); + } + + static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { + stream.writeInt(bytes.length); + stream.write(bytes); + } + + void writeTo(DataOutputStream stream) throws IOException { + writeBytes(stream, this.verityRootHash); + writeBytes(stream, this.v3Digest); + writeBytes(stream, this.pkcs7SignatureBlock); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b9207e5a12d5..ea0afa94c8aa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10207,7 +10207,9 @@ public final class Settings { * * Type: int (0 for false, 1 for true) * @hide + * @deprecated Use {@link WifiManager#isAutoWakeupEnabled()} instead. */ + @Deprecated @SystemApi public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; @@ -10243,7 +10245,6 @@ public final class Settings { * enabled state. * @hide */ - @SystemApi public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index c21577842199..b9700b2b80db 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -90,7 +90,7 @@ public abstract class DataLoaderService extends Service { * @hide */ @SystemApi - public @Nullable DataLoader onCreateDataLoader() { + public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 8e6f77b2fd0c..36f2c6267622 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1314,7 +1314,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(mContext); int res = mSystemService.startVoiceActivity(mToken, intent, - intent.resolveType(mContext.getContentResolver()), mContext.getFeatureId()); + intent.resolveType(mContext.getContentResolver())); Instrumentation.checkStartActivityResult(res, intent); } catch (RemoteException e) { } @@ -1342,7 +1342,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(mContext); int res = mSystemService.startAssistantActivity(mToken, intent, - intent.resolveType(mContext.getContentResolver()), mContext.getFeatureId()); + intent.resolveType(mContext.getContentResolver())); Instrumentation.checkStartActivityResult(res, intent); } catch (RemoteException e) { } diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java new file mode 100644 index 000000000000..cc1e5010edc7 --- /dev/null +++ b/core/java/android/view/AccessibilityEmbeddedConnection.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.os.IBinder; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityEmbeddedConnection; + +import java.lang.ref.WeakReference; + +/** + * This class is an interface this ViewRootImpl provides to the host view to the latter + * can interact with the view hierarchy in SurfaceControlViewHost. + * + * @hide + */ +final class AccessibilityEmbeddedConnection extends IAccessibilityEmbeddedConnection.Stub { + private final WeakReference<ViewRootImpl> mViewRootImpl; + + AccessibilityEmbeddedConnection(ViewRootImpl viewRootImpl) { + mViewRootImpl = new WeakReference<>(viewRootImpl); + } + + @Override + public @Nullable IBinder associateEmbeddedHierarchy(@NonNull IBinder host, int hostViewId) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = host; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = hostViewId; + if (accessibilityManager.isEnabled()) { + accessibilityManager.associateEmbeddedHierarchy(host, viewRootImpl.mLeashToken); + } + return viewRootImpl.mLeashToken; + } + return null; + } + + @Override + public void disassociateEmbeddedHierarchy() { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = null; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID; + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0); + if (accessibilityManager.isEnabled()) { + accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken); + } + } + } + + @Override + public void setScreenMatrix(float[] matrixValues) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + // TODO(b/148821260): Implement the rest of matrix values. + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set( + (int) matrixValues[Matrix.MTRANS_X], (int) matrixValues[Matrix.MTRANS_Y]); + } + } +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 203b08765663..3ca84c1eb18d 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -855,6 +855,36 @@ public final class AccessibilityInteractionController { return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0); } + private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAssociateLeashedParent()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + associateLeashedParentIfNeeded(info); + } + } + + private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { + if (info == null || shouldBypassAssociateLeashedParent()) { + return; + } + // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id + // with root view. + if (mViewRootImpl.mView.getAccessibilityViewId() + != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) { + return; + } + info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken, + mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId); + } + + private boolean shouldBypassAssociateLeashedParent() { + return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null + && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID); + } + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec) { if (info == null) { @@ -914,6 +944,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(infos); adjustBoundsInScreenIfNeeded(infos); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. @@ -935,6 +966,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(info); adjustBoundsInScreenIfNeeded(info); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java new file mode 100644 index 000000000000..d21a9520e12c --- /dev/null +++ b/core/java/android/view/CutoutSpecification.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2020 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.view; + +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.LEFT; +import static android.view.Gravity.RIGHT; +import static android.view.Gravity.TOP; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Insets; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.text.TextUtils; +import android.util.Log; +import android.util.PathParser; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; +import java.util.Objects; + +/** + * In order to accept the cutout specification for all of edges in devices, the specification + * parsing method is extracted from + * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be + * the specified class for parsing the specification. + * BNF definition: + * <ul> + * <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li> + * <li>Cutout Specification = [Vertical Position], (SVG Path Element), [Horizontal Position] + * [Bind Cutout] ;</li> + * <li>Vertical Position = "@bottom" | "@center_vertical" ;</li> + * <li>Horizontal Position = "@left" | "@right" ;</li> + * <li>Bind Cutout = "@bind_left_cutout" | "@bind_right_cutout" ;</li> + * <li>Cutout Delimiter = "@cutout" ;</li> + * <li>Dp = "@dp"</li> + * </ul> + * + * <ul> + * <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical" + * </li> + * <li>Horizontal position is center horizontal by default if there is neither "@left" nor + * "@right".</li> + * <li>@bottom make the cutout piece bind to bottom edge.</li> + * <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to + * left or right edge cutout.</li> + * </ul> + * + * @hide + */ +@VisibleForTesting(visibility = PACKAGE) +public class CutoutSpecification { + private static final String TAG = "CutoutSpecification"; + private static final boolean DEBUG = false; + + private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length(); + + private static final char MARKER_START_CHAR = '@'; + private static final String DP_MARKER = MARKER_START_CHAR + "dp"; + + private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom"; + private static final String RIGHT_MARKER = MARKER_START_CHAR + "right"; + private static final String LEFT_MARKER = MARKER_START_CHAR + "left"; + private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout"; + private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical"; + + /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */ + private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout"; + private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout"; + + private final Path mPath; + private final Rect mLeftBound; + private final Rect mTopBound; + private final Rect mRightBound; + private final Rect mBottomBound; + private final Insets mInsets; + + private CutoutSpecification(@NonNull Parser parser) { + mPath = parser.mPath; + mLeftBound = parser.mLeftBound; + mTopBound = parser.mTopBound; + mRightBound = parser.mRightBound; + mBottomBound = parser.mBottomBound; + mInsets = parser.mInsets; + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s", + mLeftBound != null ? mLeftBound.toString() : "", + mTopBound != null ? mTopBound.toString() : "", + mRightBound != null ? mRightBound.toString() : "", + mBottomBound != null ? mBottomBound.toString() : "")); + } + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Path getPath() { + return mPath; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getLeftBound() { + return mLeftBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getTopBound() { + return mTopBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getRightBound() { + return mRightBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getBottomBound() { + return mBottomBound; + } + + /** + * To count the safe inset according to the cutout bounds and waterfall inset. + * + * @return the safe inset. + */ + @VisibleForTesting(visibility = PACKAGE) + @NonNull + public Rect getSafeInset() { + return mInsets.toRect(); + } + + private static int decideWhichEdge(boolean isTopEdgeShortEdge, + boolean isShortEdge, boolean isStart) { + return (isTopEdgeShortEdge) + ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT)) + : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM)); + } + + /** + * The CutoutSpecification Parser. + */ + @VisibleForTesting(visibility = PACKAGE) + public static class Parser { + private final boolean mIsShortEdgeOnTop; + private final float mDensity; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final Matrix mMatrix; + private Insets mInsets; + private int mSafeInsetLeft; + private int mSafeInsetTop; + private int mSafeInsetRight; + private int mSafeInsetBottom; + + private final Rect mTmpRect = new Rect(); + private final RectF mTmpRectF = new RectF(); + + private boolean mInDp; + + private Path mPath; + private Rect mLeftBound; + private Rect mTopBound; + private Rect mRightBound; + private Rect mBottomBound; + + private boolean mPositionFromLeft = false; + private boolean mPositionFromRight = false; + private boolean mPositionFromBottom = false; + private boolean mPositionFromCenterVertical = false; + + private boolean mBindLeftCutout = false; + private boolean mBindRightCutout = false; + private boolean mBindBottomCutout = false; + + private boolean mIsTouchShortEdgeStart; + private boolean mIsTouchShortEdgeEnd; + private boolean mIsCloserToStartSide; + + /** + * The constructor of the CutoutSpecification parser to parse the specification of cutout. + * @param density the display density. + * @param displayWidth the display width. + * @param displayHeight the display height. + */ + @VisibleForTesting(visibility = PACKAGE) + public Parser(float density, int displayWidth, int displayHeight) { + mDensity = density; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mMatrix = new Matrix(); + mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight; + } + + private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + mTmpRectF.setEmpty(); + p.computeBounds(mTmpRectF, false /* unused */); + mTmpRectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private void resetStatus(StringBuilder sb) { + sb.setLength(0); + mPositionFromBottom = false; + mPositionFromLeft = false; + mPositionFromRight = false; + mPositionFromCenterVertical = false; + + mBindLeftCutout = false; + mBindRightCutout = false; + mBindBottomCutout = false; + } + + private void translateMatrix() { + final float offsetX; + if (mPositionFromRight) { + offsetX = mDisplayWidth; + } else if (mPositionFromLeft) { + offsetX = 0; + } else { + offsetX = mDisplayWidth / 2f; + } + + final float offsetY; + if (mPositionFromBottom) { + offsetY = mDisplayHeight; + } else if (mPositionFromCenterVertical) { + offsetY = mDisplayHeight / 2f; + } else { + offsetY = 0; + } + + mMatrix.reset(); + if (mInDp) { + mMatrix.postScale(mDensity, mDensity); + } + mMatrix.postTranslate(offsetX, offsetY); + } + + private int computeSafeInsets(int gravity, Rect rect) { + if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) { + return rect.right; + } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) { + return rect.bottom; + } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) { + return mDisplayWidth - rect.left; + } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) { + return mDisplayHeight - rect.top; + } + return 0; + } + + private void setSafeInset(int gravity, int inset) { + if (gravity == LEFT) { + mSafeInsetLeft = inset; + } else if (gravity == TOP) { + mSafeInsetTop = inset; + } else if (gravity == RIGHT) { + mSafeInsetRight = inset; + } else if (gravity == BOTTOM) { + mSafeInsetBottom = inset; + } + } + + private int getSafeInset(int gravity) { + if (gravity == LEFT) { + return mSafeInsetLeft; + } else if (gravity == TOP) { + return mSafeInsetTop; + } else if (gravity == RIGHT) { + return mSafeInsetRight; + } else if (gravity == BOTTOM) { + return mSafeInsetBottom; + } + return 0; + } + + @NonNull + private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) { + final int gravity; + if (isShortEdge) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart); + } else { + if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart); + } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, + mIsCloserToStartSide); + } else { + gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart); + } + } + + int oldSafeInset = getSafeInset(gravity); + int newSafeInset = computeSafeInsets(gravity, rect); + if (oldSafeInset < newSafeInset) { + setSafeInset(gravity, newSafeInset); + } + + return new Rect(rect); + } + + private void setEdgeCutout(@NonNull Path newPath) { + if (mBindRightCutout && mRightBound == null) { + mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindLeftCutout && mLeftBound == null) { + mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindBottomCutout && mBottomBound == null) { + mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect); + } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout) + && mTopBound == null) { + mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect); + } else { + return; + } + + if (mPath != null) { + mPath.addPath(newPath); + } else { + mPath = newPath; + } + } + + private void parseSvgPathSpec(Region region, String spec) { + if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) { + Log.e(TAG, "According to SVG definition, it shouldn't happen"); + return; + } + spec.trim(); + translateMatrix(); + + final Path newPath = PathParser.createPathFromPathData(spec); + newPath.transform(mMatrix); + computeBoundsRectAndAddToRegion(newPath, region, mTmpRect); + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b", + mPositionFromLeft, mPositionFromRight, mPositionFromBottom, + mPositionFromCenterVertical)); + Log.d(TAG, "region = " + region); + Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath); + } + + if (mTmpRect.isEmpty()) { + return; + } + + if (mIsShortEdgeOnTop) { + mIsTouchShortEdgeStart = mTmpRect.top <= 0; + mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight; + mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2; + } else { + mIsTouchShortEdgeStart = mTmpRect.left <= 0; + mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth; + mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2; + } + + setEdgeCutout(newPath); + } + + private void parseSpecWithoutDp(@NonNull String specWithoutDp) { + Region region = Region.obtain(); + StringBuilder sb = null; + int currentIndex = 0; + int lastIndex = 0; + while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) { + if (sb == null) { + sb = new StringBuilder(specWithoutDp.length()); + } + sb.append(specWithoutDp, lastIndex, currentIndex); + + if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) { + if (!mPositionFromRight) { + mPositionFromLeft = true; + } + currentIndex += LEFT_MARKER.length(); + } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) { + if (!mPositionFromLeft) { + mPositionFromRight = true; + } + currentIndex += RIGHT_MARKER.length(); + } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) { + if (!mPositionFromCenterVertical) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += BOTTOM_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mBindBottomCutout = true; + mPositionFromBottom = true; + } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) { + if (!mPositionFromBottom) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += CENTER_VERTICAL_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mPositionFromCenterVertical = true; + } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) { + parseSvgPathSpec(region, sb.toString()); + currentIndex += CUTOUT_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindRightCutout) { + mBindLeftCutout = true; + } + currentIndex += BIND_LEFT_CUTOUT_MARKER.length(); + } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindLeftCutout) { + mBindRightCutout = true; + } + currentIndex += BIND_RIGHT_CUTOUT_MARKER.length(); + } else { + currentIndex += 1; + } + + lastIndex = currentIndex; + } + + if (sb == null) { + parseSvgPathSpec(region, specWithoutDp); + } else { + sb.append(specWithoutDp, lastIndex, specWithoutDp.length()); + parseSvgPathSpec(region, sb.toString()); + } + + region.recycle(); + } + + /** + * To parse specification string as the CutoutSpecification. + * + * @param originalSpec the specification string + * @return the CutoutSpecification instance + */ + @VisibleForTesting(visibility = PACKAGE) + public CutoutSpecification parse(@NonNull String originalSpec) { + Objects.requireNonNull(originalSpec); + + int dpIndex = originalSpec.lastIndexOf(DP_MARKER); + mInDp = (dpIndex != -1); + final String spec; + if (dpIndex != -1) { + spec = originalSpec.substring(0, dpIndex) + + originalSpec.substring(dpIndex + DP_MARKER.length()); + } else { + spec = originalSpec; + } + + parseSpecWithoutDp(spec); + + mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom); + return new CutoutSpecification(this); + } + } +} diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index d433591f2b3c..31fc16188814 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -31,18 +31,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.graphics.Insets; -import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Region.Op; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; import android.util.Pair; -import android.util.PathParser; import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -63,10 +57,6 @@ import java.util.List; public final class DisplayCutout { private static final String TAG = "DisplayCutout"; - private static final String BOTTOM_MARKER = "@bottom"; - private static final String DP_MARKER = "@dp"; - private static final String RIGHT_MARKER = "@right"; - private static final String LEFT_MARKER = "@left"; /** * Category for overlays that allow emulating a display cutout on devices that don't have @@ -703,77 +693,16 @@ public final class DisplayCutout { } } - Path p = null; - Rect boundTop = null; - Rect boundBottom = null; - Rect safeInset = new Rect(); - String bottomSpec = null; - if (!TextUtils.isEmpty(spec)) { - spec = spec.trim(); - final float offsetX; - if (spec.endsWith(RIGHT_MARKER)) { - offsetX = displayWidth; - spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); - } else if (spec.endsWith(LEFT_MARKER)) { - offsetX = 0; - spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); - } else { - offsetX = displayWidth / 2f; - } - final boolean inDp = spec.endsWith(DP_MARKER); - if (inDp) { - spec = spec.substring(0, spec.length() - DP_MARKER.length()); - } - - if (spec.contains(BOTTOM_MARKER)) { - String[] splits = spec.split(BOTTOM_MARKER, 2); - spec = splits[0].trim(); - bottomSpec = splits[1].trim(); - } + spec = spec.trim(); - final Matrix m = new Matrix(); - final Region r = Region.obtain(); - if (!spec.isEmpty()) { - try { - p = PathParser.createPathFromPathData(spec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate cutout: ", e); - } - - if (p != null) { - if (inDp) { - m.postScale(density, density); - } - m.postTranslate(offsetX, 0); - p.transform(m); + CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, + displayWidth, displayHeight).parse(spec); + Rect safeInset = cutoutSpec.getSafeInset(); + final Rect boundLeft = cutoutSpec.getLeftBound(); + final Rect boundTop = cutoutSpec.getTopBound(); + final Rect boundRight = cutoutSpec.getRightBound(); + final Rect boundBottom = cutoutSpec.getBottomBound(); - boundTop = new Rect(); - toRectAndAddToRegion(p, r, boundTop); - safeInset.top = boundTop.bottom; - } - } - - if (bottomSpec != null) { - int bottomInset = 0; - Path bottomPath = null; - try { - bottomPath = PathParser.createPathFromPathData(bottomSpec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate bottom cutout: ", e); - } - - if (bottomPath != null) { - // Keep top transform - m.postTranslate(0, displayHeight); - bottomPath.transform(m); - p.addPath(bottomPath); - boundBottom = new Rect(); - toRectAndAddToRegion(bottomPath, r, boundBottom); - bottomInset = displayHeight - boundBottom.top; - } - safeInset.bottom = bottomInset; - } - } if (!waterfallInsets.equals(Insets.NONE)) { safeInset.set( @@ -784,9 +713,9 @@ public final class DisplayCutout { } final DisplayCutout cutout = new DisplayCutout( - safeInset, waterfallInsets, null /* boundLeft */, boundTop, - null /* boundRight */, boundBottom, false /* copyArguments */); - final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); + safeInset, waterfallInsets, boundLeft, boundTop, + boundRight, boundBottom, false /* copyArguments */); + final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { sCachedSpec = spec; sCachedDisplayWidth = displayWidth; @@ -798,14 +727,6 @@ public final class DisplayCutout { return result; } - private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { - final RectF rectF = new RectF(); - p.computeBounds(rectF, false /* unused */); - rectF.round(inoutRect); - inoutRegion.op(inoutRect, Op.UNION); - } - - private static Insets loadWaterfallInset(Resources res) { return Insets.of( res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index f9a023fafc3c..17303478fa50 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -97,6 +97,8 @@ interface IWindowManager IWindowSession openSession(in IWindowSessionCallback callback); + boolean useBLAST(); + @UnsupportedAppUsage void getInitialDisplaySize(int displayId, out Point size); @UnsupportedAppUsage diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index d4961eab7473..6caa4fed6409 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -198,7 +198,7 @@ public class InsetsSource implements Parcelable { return "InsetsSource: {" + "mType=" + InsetsState.typeToString(mType) + ", mFrame=" + mFrame.toShortString() - + ", mVisible" + mVisible + + ", mVisible=" + mVisible + "}"; } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 78a080de20e5..4ac6a666a21b 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -258,14 +258,14 @@ public class Surface implements Parcelable { */ public void release() { synchronized (mLock) { - if (mNativeObject != 0) { - nativeRelease(mNativeObject); - setNativeObjectLocked(0); - } if (mHwuiContext != null) { mHwuiContext.destroy(); mHwuiContext = null; } + if (mNativeObject != 0) { + nativeRelease(mNativeObject); + setNativeObjectLocked(0); + } } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index b1c354f6f717..637a088e4c5d 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -22,7 +22,6 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; @@ -43,8 +42,8 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.SurfaceControlViewHost; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.view.SurfaceCallbackHelper; @@ -386,7 +385,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); viewRoot.registerRtFrameCallback(frame -> { try { final SurfaceControl.Transaction t = useBLAST ? @@ -1120,7 +1119,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { - if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (frameNumber > 0 && !WindowManagerGlobal.getInstance().useBLAST()) { final ViewRootImpl viewRoot = getViewRootImpl(); t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(), @@ -1138,7 +1137,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void setParentSpaceRectangle(Rect position, long frameNumber) { - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); final ViewRootImpl viewRoot = getViewRootImpl(); final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() : mRtTransaction; @@ -1199,7 +1198,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { - boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); if (DEBUG) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); @@ -1538,7 +1537,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void invalidate(boolean invalidateCache) { super.invalidate(invalidateCache); - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!WindowManagerGlobal.getInstance().useBLAST()) { return; } final ViewRootImpl viewRoot = getViewRootImpl(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a6f8fad817e1..f99c96584585 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28989,6 +28989,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener; /** + * The leash token of this view's parent when it's in an embedded hierarchy that is + * re-parented to another window. + */ + IBinder mLeashedParentToken; + + /** + * The accessibility view id of this view's parent when it's in an embedded + * hierarchy that is re-parented to another window. + */ + int mLeashedParentAccessibilityViewId; + + /** * Creates a new set of attachment information with the specified * events handler and thread. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 159b93eb12dd..fa4fafaf3b26 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -135,6 +135,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; @@ -315,6 +316,8 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mForceNextConfigUpdate; + private final boolean mUseBLASTAdapter; + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. @@ -353,6 +356,8 @@ public final class ViewRootImpl implements ViewParent, final W mWindow; + final IBinder mLeashToken; + final int mTargetSdkVersion; int mSeq; @@ -650,6 +655,8 @@ public final class ViewRootImpl implements ViewParent, private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); + private IAccessibilityEmbeddedConnection mEmbeddedConnection; + static final class SystemUiVisibilityInfo { int seq; int globalVisibility; @@ -683,6 +690,7 @@ public final class ViewRootImpl implements ViewParent, mVisRect = new Rect(); mWinFrame = new Rect(); mWindow = new W(this); + mLeashToken = new Binder(); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; mViewVisibility = View.GONE; mTransparentRegion = new Region(); @@ -734,6 +742,7 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); + mUseBLASTAdapter = WindowManagerGlobal.getInstance().useBLAST(); } public static void addFirstDrawHandler(Runnable callback) { @@ -861,7 +870,7 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -1341,7 +1350,7 @@ public final class ViewRootImpl implements ViewParent, } mWindowAttributes.privateFlags |= compatibleWindowFlag; - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -7342,7 +7351,7 @@ public final class ViewRootImpl implements ViewParent, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); } else { mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, @@ -9154,6 +9163,10 @@ public final class ViewRootImpl implements ViewParent, focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } } + if (mAttachInfo.mLeashedParentToken != null) { + mAccessibilityManager.associateEmbeddedHierarchy( + mAttachInfo.mLeashedParentToken, mLeashToken); + } } else { ensureNoConnection(); mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget(); @@ -9166,6 +9179,7 @@ public final class ViewRootImpl implements ViewParent, if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mLeashToken, mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } @@ -9352,6 +9366,17 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Gets an accessibility embedded connection interface for this ViewRootImpl. + * @hide + */ + public IAccessibilityEmbeddedConnection getEmbeddedConnection() { + if (mEmbeddedConnection == null) { + mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this); + } + return mEmbeddedConnection; + } + private class SendWindowContentChangedAccessibilityEvent implements Runnable { private int mChangeTypes = 0; @@ -9537,7 +9562,7 @@ public final class ViewRootImpl implements ViewParent, } SurfaceControl getRenderSurfaceControl() { - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { return mBlastSurfaceControl; } else { return mSurfaceControl; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index f501de91b3b7..ea8e73885980 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,8 +16,6 @@ package android.view; -import static android.view.WindowInsets.Type.ime; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -148,7 +146,7 @@ public interface WindowInsetsController { * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis Duration of animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration.T his value will be + * animation doesn't have a predetermined duration. This value will be * passed to {@link InsetsAnimation#getDurationMillis()} * @param interpolator The interpolator used for this animation, or {@code null} if this * animation doesn't follow an interpolation curve. This value will be diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index f03c4e731283..c22b8921390c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -56,13 +56,7 @@ import java.util.ArrayList; public final class WindowManagerGlobal { private static final String TAG = "WindowManager"; - private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; - - /** - * This flag controls whether ViewRootImpl will utilize the Blast Adapter - * to send buffer updates to SurfaceFlinger - */ - public static final boolean USE_BLAST_ADAPTER = false; + private final boolean mUseBLASTAdapter; /** * The user is navigating with keys (not the touch screen), so @@ -165,6 +159,11 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; private WindowManagerGlobal() { + try { + mUseBLASTAdapter = getWindowManagerService().useBLAST(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } @UnsupportedAppUsage @@ -233,6 +232,13 @@ public final class WindowManagerGlobal { } } + /** + * Whether or not to use BLAST for ViewRootImpl + */ + public boolean useBLAST() { + return mUseBLASTAdapter; + } + @UnsupportedAppUsage public String[] getViewRootNames() { synchronized (mLock) { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 02b098b27974..dc87453bd867 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1086,6 +1086,50 @@ public final class AccessibilityManager { } /** + * Associate the connection between the host View and the embedded SurfaceControlViewHost. + * + * @hide + */ + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.associateEmbeddedHierarchy(host, embedded); + } catch (RemoteException e) { + return; + } + } + + /** + * Disassociate the connection between the host View and the embedded SurfaceControlViewHost. + * The given token could be either from host side or embedded side. + * + * @hide + */ + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + if (token == null) { + return; + } + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.disassociateEmbeddedHierarchy(token); + } catch (RemoteException e) { + return; + } + } + + /** * Sets the current state and notifies listeners, if necessary. * * @param stateFlags The state flags. @@ -1147,11 +1191,12 @@ public final class AccessibilityManager { /** * Adds an accessibility interaction connection interface for a given window. * @param windowToken The window token to which a connection is added. + * @param leashToken The leash token to which a connection is added. * @param connection The connection. * * @hide */ - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, String packageName, IAccessibilityInteractionConnection connection) { final IAccessibilityManager service; final int userId; @@ -1163,8 +1208,8 @@ public final class AccessibilityManager { userId = mUserId; } try { - return service.addAccessibilityInteractionConnection(windowToken, connection, - packageName, userId); + return service.addAccessibilityInteractionConnection(windowToken, leashToken, + connection, packageName, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl new file mode 100644 index 000000000000..707099ef09f4 --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +/** + * Interface used by host View to talk to the view root of the embedded SurfaceControlViewHost + * that actually implements the functionality. + * + * @hide + */ +interface IAccessibilityEmbeddedConnection { + + IBinder associateEmbeddedHierarchy(IBinder hostToken, int sourceId); + + void disassociateEmbeddedHierarchy(); + + oneway void setScreenMatrix(in float[] matrixValues); +} diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 7f8fdf83995a..97036f3f3a1b 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -47,7 +47,7 @@ interface IAccessibilityManager { @UnsupportedAppUsage List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId); - int addAccessibilityInteractionConnection(IWindow windowToken, + int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, in IAccessibilityInteractionConnection connection, String packageName, int userId); @@ -88,4 +88,8 @@ interface IAccessibilityManager { oneway void registerSystemAction(in RemoteAction action, int actionId); oneway void unregisterSystemAction(int actionId); oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection); + + void associateEmbeddedHierarchy(IBinder host, IBinder embedded); + + void disassociateEmbeddedHierarchy(IBinder token); } diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java index 4329a206746d..fed3dbf8f49c 100644 --- a/core/java/android/view/textclassifier/TextClassificationSession.java +++ b/core/java/android/view/textclassifier/TextClassificationSession.java @@ -16,6 +16,7 @@ package android.view.textclassifier; +import android.annotation.NonNull; import android.annotation.WorkerThread; import android.view.textclassifier.SelectionEvent.InvocationMethod; @@ -23,6 +24,8 @@ import com.android.internal.util.Preconditions; import java.util.Objects; +import sun.misc.Cleaner; + /** * Session-aware TextClassifier. */ @@ -35,6 +38,7 @@ final class TextClassificationSession implements TextClassifier { private final SelectionEventHelper mEventHelper; private final TextClassificationSessionId mSessionId; private final TextClassificationContext mClassificationContext; + private final Cleaner mCleaner; private boolean mDestroyed; @@ -44,6 +48,8 @@ final class TextClassificationSession implements TextClassifier { mSessionId = new TextClassificationSessionId(); mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext); initializeRemoteSession(); + // This ensures destroy() is called if the client forgot to do so. + mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate)); } @Override @@ -114,8 +120,7 @@ final class TextClassificationSession implements TextClassifier { @Override public void destroy() { - mEventHelper.endSession(); - mDelegate.destroy(); + mCleaner.clean(); mDestroyed = true; } @@ -258,4 +263,25 @@ final class TextClassificationSession implements TextClassifier { } } } + + // We use a static nested class here to avoid retaining the object reference of the outer + // class. Otherwise. the Cleaner would never be triggered. + private static class CleanerRunnable implements Runnable { + @NonNull + private final SelectionEventHelper mEventHelper; + @NonNull + private final TextClassifier mDelegate; + + CleanerRunnable( + @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) { + mEventHelper = Objects.requireNonNull(eventHelper); + mDelegate = Objects.requireNonNull(delegate); + } + + @Override + public void run() { + mEventHelper.endSession(); + mDelegate.destroy(); + } + } } diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java index f90e6b2e407c..0b6fba225280 100644 --- a/core/java/android/view/textclassifier/TextClassificationSessionId.java +++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -28,7 +30,10 @@ import java.util.UUID; * This class represents the id of a text classification session. */ public final class TextClassificationSessionId implements Parcelable { - private final @NonNull String mValue; + @NonNull + private final String mValue; + @NonNull + private final IBinder mToken; /** * Creates a new instance. @@ -36,7 +41,7 @@ public final class TextClassificationSessionId implements Parcelable { * @hide */ public TextClassificationSessionId() { - this(UUID.randomUUID().toString()); + this(UUID.randomUUID().toString(), new Binder()); } /** @@ -46,34 +51,28 @@ public final class TextClassificationSessionId implements Parcelable { * * @hide */ - public TextClassificationSessionId(@NonNull String value) { - mValue = value; + public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) { + mValue = Objects.requireNonNull(value); + mToken = Objects.requireNonNull(token); + } + + /** @hide */ + @NonNull + public IBinder getToken() { + return mToken; } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mValue.hashCode(); - return result; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TextClassificationSessionId that = (TextClassificationSessionId) o; + return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken); } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - TextClassificationSessionId other = (TextClassificationSessionId) obj; - if (!mValue.equals(other.mValue)) { - return false; - } - return true; + public int hashCode() { + return Objects.hash(mValue, mToken); } @Override @@ -84,6 +83,7 @@ public final class TextClassificationSessionId implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mValue); + parcel.writeStrongBinder(mToken); } @Override @@ -96,28 +96,18 @@ public final class TextClassificationSessionId implements Parcelable { * * @return The flattened id. */ - public @NonNull String flattenToString() { + @NonNull + public String flattenToString() { return mValue; } - /** - * Unflattens a print job id from a string. - * - * @param string The string. - * @return The unflattened id, or null if the string is malformed. - * - * @hide - */ - public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) { - return new TextClassificationSessionId(string); - } - - public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationSessionId> CREATOR = + @NonNull + public static final Parcelable.Creator<TextClassificationSessionId> CREATOR = new Parcelable.Creator<TextClassificationSessionId>() { @Override public TextClassificationSessionId createFromParcel(Parcel parcel) { return new TextClassificationSessionId( - Objects.requireNonNull(parcel.readString())); + parcel.readString(), parcel.readStrongBinder()); } @Override diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 8c52d1fadc52..9e4ebfe44b58 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -460,8 +460,19 @@ public class Toast { * @hide */ @UnsupportedAppUsage - public WindowManager.LayoutParams getWindowParams() { - return mTN.mParams; + @Nullable public WindowManager.LayoutParams getWindowParams() { + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { + if (mNextView != null) { + // Custom toasts + return mTN.mParams; + } else { + // Text toasts + return null; + } + } else { + // Text and custom toasts are app-rendered + return mTN.mParams; + } } /** diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 417e23f56448..f3b6d292623d 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -38,10 +38,8 @@ interface IVoiceInteractionManagerService { IVoiceInteractor interactor); boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags); boolean hideSessionFromSession(IBinder token); - int startVoiceActivity(IBinder token, in Intent intent, String resolvedType, - String callingFeatureId); - int startAssistantActivity(IBinder token, in Intent intent, String resolvedType, - String callingFeatureId); + int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); + int startAssistantActivity(IBinder token, in Intent intent, String resolvedType); void setKeepAwake(IBinder token, boolean keepAwake); void closeSystemDialogs(IBinder token); void finish(IBinder token); diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 3322834c9ed1..086b9d83fed5 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -377,6 +377,11 @@ public final class SystemUiDeviceConfigFlags { public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN = "nav_bar_handle_show_over_lockscreen"; + /** + * (boolean) Whether to enable user-drag resizing for PIP. + */ + public static final String PIP_USER_RESIZE = "pip_user_resize"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 76e7e191d293..8959d6fb845e 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -360,7 +360,6 @@ cc_library_static { "android_view_RenderNode.cpp", "android_util_PathParser.cpp", - "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/Bitmap.cpp", "android/graphics/BitmapFactory.cpp", "android/graphics/ByteBufferStreamAdaptor.cpp", @@ -436,6 +435,7 @@ cc_library_static { "android_view_TextureLayer.cpp", "android_view_ThreadedRenderer.cpp", + "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/BitmapRegionDecoder.cpp", "android/graphics/GIFMovie.cpp", "android/graphics/Movie.cpp", diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 571a33879411..c49c3a0f6b61 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -40,6 +40,7 @@ extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); @@ -115,6 +116,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, + {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index d41e98241b07..44bff0188544 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -37,10 +37,28 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env, return (jboolean)IncFs_IsIncFsPath(path.c_str()); } -static const JNINativeMethod method_table[] = { - {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, - {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, -}; +static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + + char signature[INCFS_MAX_SIGNATURE_SIZE]; + size_t size = sizeof(signature); + if (IncFs_UnsafeGetSignatureByPath(path.c_str(), signature, &size) < 0) { + return nullptr; + } + + jbyteArray result = env->NewByteArray(size); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, size, (const jbyte*)signature); + } + return result; +} + +static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, + {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", + (void*)nativeIsIncrementalPath}, + {"nativeUnsafeGetFileSignature", + "(Ljava/lang/String;)[B", + (void*)nativeUnsafeGetFileSignature}}; int register_android_os_incremental_IncrementalManager(JNIEnv* env) { return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager", diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 92941b8cb22b..7290e3031d21 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -611,6 +611,12 @@ static void PreApplicationInit() { // Set the jemalloc decay time to 1. mallopt(M_DECAY_TIME, 1); + + // Maybe initialize GWP-ASan here. Must be called after + // mallopt(M_SET_ZYGOTE_CHILD). + bool ForceEnableGwpAsan = false; + android_mallopt(M_INITIALIZE_GWP_ASAN, &ForceEnableGwpAsan, + sizeof(ForceEnableGwpAsan)); } static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 4625418c1581..53aa2bbd57ea 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -714,6 +714,16 @@ enum Action { // ACTION: Battery feature runtime event ACTION_BATTERY_OPTION_RUNTIME_EVENT = 1733; + + // ACTION: Settings > Developer Options > Toggle on Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_ON = 1734; + + // ACTION: Settings > Developer Options > Toggle off Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_OFF = 1735; } /** @@ -2591,4 +2601,20 @@ enum PageId { // OPEN: Settings > Notifications > (app or conversations) > conversation NOTIFICATION_CONVERSATION_SETTINGS = 1830; + // OPEN: Settings > Developer Options > Wireless debugging + // CATEGORY: SETTINGS + // OS: R + SETTINGS_ADB_WIRELESS = 1831; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with pairing code > Pairing code dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_PAIRING_DIALOG = 1832; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with QR code > Scan QR code > Pairing device dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833; } diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto new file mode 100644 index 000000000000..4f0ce6b19e70 --- /dev/null +++ b/core/proto/android/content/locusid.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto2"; + +package android.content; + +option java_multiple_files = true; + +// On disk representation of android.content.LocusId. Currently used by +// com.android.server.people.ConversationInfoProto. +message LocusIdProto { + optional string locus_id = 1; +} diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto new file mode 100644 index 000000000000..294b6efd3cbe --- /dev/null +++ b/core/proto/android/server/peopleservice.proto @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto2"; + +package com.android.server.people; + +option java_multiple_files = true; + +import "frameworks/base/core/proto/android/content/locusid.proto"; + +// On disk data of conversation infos for a user and app package. +message ConversationInfosProto { + + // The series of conversation infos for a user and app package. + repeated ConversationInfoProto conversation_infos = 1; +} + +// Individual conversation info (com.android.server.people.data.ConversationInfo) for a user +// and app package. +message ConversationInfoProto { + + // The conversation's shortcut id. + optional string shortcut_id = 1; + + // The conversation's locus id. + optional .android.content.LocusIdProto locus_id_proto = 2; + + // The URI of the contact in the conversation. + optional string contact_uri = 3; + + // The notification channel id of the conversation. + optional string notification_channel_id = 4; + + // Integer representation of shortcut bit flags. + optional int32 shortcut_flags = 5; + + // Integer representation of conversation bit flags. + optional int32 conversation_flags = 6; +} + +// Individual event (com.android.server.people.data.Event). +message PeopleEventProto { + + // For valid values, refer to java class documentation. + optional int32 event_type = 1; + + optional int64 time = 2; + + // The duration of the event. Should only be set for some event_types. Refer to java class + // documentation for details. + optional int32 duration = 3; +} + +// Index of events' time distributions (com.android.server.people.data.EventIndex). +message PeopleEventIndexProto { + // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further + // details can be found in class documentation. + repeated int64 event_bitmaps = 1; + + optional int64 last_updated_time = 2; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ff696715e94d..71a42e454f30 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -96,6 +96,7 @@ <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" /> + <protected-broadcast android:name="android.intent.action.LOAD_DATA" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" /> @@ -2376,6 +2377,7 @@ @hide --> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature|installer|telephony" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <!-- Allows interaction across profiles in the same profile group. --> <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 20901e04e6c5..7d8b8db9d4a0 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3790,6 +3790,12 @@ <attr name="description" /> <!-- Brief summary of the target of accessibility shortcut purpose or behavior. --> <attr name="summary" /> + <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help + users understand how the target of accessibility shortcut can help them.--> + <attr name="animatedImageDrawable" format="reference"/> + <!-- Html description of the target of accessibility shortcut purpose or behavior, to help + users understand how the target of accessibility shortcut can help them. --> + <attr name="htmlDescription" format="string"/> </declare-styleable> <!-- Use <code>print-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b22e1867f257..c66261bb6630 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2056,6 +2056,9 @@ <attr name="name" /> </declare-styleable> <declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" /> + <declare-styleable name="AndroidManifestQueriesProvider" parent="AndroidManifestQueries" > + <attr name="authorities" /> + </declare-styleable> <!-- The <code>static-library</code> tag declares that this apk is providing itself diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f17cd45cd880..df6330654e77 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2351,7 +2351,7 @@ type. These are flags and can be freely combined. 0 - disable whitelist (install all system packages; no logging) 1 - enforce (only install system packages if they are whitelisted) - 2 - log (log when a non-whitelisted package is run) + 2 - log (log non-whitelisted packages) 4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users 8 - same as 4, but just for the SYSTEM user 16 - ignore OTAs (don't install system packages during OTAs) @@ -3792,6 +3792,10 @@ or empty if the default should be used. --> <string translatable="false" name="config_deviceSpecificAudioService"></string> + <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider + or empty if the default should be used. --> + <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string> + <!-- Component name of media projection permission dialog --> <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a54566cfed17..08c840380fe5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -414,6 +414,14 @@ logging. [CHAR LIMIT=NONE]--> <string name="network_logging_notification_text">Your organization manages this device and may monitor network traffic. Tap for details.</string> + <!-- Content title for a notification. This notification indicates that the device owner has + changed the location settings. [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_title">Location settings changed by your admin</string> + <!-- Content text for a notification. Tapping opens device location settings. + [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_text">Tap to see your location settings.</string> + + <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> <string name="factory_reset_warning">Your device will be erased</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2dd62c176e98..0babe48e832e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1213,6 +1213,8 @@ <java-symbol type="string" name="device_ownership_relinquished" /> <java-symbol type="string" name="network_logging_notification_title" /> <java-symbol type="string" name="network_logging_notification_text" /> + <java-symbol type="string" name="location_changed_notification_title" /> + <java-symbol type="string" name="location_changed_notification_text" /> <java-symbol type="string" name="personal_apps_suspended_notification_title" /> <java-symbol type="string" name="personal_apps_suspended_notification_text" /> <java-symbol type="string" name="factory_reset_warning" /> @@ -3864,6 +3866,8 @@ <!-- Toast message for background started foreground service while-in-use permission restriction feature --> <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> + <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" /> + <!-- Whether to expand the lock screen user switcher by default --> <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" /> </resources> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 718ca46a4f18..59335a595334 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1330,12 +1330,6 @@ android:process=":FakeProvider"> </provider> - <provider - android:name="android.content.SlowProvider" - android:authorities="android.content.SlowProvider" - android:process=":SlowProvider"> - </provider> - <!-- Application components used for os tests --> <service android:name="android.os.MessengerService" diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index f630188e54dc..21613a82deef 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -148,4 +148,7 @@ <!-- Summary of the accessibility shortcut [CHAR LIMIT=NONE] --> <string name="accessibility_shortcut_summary">Accessibility shortcut summary</string> + + <!-- Html description of the accessibility shortcut [CHAR LIMIT=NONE] --> + <string name="accessibility_shortcut_html_description">Accessibility shortcut html description</string> </resources> diff --git a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml index 60e29989ef0d..a597b7190fd7 100644 --- a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml +++ b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml @@ -19,4 +19,6 @@ <accessibility-shortcut-target xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_shortcut_description" android:summary="@string/accessibility_shortcut_summary" + android:animatedImageDrawable="@drawable/bitmap_drawable" + android:htmlDescription="@string/accessibility_shortcut_html_description" />
\ No newline at end of file diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java index ae6d8df26feb..82a7b2c9217e 100644 --- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java @@ -84,6 +84,22 @@ public class AccessibilityShortcutInfoTest { } @Test + public void testAnimatedImageRes() { + assertThat("Animated image resource id is not correct", + mShortcutInfo.getAnimatedImageRes(), is(R.drawable.bitmap_drawable)); + } + + @Test + public void testHtmlDescription() { + final String htmlDescription = mTargetContext.getResources() + .getString(R.string.accessibility_shortcut_html_description); + + assertNotNull("Can't find html description string", htmlDescription); + assertThat("Html description is not correct", + mShortcutInfo.loadHtmlDescription(mPackageManager), is(htmlDescription)); + } + + @Test public void testEquals() { assertTrue(mShortcutInfo.equals(mShortcutInfo)); assertFalse(mShortcutInfo.equals(null)); diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 6dc7392945d8..9dcce1e51e0b 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -209,13 +209,4 @@ public class ContentResolverTest { String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote")); assertEquals("fake/remote", type); } - - - @Test - public void testGetType_slowProvider() { - // This provider is running in a different process and is intentionally slow to start. - // We are trying to confirm that it does not cause an ANR - String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider")); - assertEquals("slow", type); - } } diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java deleted file mode 100644 index aba32e836e80..000000000000 --- a/core/tests/coretests/src/android/content/SlowProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 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.content; - -import android.database.Cursor; -import android.net.Uri; - -/** - * A dummy content provider for tests. This provider runs in a different process from the test and - * is intentionally slow. - */ -public class SlowProvider extends ContentProvider { - - private static final int ON_CREATE_LATENCY_MILLIS = 3000; - - @Override - public boolean onCreate() { - try { - Thread.sleep(ON_CREATE_LATENCY_MILLIS); - } catch (InterruptedException e) { - // Ignore - } - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return null; - } - - @Override - public String getType(Uri uri) { - return "slow"; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return 0; - } -} diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java new file mode 100644 index 000000000000..c897ace0e0b5 --- /dev/null +++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 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.content.integrity; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; + +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class InstallerAllowedByManifestFormulaTest { + + private static final InstallerAllowedByManifestFormula + FORMULA = new InstallerAllowedByManifestFormula(); + + @Test + public void testFormulaMatches_installerAndCertBothInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert1", + "installer2", "installer_cert2" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_installerAndCertDoesNotMatchInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_installerNotInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer3") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_certificateNotInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_emptyManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of()).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isTrue(); + } + + /** Returns a builder with all fields filled with some dummy data. */ + private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { + return new AppInstallMetadata.Builder() + .setPackageName("abc") + .setAppCertificates(Collections.emptyList()) + .setInstallerCertificates(Collections.emptyList()) + .setInstallerName("abc") + .setVersionCode(-1) + .setIsPreInstalled(true); + } +} diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index 75ef1f22b819..62c9c98f4e1d 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -20,8 +20,6 @@ import static android.content.integrity.IntegrityFormula.COMPOUND_FORMULA_TAG; import static com.google.common.truth.Truth.assertThat; -import static org.testng.Assert.assertThrows; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,8 +30,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_packageName() { String packageName = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); + IntegrityFormula formula = IntegrityFormula.Application.packageNameEquals(packageName); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -46,8 +43,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_appCertificate() { String appCertificate = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.APP_CERTIFICATE.equalTo(appCertificate); + IntegrityFormula formula = IntegrityFormula.Application.certificatesContain(appCertificate); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -60,8 +56,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_installerName() { String installerName = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.INSTALLER_NAME.equalTo(installerName); + IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -75,7 +70,7 @@ public class IntegrityFormulaTest { public void createEqualsFormula_installerCertificate() { String installerCertificate = "com.test.app"; IntegrityFormula formula = - IntegrityFormula.INSTALLER_CERTIFICATE.equalTo(installerCertificate); + IntegrityFormula.Installer.certificatesContain(installerCertificate); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -88,8 +83,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.equalTo(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeEquals(versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -100,24 +94,9 @@ public class IntegrityFormulaTest { } @Test - public void createEqualsFormula_invalidKeyTypeForStringParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PRE_INSTALLED.equalTo("wrongString")); - } - - @Test - public void createEqualsFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.equalTo(12)); - } - - @Test public void createGreaterThanFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.greaterThan(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThan(versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -128,17 +107,10 @@ public class IntegrityFormulaTest { } @Test - public void createGreaterThanFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.greaterThan(12)); - } - - @Test public void createGreaterThanOrEqualsToFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.greaterThanOrEquals(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThanOrEqualTo( + versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -149,15 +121,8 @@ public class IntegrityFormulaTest { } @Test - public void createGreaterThanOrEqualsToFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.greaterThanOrEquals(12)); - } - - @Test public void createIsTrueFormula_preInstalled() { - IntegrityFormula formula = IntegrityFormula.PRE_INSTALLED.equalTo(true); + IntegrityFormula formula = IntegrityFormula.Application.isPreInstalled(); AtomicFormula.BooleanAtomicFormula stringAtomicFormula = (AtomicFormula.BooleanAtomicFormula) formula; @@ -167,20 +132,12 @@ public class IntegrityFormulaTest { } @Test - public void createIsTrueFormula_invalidKeyTypeForBoolParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.equalTo(true)); - } - - @Test public void createAllFormula() { String packageName = "com.test.package"; String certificateName = "certificate"; - IntegrityFormula formula1 = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); - IntegrityFormula formula2 = - IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName); + IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); + IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( + certificateName); IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2); @@ -191,10 +148,9 @@ public class IntegrityFormulaTest { public void createAnyFormula() { String packageName = "com.test.package"; String certificateName = "certificate"; - IntegrityFormula formula1 = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); - IntegrityFormula formula2 = - IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName); + IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); + IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( + certificateName); IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2); @@ -206,8 +162,7 @@ public class IntegrityFormulaTest { String packageName = "com.test.package"; IntegrityFormula compoundFormula = - IntegrityFormula.not( - IntegrityFormula.PACKAGE_NAME.equalTo(packageName)); + IntegrityFormula.not(IntegrityFormula.Application.packageNameEquals(packageName)); assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG); } diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java index 2c956c990e97..669138c15698 100644 --- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java +++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java @@ -148,6 +148,15 @@ public class ConfigurationTest extends TestCase { assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, config.smallestScreenWidthDp); } + @Test + public void testNightModeHelper() { + Configuration config = new Configuration(); + config.uiMode = Configuration.UI_MODE_NIGHT_YES; + assertTrue(config.isNightModeActive()); + config.uiMode = Configuration.UI_MODE_NIGHT_NO; + assertFalse(config.isNightModeActive()); + } + private void dumpDebug(File f, Configuration config) throws Exception { final AtomicFile af = new AtomicFile(f); FileOutputStream fos = af.startWrite(); diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java index 49fb75bf6a45..b8dbfd3186c5 100644 --- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java +++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java @@ -466,179 +466,7 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT // expected } } - - @MediumTest - public void testTokenize() throws Exception { - Cursor c; - mDatabase.execSQL("CREATE TABLE tokens (" + - "token TEXT COLLATE unicode," + - "source INTEGER," + - "token_index INTEGER," + - "tag TEXT" + - ");"); - mDatabase.execSQL("CREATE TABLE tokens_no_index (" + - "token TEXT COLLATE unicode," + - "source INTEGER" + - ");"); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null)); - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null)); - - // test Chinese - String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca"); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null)); - - String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g"); - - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null)); - - Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens;", null)); - - String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("string"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("second field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey(chinese); - String[] a = new String[1]; - a[0] = key; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= ?", a)); - a[0] += "*"; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB ?", a)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= '" + key + "'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= '" + key + "'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= '" + key + "'", null)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca"); - Log.d("DatabaseGeneralTest", "key = " + key); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB 'ab*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("bar"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - } - + @MediumTest public void testTransactions() throws Exception { mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java new file mode 100644 index 000000000000..1f831bb2f9a8 --- /dev/null +++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2020 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.view; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.graphics.Rect; + +import org.junit.Before; +import org.junit.Test; + +public class CutoutSpecificationTest { + private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@dp"; + private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@dp"; + private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 1\n" + + "v 1\n" + + "h -1\n" + + "z\n" + + "@left\n" + + "@cutout\n" + + "M 0, 0\n" + + "h -2\n" + + "v 2\n" + + "h 2\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@cutout\n" + + "M 0, 200\n" + + "h 3\n" + + "v -3\n" + + "h -3\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@bottom\n" + + "M 0, 0\n" + + "h -4\n" + + "v -4\n" + + "h 4\n" + + "z\n" + + "@right\n" + + "@dp"; + + private CutoutSpecification.Parser mParser; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920); + } + + @Test + public void parse_nullString_shouldTriggerException() { + assertThrows(NullPointerException.class, () -> mParser.parse(null)); + } + + @Test + public void parse_emptyString_pathShouldBeNull() { + CutoutSpecification cutoutSpecification = mParser.parse(""); + assertThat(cutoutSpecification.getPath()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isNull(); + } + + @Test + public void parse_withBindMarker_shouldHaveLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128)); + } + + @Test + public void parse_withBindMarker_shouldHaveRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128)); + } + + @Test + public void parse_tallCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "L -44.3940446283, 36.0595537175\n" + + "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n" + + "L 31.2, 48.0\n" + + "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n" + + "L 48, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_wideCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504); + } + + @Test + public void parse_narrowCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -24, 0\n" + + "L -21.9940446283, 20.0595537175\n" + + "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n" + + "L 8.8, 32.0\n" + + "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n" + + "L 24, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168); + } + + @Test + public void parse_doubleCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20" + + ".0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112); + } + + @Test + public void parse_cornerCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "C -48,48 -48,48 0,48\n" + + "Z\n" + + "@dp\n" + + "@right"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_holeCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n" + + "h 136\n" + + "v 136\n" + + "h -136\n" + + "Z\n" + + "@left"); + + assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0)); + } + + @Test + public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 200, 400) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(0, 4, 0, 8)); + } + + @Test + public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 400, 200) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(6, 0, 8, 0)); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index a2d23558616b..8dbb5f544ad4 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -206,6 +206,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { blockModes, userAuthenticationRequired, (int) userAuthenticationValidityDurationSeconds, + keymasterHwEnforcedUserAuthenticators, userAuthenticationRequirementEnforcedBySecureHardware, userAuthenticationValidWhileOnBody, trustedUserPresenceRequred, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 450dd3301253..d683041fbfdc 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -263,6 +263,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final boolean mUserPresenceRequired; private final byte[] mAttestationChallenge; private final boolean mUniqueIdIncluded; @@ -301,6 +302,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, + @KeyProperties.AuthEnum int userAuthenticationType, boolean userPresenceRequired, byte[] attestationChallenge, boolean uniqueIdIncluded, @@ -352,6 +354,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationRequired = userAuthenticationRequired; mUserPresenceRequired = userPresenceRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + mUserAuthenticationType = userAuthenticationType; mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -605,6 +608,22 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Gets the modes of authentication that can authorize use of this key. This has effect only if + * user authentication is required (see {@link #isUserAuthenticationRequired()}). + * + * <p>This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @return integer representing the bitwse OR of all acceptable authentication types for the + * key. + * + * @see #isUserAuthenticationRequired() + * @see Builder#setUserAuthenticationParameters(int, int) + */ + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + /** * Returns {@code true} if the key is authorized to be used only if a test of user presence has * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. * It requires that the KeyStore implementation have a direct way to validate the user presence @@ -746,6 +765,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; + private @KeyProperties.AuthEnum int mUserAuthenticationType; private boolean mUserPresenceRequired = false; private byte[] mAttestationChallenge = null; private boolean mUniqueIdIncluded = false; @@ -810,6 +830,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired(); mUserAuthenticationValidityDurationSeconds = sourceSpec.getUserAuthenticationValidityDurationSeconds(); + mUserAuthenticationType = sourceSpec.getUserAuthenticationType(); mUserPresenceRequired = sourceSpec.isUserPresenceRequired(); mAttestationChallenge = sourceSpec.getAttestationChallenge(); mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded(); @@ -1207,14 +1228,62 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see BiometricPrompt * @see BiometricPrompt.CryptoObject * @see KeyguardManager + * @deprecated See {@link #setUserAuthenticationParameters(int, int)} */ + @Deprecated @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { if (seconds < -1) { throw new IllegalArgumentException("seconds must be -1 or larger"); } - mUserAuthenticationValidityDurationSeconds = seconds; + if (seconds == -1) { + return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + + /** + * Sets the duration of time (seconds) and authorization type for which this key is + * authorized to be used after the user is successfully authenticated. This has effect if + * the key requires user authentication for its use (see + * {@link #setUserAuthenticationRequired(boolean)}). + * + * <p>By default, if user authentication is required, it must take place for every use of + * the key. + * + * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during + * initialization if the user needs to be authenticated to proceed. This situation can be + * resolved by the user authenticating with the appropriate biometric or credential as + * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)} + * and {@link BiometricManager.Authenticators}. + * + * <p>Once resolved, initializing a new cryptographic operation using this key (or any other + * key which is authorized to be used for a fixed duration of time after user + * authentication) should succeed provided the user authentication flow completed + * successfully. + * + * @param timeout duration in seconds or {@code 0} if user authentication must take place + * for every use of the key. {@code -1} is also accepted for legacy purposes. It is + * functionally the same as {@code 0}. + * @param type set of authentication types which can authorize use of the key. See + * {@link KeyProperties}.{@code AUTH} flags. + * + * @see #setUserAuthenticationRequired(boolean) + * @see BiometricPrompt + * @see BiometricPrompt.CryptoObject + * @see KeyguardManager + */ + @NonNull + public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout, + @KeyProperties.AuthEnum int type) { + if (timeout < -1) { + throw new IllegalArgumentException("timeout must be -1 or larger"); + } else if (timeout == -1) { + timeout = 0; + } + mUserAuthenticationValidityDurationSeconds = timeout; + mUserAuthenticationType = type; return this; } @@ -1392,6 +1461,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mRandomizedEncryptionRequired, mUserAuthenticationRequired, mUserAuthenticationValidityDurationSeconds, + mUserAuthenticationType, mUserPresenceRequired, mAttestationChallenge, mUniqueIdIncluded, diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index 0a75cd5268b7..d891a25dba68 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -78,6 +78,7 @@ public class KeyInfo implements KeySpec { private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mTrustedUserPresenceRequired; @@ -101,6 +102,7 @@ public class KeyInfo implements KeySpec { @KeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, + @KeyProperties.AuthEnum int userAuthenticationType, boolean userAuthenticationRequirementEnforcedBySecureHardware, boolean userAuthenticationValidWhileOnBody, boolean trustedUserPresenceRequired, @@ -122,6 +124,7 @@ public class KeyInfo implements KeySpec { mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mUserAuthenticationRequired = userAuthenticationRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + mUserAuthenticationType = userAuthenticationType; mUserAuthenticationRequirementEnforcedBySecureHardware = userAuthenticationRequirementEnforcedBySecureHardware; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -301,6 +304,22 @@ public class KeyInfo implements KeySpec { } /** + * Gets the acceptable user authentication types for which this key can be authorized to be + * used. This has effect only if user authentication is required (see + * {@link #isUserAuthenticationRequired()}). + * + * <p>This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @return integer representing the accepted forms of user authentication for this key + * + * @see #isUserAuthenticationRequired() + */ + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + + /** * Returns {@code true} if the requirement that this key can only be used if the user has been * authenticated is enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or * Secure Element (SE)). diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index f12a659039ee..c58a1236d475 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -39,6 +39,27 @@ public abstract class KeyProperties { * @hide */ @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUTH_" }, value = { + AUTH_BIOMETRIC_STRONG, + AUTH_DEVICE_CREDENTIAL, + }) + public @interface AuthEnum {} + + /** + * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password) + */ + public static final int AUTH_DEVICE_CREDENTIAL = 1 << 0; + + /** + * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the + * requirements for <strong>Strong</strong>, as defined by the Android CDD. + */ + public static final int AUTH_BIOMETRIC_STRONG = 1 << 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "PURPOSE_" }, value = { PURPOSE_ENCRYPT, PURPOSE_DECRYPT, diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 26181a65dc1d..e230b7c3708b 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -225,6 +225,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserPresenceRequred; private final boolean mUserAuthenticationValidWhileOnBody; @@ -246,6 +247,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { @KeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, + @KeyProperties.AuthEnum int userAuthenticationType, int userAuthenticationValidityDurationSeconds, boolean userPresenceRequred, boolean userAuthenticationValidWhileOnBody, @@ -267,6 +269,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mRandomizedEncryptionRequired = randomizedEncryptionRequired; mUserAuthenticationRequired = userAuthenticationRequired; + mUserAuthenticationType = userAuthenticationType; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; mUserPresenceRequred = userPresenceRequred; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -429,6 +432,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return mUserConfirmationRequired; } + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -555,6 +562,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private @KeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; + private @KeyProperties.AuthEnum int mUserAuthenticationType; private int mUserAuthenticationValidityDurationSeconds = -1; private boolean mUserPresenceRequired = false; private boolean mUserAuthenticationValidWhileOnBody; @@ -850,14 +858,62 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see BiometricPrompt * @see BiometricPrompt.CryptoObject * @see KeyguardManager + * @deprecated See {@link #setUserAuthenticationParameters(int, int)} */ + @Deprecated @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { if (seconds < -1) { throw new IllegalArgumentException("seconds must be -1 or larger"); } - mUserAuthenticationValidityDurationSeconds = seconds; + if (seconds == -1) { + return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + + /** + * Sets the duration of time (seconds) and authorization type for which this key is + * authorized to be used after the user is successfully authenticated. This has effect if + * the key requires user authentication for its use (see + * {@link #setUserAuthenticationRequired(boolean)}). + * + * <p>By default, if user authentication is required, it must take place for every use of + * the key. + * + * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during + * initialization if the user needs to be authenticated to proceed. This situation can be + * resolved by the user authenticating with the appropriate biometric or credential as + * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)} + * and {@link BiometricManager.Authenticators}. + * + * <p>Once resolved, initializing a new cryptographic operation using this key (or any other + * key which is authorized to be used for a fixed duration of time after user + * authentication) should succeed provided the user authentication flow completed + * successfully. + * + * @param timeout duration in seconds or {@code 0} if user authentication must take place + * for every use of the key. {@code -1} is also accepted for legacy purposes. It is + * functionally the same as {@code 0}. + * @param type set of authentication types which can authorize use of the key. See + * {@link KeyProperties}.{@code AUTH} flags. + * + * @see #setUserAuthenticationRequired(boolean) + * @see BiometricPrompt + * @see BiometricPrompt.CryptoObject + * @see KeyguardManager + */ + @NonNull + public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout, + @KeyProperties.AuthEnum int type) { + if (timeout < -1) { + throw new IllegalArgumentException("timeout must be -1 or larger"); + } else if (timeout == -1) { + timeout = 0; + } + mUserAuthenticationValidityDurationSeconds = timeout; + mUserAuthenticationType = type; return this; } @@ -1002,6 +1058,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mBlockModes, mRandomizedEncryptionRequired, mUserAuthenticationRequired, + mUserAuthenticationType, mUserAuthenticationValidityDurationSeconds, mUserPresenceRequired, mUserAuthenticationValidWhileOnBody, diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 79e48cdfdd8e..37b1f23ba55a 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -88,17 +88,9 @@ public abstract class KeymasterUtils { * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. * - * @param userAuthenticationRequired whether user authentication is required to authorize the - * use of the key. - * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user - * authentication is valid as authorization for using the key or {@code -1} if every - * use of the key needs authorization. - * @param boundToSpecificSecureUserId if non-zero, specify which SID the key will be bound to, - * overriding the default logic in this method where the key is bound to either the root - * SID of the current user, or the fingerprint SID if explicit fingerprint authorization - * is requested. - * @param userConfirmationRequired whether user confirmation is required to authorize the use - * of the key. + * @param args The arguments sent to keymaster that need to be populated from the spec + * @param spec The user authentication relevant portions of the spec passed in from the caller. + * This spec will be translated into the relevant keymaster tags to be loaded into args. * @throws IllegalStateException if user authentication is required but the system is in a wrong * state (e.g., secure lock screen not set up) for generating or importing keys that * require user authentication. @@ -122,7 +114,7 @@ public abstract class KeymasterUtils { return; } - if (spec.getUserAuthenticationValidityDurationSeconds() == -1) { + if (spec.getUserAuthenticationValidityDurationSeconds() == 0) { PackageManager pm = KeyStore.getApplicationContext().getPackageManager(); // Every use of this key needs to be authorized by the user. This currently means // fingerprint or face auth. @@ -168,7 +160,8 @@ public abstract class KeymasterUtils { args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sids.get(i))); } - args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC); + + args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()); if (spec.isUserAuthenticationValidWhileOnBody()) { throw new ProviderException("Key validity extension while device is on-body is not " diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 98e458930a7f..9c9773e5d145 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -97,6 +97,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isRandomizedEncryptionRequired()); out.writeBoolean(mSpec.isUserAuthenticationRequired()); out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds()); + out.writeInt(mSpec.getUserAuthenticationType()); out.writeBoolean(mSpec.isUserPresenceRequired()); out.writeByteArray(mSpec.getAttestationChallenge()); out.writeBoolean(mSpec.isUniqueIdIncluded()); @@ -153,6 +154,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean randomizedEncryptionRequired = in.readBoolean(); final boolean userAuthenticationRequired = in.readBoolean(); final int userAuthenticationValidityDurationSeconds = in.readInt(); + final int userAuthenticationTypes = in.readInt(); final boolean userPresenceRequired = in.readBoolean(); final byte[] attestationChallenge = in.createByteArray(); final boolean uniqueIdIncluded = in.readBoolean(); @@ -185,6 +187,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { randomizedEncryptionRequired, userAuthenticationRequired, userAuthenticationValidityDurationSeconds, + userAuthenticationTypes, userPresenceRequired, attestationChallenge, uniqueIdIncluded, diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java index 69520606f101..c9e9bf0ab82f 100644 --- a/keystore/java/android/security/keystore/UserAuthArgs.java +++ b/keystore/java/android/security/keystore/UserAuthArgs.java @@ -28,6 +28,7 @@ public interface UserAuthArgs { boolean isUserAuthenticationRequired(); int getUserAuthenticationValidityDurationSeconds(); + @KeyProperties.AuthEnum int getUserAuthenticationType(); boolean isUserAuthenticationValidWhileOnBody(); boolean isInvalidatedByBiometricEnrollment(); boolean isUserConfirmationRequired(); diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 8cfd2d8ca696..32086625a726 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -992,6 +992,11 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { return bag; } +static bool compare_bag_entries(const ResolvedBag::Entry& entry1, + const ResolvedBag::Entry& entry2) { + return entry1.key < entry2.key; +} + const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) { auto cached_iter = cached_bags_.find(resid); if (cached_iter != cached_bags_.end()) { @@ -1027,13 +1032,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids.push_back(resid); uint32_t parent_resid = dtohl(map->parent.ident); - if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid) + if (parent_resid == 0U || std::find(child_resids.begin(), child_resids.end(), parent_resid) != child_resids.end()) { - // There is no parent or that a circular dependency exist, meaning there is nothing to - // inherit and we can do a simple copy of the entries in the map. + // There is no parent or a circular dependency exist, meaning there is nothing to inherit and + // we can do a simple copy of the entries in the map. const size_t entry_count = map_entry_end - map_entry; util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>( malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))}; + + bool sort_entries = false; ResolvedBag::Entry* new_entry = new_bag->entries; for (; map_entry != map_entry_end; ++map_entry) { uint32_t new_key = dtohl(map_entry->name.ident); @@ -1059,8 +1066,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->value.data, new_key); return nullptr; } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); ++new_entry; } + + if (sort_entries) { + std::sort(new_bag->entries, new_bag->entries + entry_count, compare_bag_entries); + } + new_bag->type_spec_flags = entry.type_flags; new_bag->entry_count = static_cast<uint32_t>(entry_count); ResolvedBag* result = new_bag.get(); @@ -1091,6 +1105,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count; // The keys are expected to be in sorted order. Merge the two bags. + bool sort_entries = false; while (map_entry != map_entry_end && parent_entry != parent_entry_end) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { @@ -1123,6 +1138,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& memcpy(new_entry, parent_entry, sizeof(*new_entry)); } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); if (child_key >= parent_entry->key) { // Move to the next parent entry if we used it or it was overridden. ++parent_entry; @@ -1153,6 +1170,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->value.dataType, new_entry->value.data, new_key); return nullptr; } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); ++map_entry; ++new_entry; } @@ -1172,6 +1191,10 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry))))); } + if (sort_entries) { + std::sort(new_bag->entries, new_bag->entries + actual_count, compare_bag_entries); + } + // Combine flags from the parent and our own bag. new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags; new_bag->entry_count = static_cast<uint32_t>(actual_count); diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 2f6f3dfcaf1c..35fea7ab86cb 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -285,6 +285,27 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); } +TEST_F(AssetManager2Test, FindsBagResourceFromMultipleSharedLibraries) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::ThemeMultiLib); + ASSERT_NE(nullptr, bag); + ASSERT_EQ(bag->entry_count, 2u); + + // First attribute comes from lib_two. + EXPECT_EQ(2, bag->entries[0].cookie); + EXPECT_EQ(0x02, get_package_id(bag->entries[0].key)); + + // The next two attributes come from lib_one. + EXPECT_EQ(2, bag->entries[1].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); +} + TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) { AssetManager2 assetmanager; diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h index 92b9cc10e7a8..fd5a910961cb 100644 --- a/libs/androidfw/tests/data/lib_two/R.h +++ b/libs/androidfw/tests/data/lib_two/R.h @@ -30,16 +30,22 @@ struct R { }; }; + struct integer { + enum : uint32_t { + bar = 0x02020000, // default + }; + }; + struct string { enum : uint32_t { - LibraryString = 0x02020000, // default - foo = 0x02020001, // default + LibraryString = 0x02030000, // default + foo = 0x02030001, // default }; }; struct style { enum : uint32_t { - Theme = 0x02030000, // default + Theme = 0x02040000, // default }; }; }; diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk Binary files differindex 486c23000276..8193db637eed 100644 --- a/libs/androidfw/tests/data/lib_two/lib_two.apk +++ b/libs/androidfw/tests/data/lib_two/lib_two.apk diff --git a/libs/androidfw/tests/data/lib_two/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml index 340d14c34c5d..4e1d69aa5a5a 100644 --- a/libs/androidfw/tests/data/lib_two/res/values/values.xml +++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml @@ -18,14 +18,17 @@ <public type="attr" name="attr3" id="0x00010000" /> <attr name="attr3" format="integer" /> - <public type="string" name="LibraryString" id="0x00020000" /> + <public type="integer" name="bar" id="0x00020000" /> + <integer name="bar">1337</integer> + + <public type="string" name="LibraryString" id="0x00030000" /> <string name="LibraryString">Hi from library two</string> - <public type="string" name="foo" id="0x00020001" /> + <public type="string" name="foo" id="0x00030001" /> <string name="foo">Foo from lib_two</string> - <public type="style" name="Theme" id="0x02030000" /> + <public type="style" name="Theme" id="0x00040000" /> <style name="Theme"> - <item name="com.android.lib_two:attr3">800</item> + <item name="com.android.lib_two:attr3">@integer/bar</item> </style> </resources> diff --git a/libs/androidfw/tests/data/libclient/R.h b/libs/androidfw/tests/data/libclient/R.h index 43d1f9bb68e7..e21b3ebae826 100644 --- a/libs/androidfw/tests/data/libclient/R.h +++ b/libs/androidfw/tests/data/libclient/R.h @@ -34,6 +34,7 @@ struct R { struct style { enum : uint32_t { Theme = 0x7f020000, // default + ThemeMultiLib = 0x7f020001, // default }; }; diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk Binary files differindex 17990248e862..4b9a8833c64a 100644 --- a/libs/androidfw/tests/data/libclient/libclient.apk +++ b/libs/androidfw/tests/data/libclient/libclient.apk diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml index fead7c323767..a29f473e3c17 100644 --- a/libs/androidfw/tests/data/libclient/res/values/values.xml +++ b/libs/androidfw/tests/data/libclient/res/values/values.xml @@ -27,6 +27,12 @@ <item name="bar">@com.android.lib_one:string/foo</item> </style> + <public type="style" name="ThemeMultiLib" id="0x7f020001" /> + <style name="ThemeMultiLib" > + <item name="com.android.lib_one:attr1">@com.android.lib_one:string/foo</item> + <item name="com.android.lib_two:attr3">@com.android.lib_two:integer/bar</item> + </style> + <public type="string" name="foo_one" id="0x7f030000" /> <string name="foo_one">@com.android.lib_one:string/foo</string> diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 301d1afc6c13..debb38b2c1b0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -171,7 +171,6 @@ cc_defaults { "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", "hwui/AnimatedImageDrawable.cpp", - "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", @@ -213,6 +212,7 @@ cc_defaults { android: { srcs: [ + "hwui/AnimatedImageThread.cpp", "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 4544beae5df8..638de850a6c5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -15,7 +15,9 @@ */ #include "AnimatedImageDrawable.h" +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread #include "AnimatedImageThread.h" +#endif #include "utils/TraceUtils.h" @@ -160,8 +162,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } else if (starting) { // The image has animated, and now is being reset. Queue up the first // frame, but keep showing the current frame until the first is ready. +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.reset(sk_ref_sp(this)); +#endif } bool finalFrame = false; @@ -187,8 +191,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } if (mRunning && !mNextSnapshot.valid()) { +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); +#endif } if (!drawDirectly) { diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java index 944ebf937dc8..f075a53829ce 100644 --- a/location/java/android/location/AbstractListenerManager.java +++ b/location/java/android/location/AbstractListenerManager.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -35,26 +36,34 @@ import java.util.function.Consumer; * * @hide */ -abstract class AbstractListenerManager<T> { +abstract class AbstractListenerManager<TRequest, TListener> { - private static class Registration<T> { + private static class Registration<TRequest, TListener> { private final Executor mExecutor; - @Nullable private volatile T mListener; + @Nullable private TRequest mRequest; + @Nullable private volatile TListener mListener; - private Registration(Executor executor, T listener) { + private Registration(@Nullable TRequest request, Executor executor, TListener listener) { Preconditions.checkArgument(listener != null, "invalid null listener/callback"); Preconditions.checkArgument(executor != null, "invalid null executor"); mExecutor = executor; mListener = listener; + mRequest = request; + } + + @Nullable + public TRequest getRequest() { + return mRequest; } private void unregister() { + mRequest = null; mListener = null; } - private void execute(Consumer<T> operation) { + private void execute(Consumer<TListener> operation) { mExecutor.execute(() -> { - T listener = mListener; + TListener listener = mListener; if (listener == null) { return; } @@ -71,71 +80,135 @@ abstract class AbstractListenerManager<T> { } @GuardedBy("mListeners") - private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>(); + private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners = + new ArrayMap<>(); + + @GuardedBy("mListeners") + @Nullable + private TRequest mMergedRequest; - public boolean addListener(@NonNull T listener, @NonNull Handler handler) + public boolean addListener(@NonNull TListener listener, @NonNull Handler handler) throws RemoteException { - return addInternal(listener, handler); + return addInternal(/* request= */ null, listener, handler); } - public boolean addListener(@NonNull T listener, @NonNull Executor executor) + public boolean addListener(@NonNull TListener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(/* request= */ null, listener, executor); } - protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler) - throws RemoteException { - return addInternal(listener, new HandlerExecutor(handler)); + public boolean addListener(@Nullable TRequest request, @NonNull TListener listener, + @NonNull Handler handler) throws RemoteException { + return addInternal(request, listener, handler); + } + + public boolean addListener(@Nullable TRequest request, @NonNull TListener listener, + @NonNull Executor executor) throws RemoteException { + return addInternal(request, listener, executor); + } + + protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener, + @NonNull Handler handler) throws RemoteException { + return addInternal(request, listener, new HandlerExecutor(handler)); } - protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor) + protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener, + @NonNull Executor executor) throws RemoteException { Preconditions.checkArgument(listener != null, "invalid null listener/callback"); - return addInternal(listener, new Registration<>(executor, convertKey(listener))); + return addInternal(listener, new Registration<>(request, executor, convertKey(listener))); } - private boolean addInternal(Object key, Registration<T> registration) throws RemoteException { + private boolean addInternal(Object key, Registration<TRequest, TListener> registration) + throws RemoteException { Preconditions.checkNotNull(registration); synchronized (mListeners) { - if (mListeners.isEmpty() && !registerService()) { - return false; - } - Registration<T> oldRegistration = mListeners.put(key, registration); + boolean initialRequest = mListeners.isEmpty(); + + Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration); if (oldRegistration != null) { oldRegistration.unregister(); } + TRequest merged = mergeRequests(); + + if (initialRequest || !Objects.equals(merged, mMergedRequest)) { + mMergedRequest = merged; + if (!initialRequest) { + unregisterService(); + } + registerService(mMergedRequest); + } + return true; } } public void removeListener(Object listener) throws RemoteException { synchronized (mListeners) { - Registration<T> oldRegistration = mListeners.remove(listener); + Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener); if (oldRegistration == null) { return; } oldRegistration.unregister(); - if (mListeners.isEmpty()) { + boolean lastRequest = mListeners.isEmpty(); + TRequest merged = lastRequest ? null : mergeRequests(); + boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest); + + if (lastRequest || newRequest) { unregisterService(); + mMergedRequest = merged; + if (newRequest) { + registerService(mMergedRequest); + } } } } @SuppressWarnings("unchecked") - protected T convertKey(@NonNull Object listener) { - return (T) listener; + protected TListener convertKey(@NonNull Object listener) { + return (TListener) listener; } - protected abstract boolean registerService() throws RemoteException; + protected abstract boolean registerService(TRequest request) throws RemoteException; protected abstract void unregisterService() throws RemoteException; - protected void execute(Consumer<T> operation) { + @Nullable + protected TRequest merge(@NonNull TRequest[] requests) { + for (TRequest request : requests) { + Preconditions.checkArgument(request == null, + "merge() has to be overridden for non-null requests."); + } + return null; + } + + protected void execute(Consumer<TListener> operation) { synchronized (mListeners) { - for (Registration<T> registration : mListeners.values()) { + for (Registration<TRequest, TListener> registration : mListeners.values()) { registration.execute(operation); } } } + + @GuardedBy("mListeners") + @SuppressWarnings("unchecked") + @Nullable + private TRequest mergeRequests() { + Preconditions.checkState(Thread.holdsLock(mListeners)); + + if (mListeners.isEmpty()) { + return null; + } + + if (mListeners.size() == 1) { + return mListeners.valueAt(0).getRequest(); + } + + TRequest[] requests = (TRequest[]) new Object[mListeners.size()]; + for (int index = 0; index < mListeners.size(); index++) { + requests[index] = mListeners.valueAt(index).getRequest(); + } + return merge(requests); + } } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 6a5c0ec9457a..a99e68fbb7b6 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -22,6 +22,7 @@ import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssStatusListener; @@ -69,8 +70,10 @@ interface ILocationManager double upperRightLatitude, double upperRightLongitude, int maxResults, in GeocoderParams params, out List<Address> addrs); - boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener, - String packageName, String featureId, String listenerIdentifier); + boolean addGnssMeasurementsListener(in GnssRequest request, + in IGnssMeasurementsListener listener, + String packageName, String featureId, + String listenerIdentifier); void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections, in String packageName); long getGnssCapabilities(in String packageName); @@ -107,6 +110,7 @@ interface ILocationManager boolean isProviderEnabledForUser(String provider, int userId); boolean isLocationEnabledForUser(int userId); + void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); void setTestProviderLocation(String provider, in Location loc, String opPackageName); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 7e6486cc933e..8ae967fe79c2 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -478,13 +478,11 @@ public class LocationManager { @TestApi @RequiresPermission(WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean enabled, @NonNull UserHandle userHandle) { - Settings.Secure.putIntForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_MODE, - enabled - ? Settings.Secure.LOCATION_MODE_ON - : Settings.Secure.LOCATION_MODE_OFF, - userHandle.getIdentifier()); + try { + mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -2199,7 +2197,7 @@ public class LocationManager { * Registers a GNSS Measurement callback. * * @param request extra parameters to pass to GNSS measurement provider. For example, if {@link - * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty + * GnssRequest#isFullTracking()} is true, GNSS chipset switches off duty * cycling. * @param executor the executor that the callback runs on. * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. @@ -2216,7 +2214,12 @@ public class LocationManager { @NonNull GnssRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull GnssMeasurementsEvent.Callback callback) { - throw new RuntimeException(); + Preconditions.checkArgument(request != null, "invalid null request"); + try { + return mGnssMeasurementsListenerManager.addListener(request, callback, executor); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -2763,8 +2766,7 @@ public class LocationManager { } private class GnssStatusListenerManager extends - AbstractListenerManager<GnssStatus.Callback> { - + AbstractListenerManager<Void, GnssStatus.Callback> { @Nullable private IGnssStatusListener mListenerTransport; @@ -2782,19 +2784,19 @@ public class LocationManager { public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(null, listener, executor); } public boolean addListener(@NonNull OnNmeaMessageListener listener, @NonNull Handler handler) throws RemoteException { - return addInternal(listener, handler); + return addInternal(null, listener, handler); } public boolean addListener(@NonNull OnNmeaMessageListener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(null, listener, executor); } @Override @@ -2833,7 +2835,7 @@ public class LocationManager { } @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssStatusListener transport = new GnssStatusListener(); @@ -2893,17 +2895,17 @@ public class LocationManager { } private class GnssMeasurementsListenerManager extends - AbstractListenerManager<GnssMeasurementsEvent.Callback> { + AbstractListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> { @Nullable private IGnssMeasurementsListener mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(GnssRequest request) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssMeasurementsListener transport = new GnssMeasurementsListener(); - if (mService.addGnssMeasurementsListener(transport, mContext.getPackageName(), + if (mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(), mContext.getFeatureId(), "gnss measurement callback")) { mListenerTransport = transport; return true; @@ -2920,6 +2922,18 @@ public class LocationManager { mListenerTransport = null; } + @Override + @Nullable + protected GnssRequest merge(@NonNull GnssRequest[] requests) { + Preconditions.checkArgument(requests.length > 0); + for (GnssRequest request : requests) { + if (request.isFullTracking()) { + return request; + } + } + return requests[0]; + } + private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub { @Override public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { @@ -2934,13 +2948,13 @@ public class LocationManager { } private class GnssNavigationMessageListenerManager extends - AbstractListenerManager<GnssNavigationMessage.Callback> { + AbstractListenerManager<Void, GnssNavigationMessage.Callback> { @Nullable private IGnssNavigationMessageListener mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssNavigationMessageListener transport = new GnssNavigationMessageListener(); @@ -2975,13 +2989,13 @@ public class LocationManager { } private class BatchedLocationCallbackManager extends - AbstractListenerManager<BatchedLocationCallback> { + AbstractListenerManager<Void, BatchedLocationCallback> { @Nullable private IBatchedLocationCallback mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); BatchedLocationCallback transport = new BatchedLocationCallback(); diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 767b67b7e885..d2379757f226 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1457,6 +1457,9 @@ public class ExifInterface { private int mRw2JpgFromRawOffset; private boolean mIsSupportedFile; private boolean mModified; + // XMP data can be contained as either part of the EXIF data (tag number 700), or as a + // separate data marker (a separate MARKER_APP1). + private boolean mXmpIsFromSeparateMarker; // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); @@ -2837,10 +2840,12 @@ public class ExifInterface { final long offset = start + IDENTIFIER_XMP_APP1.length; final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_XMP_APP1.length, bytes.length); - + // TODO: check if ignoring separate XMP data when tag 700 already exists is + // valid. if (getAttribute(TAG_XMP) == null) { mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( IFD_FORMAT_BYTE, value.length, offset, value)); + mXmpIsFromSeparateMarker = true; } } break; @@ -3445,11 +3450,24 @@ public class ExifInterface { } dataOutputStream.writeByte(MARKER_SOI); + // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not + // IDENTIFIER_EXIF_APP1) + // Will re-add it later after the rest of the file is written + ExifAttribute xmpAttribute = null; + if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) { + xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP); + } + // Write EXIF APP1 segment dataOutputStream.writeByte(MARKER); dataOutputStream.writeByte(MARKER_APP1); writeExifSegment(dataOutputStream); + // Re-add previously removed XMP data. + if (xmpAttribute != null) { + mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute); + } + byte[] bytes = new byte[4096]; while (true) { diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index b1f930d0f2cc..2c1fdab9da01 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -247,13 +247,23 @@ public class MediaRouter2Manager { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); + boolean transferred = false; + //TODO: instead of release all controllers, add an API to specify controllers that + // should be released (or is the system controller). for (RoutingController controller : getRoutingControllers(packageName)) { - if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) { + if (!transferred && controller.getSessionInfo().getTransferrableRoutes() + .contains(route.getId())) { controller.transferToRoute(route); - return; + transferred = true; + } else if (!controller.getSessionInfo().isSystemSession()) { + controller.release(); } } + if (transferred) { + return; + } + Client client; synchronized (sLock) { client = mClient; diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 870c1b4a3558..486c0c25a737 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -266,8 +266,12 @@ public final class MediaSession { * playback after the session has been stopped. If your app is started in * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via * the pending intent. + * <p> + * The pending intent is recommended to be explicit to follow the security recommendation of + * {@link PendingIntent#getActivity}. * * @param mbr The {@link PendingIntent} to send the media button event to. + * @see PendingIntent#getActivity */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 3561f8393eea..9b183a3e0e92 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -244,7 +244,7 @@ public class Tuner implements AutoCloseable { * * <p> * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link - * #stopTune()} is called. + * #cancelTuning()} is called. * * @param eventListener receives tune events. * @throws SecurityException if the caller does not have appropriate permissions. @@ -309,7 +309,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopTune() { + public int cancelTuning() { TunerUtils.checkTunerPermission(mContext); return nativeStopTune(); } @@ -322,8 +322,8 @@ public class Tuner implements AutoCloseable { * @param settings A {@link FrontendSettings} to configure the frontend. * @param scanType The scan type. * @throws SecurityException if the caller does not have appropriate permissions. - * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is - * called. + * @throws IllegalStateException if {@code scan} is called again before + * {@link #cancelScanning()} is called. */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result @@ -354,7 +354,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopScan() { + public int cancelScanning() { TunerUtils.checkTunerPermission(mContext); int retVal = nativeStopScan(); mScanCallback = null; diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS new file mode 100644 index 000000000000..73ea663aa37e --- /dev/null +++ b/media/tests/TunerTest/OWNERS @@ -0,0 +1,4 @@ +amyjojo@google.com +nchalko@google.com +quxiangfang@google.com +shubang@google.com diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java index bd5b79594c19..c4e41c87564f 100644 --- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java +++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java @@ -16,6 +16,8 @@ package com.android.incremental.nativeadb; +import android.annotation.NonNull; +import android.content.pm.DataLoaderParams; import android.service.dataloader.DataLoaderService; /** This code is used for testing only. */ @@ -26,7 +28,7 @@ public class NativeAdbDataLoaderService extends DataLoaderService { } @Override - public DataLoader onCreateDataLoader() { + public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 4d96251043ce..59881e7ba13d 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -553,6 +553,60 @@ <string name="enable_adb_summary">Debug mode when USB is connected</string> <!-- Setting title to revoke secure USB debugging authorizations --> <string name="clear_adb_keys">Revoke USB debugging authorizations</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless switch --> + <string name="enable_adb_wireless">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Setting checkbox summary for whether to enable Wireless debugging support on the phone --> + <string name="enable_adb_wireless_summary">Debug mode when Wi\u2011Fi is connected</string> + <!-- [CHAR LIMIT=32] Summary text when ADB wireless has error --> + <string name="adb_wireless_error">Error</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless fragment --> + <string name="adb_wireless_settings">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Wireless debugging settings. text displayed when wireless debugging is off and network list is empty. --> + <string name="adb_wireless_list_empty_off">To see and use available devices, turn on wireless debugging</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_title">Pair device with QR code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_title">Pair device with pairing code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_summary">Pair new devices using six digit code</string> + <!-- [CHAR LIMIT=50] Title for adb wireless paired devices category --> + <string name="adb_paired_devices_title">Paired devices</string> + <!-- [CHAR LIMIT=50] Summary for adb wireless paired device preference --> + <string name="adb_wireless_device_connected_summary">Currently connected</string> + <!-- [CHAR LIMIT=50] Title for the adb device details fragment --> + <string name="adb_wireless_device_details_title">Device details</string> + <!-- [CHAR LIMIT=16] Button label to forget an adb device --> + <string name="adb_device_forget">Forget</string> + <!-- [CHAR LIMIT=50] Title format for mac address preference in adb device details fragment --> + <string name="adb_device_fingerprint_title_format">Device fingerprint: <xliff:g id="fingerprint_param" example="a1:b2:c3:d4:e5:f6">%1$s</xliff:g></string> + <!-- [CHAR LIMIT=50] Title for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_title">Connection unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Message for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_message">Make sure <xliff:g id="device_name" example="Bob's Macbook">%1$s</xliff:g> is connected to the correct network</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog title --> + <string name="adb_pairing_device_dialog_title">Pair with device</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog pairing code label --> + <string name="adb_pairing_device_dialog_pairing_code_label">Wi\u2011Fi pairing code</string> + <!-- [CHAR LIMIT=50] Adb Wireless pairing device failed dialog title --> + <string name="adb_pairing_device_dialog_failed_title">Pairing unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Adb wireless pairing device failed dialog message --> + <string name="adb_pairing_device_dialog_failed_msg">Make sure the device is connected to the same network.</string> + <!-- [CHAR LIMIT=NONE] Adb wireless qr code scanner description --> + <string name="adb_wireless_qrcode_summary">Pair device over Wi\u2011Fi by scanning a QR code</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code pairing in progress text --> + <string name="adb_wireless_verifying_qrcode_text">Pairing device\u2026</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code failed message --> + <string name="adb_qrcode_pairing_device_failed_msg">Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network.</string> + <!-- [CHAR LIMIT=50] Adb Wireless ip address and port title --> + <string name="adb_wireless_ip_addr_preference_title">IP address \u0026 Port</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title --> + <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description --> + <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string> + <!--Adb wireless search Keywords [CHAR LIMIT=NONE]--> + <string name="keywords_adb_wireless">adb, debug, dev</string> <!-- [CHAR LIMIT=NONE] Setting checkbox title for Whether to include bug report item in power menu. --> <string name="bugreport_in_power">Bug report shortcut</string> <!-- [CHAR LIMIT=NONE] Setting checkbox summary for Whether to include bug report item in power --> @@ -689,6 +743,10 @@ <string name="adb_warning_title">Allow USB debugging?</string> <!-- Warning text to user about the implications of enabling USB debugging --> <string name="adb_warning_message">USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> + <!-- Title of warning dialog about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_title">Allow wireless debugging?</string> + <!-- Warning text to user about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_message">Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> <!-- Message of dialog confirming that user wants to revoke access to adb from all computers they have authorized --> <string name="adb_keys_warning_message">Revoke access to USB debugging from all computers you\u2019ve previously authorized?</string> <!-- Title of warning dialog about the implications of enabling developer settings --> @@ -1011,17 +1069,17 @@ <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=40] Label for battery level chart when charging --> <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 1ebe91736ba1..d03a747b743c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -216,12 +216,12 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean supportsHighQualityAudio(BluetoothDevice device) { - int support = mService.supportsOptionalCodecs(device); + int support = mService.isOptionalCodecsSupported(device); return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED; } public boolean isHighQualityAudioEnabled(BluetoothDevice device) { - int enabled = mService.getOptionalCodecsEnabled(device); + int enabled = mService.isOptionalCodecsEnabled(device); if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) { return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED; } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED && diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index b9081f241a91..b725ba5b8748 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,12 +16,9 @@ package com.android.settingslib.media; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; -import android.text.TextUtils; -import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -62,46 +59,6 @@ public class InfoMediaDevice extends MediaDevice { return MediaDeviceUtils.getId(mRouteInfo); } - @Override - public void requestSetVolume(int volume) { - mRouterManager.requestSetVolume(mRouteInfo, volume); - } - - @Override - public int getMaxVolume() { - return mRouteInfo.getVolumeMax(); - } - - @Override - public int getCurrentVolume() { - return mRouteInfo.getVolume(); - } - - @Override - public String getClientPackageName() { - return mRouteInfo.getClientPackageName(); - } - - @Override - public String getClientAppLabel() { - final String packageName = mRouteInfo.getClientPackageName(); - if (TextUtils.isEmpty(packageName)) { - Log.d(TAG, "Client package name is empty"); - return mContext.getResources().getString(R.string.unknown); - } - try { - final PackageManager packageManager = mContext.getPackageManager(); - final String appLabel = packageManager.getApplicationLabel( - packageManager.getApplicationInfo(packageName, 0)).toString(); - if (!TextUtils.isEmpty(appLabel)) { - return appLabel; - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "unable to find " + packageName); - } - return mContext.getResources().getString(R.string.unknown); - } - public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 580e086973d6..33c3d7e039b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -16,14 +16,18 @@ package com.android.settingslib.media; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import com.android.settingslib.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -113,7 +117,9 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * * @param volume is the new value. */ + public void requestSetVolume(int volume) { + mRouterManager.requestSetVolume(mRouteInfo, volume); } /** @@ -122,7 +128,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return max volume. */ public int getMaxVolume() { - return 100; + return mRouteInfo.getVolumeMax(); } /** @@ -131,7 +137,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return current volume. */ public int getCurrentVolume() { - return 0; + return mRouteInfo.getVolume(); } /** @@ -140,7 +146,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return package name. */ public String getClientPackageName() { - return null; + return mRouteInfo.getClientPackageName(); } /** @@ -149,7 +155,22 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return application label. */ public String getClientAppLabel() { - return null; + final String packageName = mRouteInfo.getClientPackageName(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "Client package name is empty"); + return mContext.getResources().getString(R.string.unknown); + } + try { + final PackageManager packageManager = mContext.getPackageManager(); + final String appLabel = packageManager.getApplicationLabel( + packageManager.getApplicationInfo(packageName, 0)).toString(); + if (!TextUtils.isEmpty(appLabel)) { + return appLabel; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to find " + packageName); + } + return mContext.getResources().getString(R.string.unknown); } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java index c555cbec4bab..cb9092eba441 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java @@ -73,26 +73,26 @@ public class A2dpProfileTest { @Test public void supportsHighQualityAudio() { - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse(); } @Test public void isHighQualityAudioEnabled() { - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue(); - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse(); @@ -100,16 +100,16 @@ public class A2dpProfileTest { // then isHighQualityAudioEnabled() should return true or false based on whether optional // codecs are supported. If the device is connected then we should ask it directly, but if // the device isn't connected then rely on the stored pref about such support. - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); when(mBluetoothA2dp.getConnectionState(any())).thenReturn( BluetoothProfile.STATE_DISCONNECTED); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue(); @@ -151,14 +151,14 @@ public class A2dpProfileTest { // Most tests want to simulate optional codecs being supported by the device, so do that // by default here. - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); } @Test public void getLableCodecsNotSupported() { setupLabelTest(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java index 04ceb2147c6e..77a67c286989 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java @@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -36,14 +33,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; -import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class InfoMediaDeviceTest { private static final String TEST_PACKAGE_NAME = "com.test.packagename"; - private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2"; private static final String TEST_ID = "test_id"; private static final String TEST_NAME = "test_name"; @@ -52,27 +46,13 @@ public class InfoMediaDeviceTest { @Mock private MediaRoute2Info mRouteInfo; - private Context mContext; private InfoMediaDevice mInfoMediaDevice; - private ShadowPackageManager mShadowPackageManager; - private ApplicationInfo mAppInfo; - private PackageInfo mPackageInfo; - private PackageStats mPackageStats; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); - mAppInfo = new ApplicationInfo(); - mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; - mAppInfo.packageName = TEST_PACKAGE_NAME; - mAppInfo.name = TEST_NAME; - mPackageInfo = new PackageInfo(); - mPackageInfo.packageName = TEST_PACKAGE_NAME; - mPackageInfo.applicationInfo = mAppInfo; - mPackageStats = new PackageStats(TEST_PACKAGE_NAME); mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo, TEST_PACKAGE_NAME); @@ -106,32 +86,4 @@ public class InfoMediaDeviceTest { assertThat(mInfoMediaDevice.getId()).isEqualTo(TEST_ID); } - - @Test - public void getClientPackageName_returnPackageName() { - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); - } - - @Test - public void getClientAppLabel_matchedPackageName_returnLabel() { - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME); - } - - @Test - public void getClientAppLabel_noMatchedPackageName_returnDefault() { - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index fb8b78b22055..3f29b72b978d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -23,9 +23,13 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidProfile; @@ -39,6 +43,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; import java.util.Collections; @@ -58,6 +64,8 @@ public class MediaDeviceTest { private static final String ROUTER_ID_2 = "RouterId_2"; private static final String ROUTER_ID_3 = "RouterId_3"; private static final String TEST_PACKAGE_NAME = "com.test.playmusic"; + private static final String TEST_PACKAGE_NAME2 = "com.test.playmusic2"; + private static final String TEST_APPLICATION_LABEL = "playmusic"; private final BluetoothClass mHeadreeClass = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass mCarkitClass = @@ -111,6 +119,10 @@ public class MediaDeviceTest { private InfoMediaDevice mInfoMediaDevice3; private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; + private ShadowPackageManager mShadowPackageManager; + private ApplicationInfo mAppInfo; + private PackageInfo mPackageInfo; + private PackageStats mPackageStats; @Before public void setUp() { @@ -394,4 +406,46 @@ public class MediaDeviceTest { verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1); } + + @Test + public void getClientPackageName_returnPackageName() { + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice1.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); + } + + private void initPackage() { + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mAppInfo = new ApplicationInfo(); + mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; + mAppInfo.packageName = TEST_PACKAGE_NAME; + mAppInfo.name = TEST_APPLICATION_LABEL; + mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = TEST_PACKAGE_NAME; + mPackageInfo.applicationInfo = mAppInfo; + mPackageStats = new PackageStats(TEST_PACKAGE_NAME); + } + + @Test + public void getClientAppLabel_matchedPackageName_returnLabel() { + initPackage(); + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(TEST_APPLICATION_LABEL); + } + + @Test + public void getClientAppLabel_noMatchedPackageName_returnDefault() { + initPackage(); + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index 61fdbd54ead1..ed308c8d6f6a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -120,9 +120,9 @@ public class PowerUtilTest { true /* basedOnUsage */); // additional battery percentage in this string - assertThat(info).isEqualTo("Phone may shutdown soon (10%)"); + assertThat(info).isEqualTo("Phone may shut down soon (10%)"); // shortened string should not have percentage - assertThat(info2).isEqualTo("Phone may shutdown soon"); + assertThat(info2).isEqualTo("Phone may shut down soon"); } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index c969bfd193b5..7b518a6167b7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2193,7 +2193,7 @@ public class SettingsProvider extends ContentProvider { if (prefix == null) { return; } - String callingPackage = getCallingPackage(); + String callingPackage = resolveCallingPackage(); String namespace = prefix.replace("/", ""); if (DeviceConfig.getPublicNamespaces().contains(namespace)) { return; diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1fe967b4750d..5458676e1061 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -646,16 +646,17 @@ android:label="Controls Providers" android:theme="@style/Theme.ControlsManagement" android:showForAllUsers="true" + android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> <activity android:name=".controls.management.ControlsFavoritingActivity" - android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" android:showForAllUsers="true" + android:finishOnTaskLaunch="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png Binary files differnew file mode 100644 index 000000000000..02de479cb8c0 --- /dev/null +++ b/packages/SystemUI/docs/QS-QQS.png diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md new file mode 100644 index 000000000000..b48ba6708313 --- /dev/null +++ b/packages/SystemUI/docs/qs-tiles.md @@ -0,0 +1,377 @@ +# Quick Settings Tiles (almost all there is to know about them) + +[TOC] + +## About this document + +This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics. + +## What are Quick Settings Tiles? + +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text. + +Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. + +The following image shows QQS on the left and QS on the right, with the tiles highlighted. + + + +QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. + +### A note on multi-user support + +All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users. + +For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users. + +## What are tiles made of? + +### Tile backend + +QS Tiles are composed of the following backend classes. + +* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles. + * `Icon`: Defines the basic interface for an icon as used by the tiles. + * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI. +* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation. +* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes: + * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device) + * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi). +* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile) + +All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles. + +The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md) + +#### Tile State + +Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields: + +* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`. +* **`icon`**; icon to display. It may depend on the current state. +* **`label`**: usually the name of the tile. +* **`secondaryLabel`**: text to display in a second line. Usually extra state information. +* **`contentDescription`** +* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`. +* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility. + +Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it. + +Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`. + +#### SystemUI tiles + +Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.). + +For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile). + +### Tile views + +Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. + +* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. +* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view: + * Holding the icon + * Background color and shape + * Ripple + * Click listening +* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS. +* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** +* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** + +#### QSIconView and QSIconViewImpl + +`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. + +This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes. + +Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon. + +### How are the backend and the views related? + +The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation. + +It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI. + +When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following: + +* Create the corresponding `QSTileView` to display in that container. +* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS. + +#### Life of a tile click + +This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. + +1. User clicks on tile. The following calls happen in sequence: + 1. `QSTileBaseView#onClickListener`. + 2. `QSTile#click`. + 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. +2. State in the device changes. This is normally outside of SystemUI's control. +3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen: + 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state. + 2. `QSTileImpl#handleRefreshState` +4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. +5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. +6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: + * The tile is rippled and the color changes to match the new state. + * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. + * If the tile is a `QSTileView` (in expanded QS), the labels are changed. + +## Third party tiles (TileService) + +A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API. + +### API classes + +The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings). + +#### Tile + +Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields: + +* Label +* Subtitle +* Icon +* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`) +* Content description + +Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed. + +#### TileService + +This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user. + +The implementer is responsible for creating the methods that will respond to the following calls from SystemUI: + +* **`onTileAdded`**: called when the tile is added to QS. +* **`onTileRemoved`**: called when the tile is removed from QS. +* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object. +* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`. +* **`onClick`**: called when the user clicks on the tile. + +Additionally, the following final methods are provided: + +* ```java + public final Tile getQsTile() + ``` + + Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`. + +* ```java + public final boolean isLocked() + + public final boolean isSecure() + ``` + + Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile. + +* ```java + public final void unlockAndRun(Runnable) + ``` + + May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`. + +* ```java + public final void showDialog(Dialog) + ``` + + Shows the provided dialog. + +##### Binding + +When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile. + +The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again. + +##### Active tile + +A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated. + +The tile will also be granted listening status if it's clicked by the user. + +### SystemUI classes + +The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) + +#### CustomTile + +This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest. + +#### TileServices + +This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`. + +Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing. + +As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation. + +#### TileLifecycleManager + +This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable). + +The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound. + +Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user. + +#### TileServiceManager + +Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`. + +This class handles management of the service, including: + +* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`. +* Relaying messages to the `TileService` through the `TileLifecycleManager`. +* Determining the service's bind priority (to deal with OOM situations). +* Detecting when the package/component has been removed in order to remove the tile and references to it. + +## How are tiles created/instantiated? + +This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes. + +### QSTileHost + +This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones. + +It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`). + +#### Tile specs + +Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise. + +SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`. + +### QSFactory + +This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles. + +In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`. + +#### QSFactoryImpl + +This class implements two methods as specified in the `QSFactory` interface: + +* ```java + public QSTile createTile(String) + ``` + + Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used. + + If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available. + +* ```java + public QSTileView createTileView(QSTile, boolean) + ``` + + Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS). + +### Lifecycle of a Tile + +We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles. + +1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). +2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new". +3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward. +4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes. +5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally: + * a callback is attached to the tile to communicate between the backend and the view or the panel. + * the click listeners in the tile are attached to those of the view. +6. The tile view is added to the corresponding layout. + +When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to. + +#### Lifecycle of a CustomTile + +In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice). + +1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it. +2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service. +3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`. + +## Implementing a tile + +This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile. + +### Implementing a SystemUI tile + +1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter. +2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers. +3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind: + * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`). + * Changes to the tile state (either from controllers or from clicks) should call `refreshState`. + * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. + * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. + * Implement `isAvailable` so the tile will not be created when it's not necessary. +4. In `QSFactoryImpl`: + * Inject a `Provider` for the tile created before. + * Add a case to the `switch` with a unique String spec for the chosen tile. +5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. + +#### Abstract methods in QSTileImpl + +Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`. + +* ```java + public TState newTileState() + ``` + + Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). + + If a tile has special behavior (no long click, no ripple), it can be set in its state here. + +* ```java + public void handleSetListening(boolean) + ``` + + Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) + +* ```java + public QSIconView createTileView(Context) + ``` + + Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` + +* ```java + public Intent getLongClickIntent() + ``` + + Determines the `Intent` launched when the Tile is long pressed. + +* ```java + protected void handleClick() + + protected void handleSecondaryClick() + + protected void handleLongClick() + ``` + + Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. + + By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`. + +* ```java + protected void handleUpdateState(TState, Object) + ``` + + Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. + +* ```java + public int getMetricsCategory() + ``` + + Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile. + +* ```java + public boolean isAvailable() + ``` + + Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation. + +* ```java + public CharSequence getTileLabel() + ``` + + Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile. + +### Implementing a third party tile + +For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 01811e9cdced..f607cc8f7e15 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -118,6 +118,7 @@ public interface QSTile { public CharSequence label; public CharSequence secondaryLabel; public CharSequence contentDescription; + public CharSequence stateDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; public boolean dualTarget = false; @@ -135,6 +136,7 @@ public interface QSTile { || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) || !Objects.equals(other.contentDescription, contentDescription) + || !Objects.equals(other.stateDescription, stateDescription) || !Objects.equals(other.dualLabelContentDescription, dualLabelContentDescription) || !Objects.equals(other.expandedAccessibilityClassName, @@ -151,6 +153,7 @@ public interface QSTile { other.label = label; other.secondaryLabel = secondaryLabel; other.contentDescription = contentDescription; + other.stateDescription = stateDescription; other.dualLabelContentDescription = dualLabelContentDescription; other.expandedAccessibilityClassName = expandedAccessibilityClassName; other.disabledByPolicy = disabledByPolicy; @@ -177,6 +180,7 @@ public interface QSTile { sb.append(",label=").append(label); sb.append(",secondaryLabel=").append(secondaryLabel); sb.append(",contentDescription=").append(contentDescription); + sb.append(",stateDescription=").append(stateDescription); sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); sb.append(",disabledByPolicy=").append(disabledByPolicy); diff --git a/packages/SystemUI/res-keyguard/layout/controls_management.xml b/packages/SystemUI/res-keyguard/layout/controls_management.xml deleted file mode 100644 index 8330258e2456..000000000000 --- a/packages/SystemUI/res-keyguard/layout/controls_management.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:paddingTop="@dimen/controls_management_top_padding" - android:paddingStart="@dimen/controls_management_side_padding" - android:paddingEnd="@dimen/controls_management_side_padding" > - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="@dimen/controls_title_size" - android:textAlignment="center" /> - - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_titles_margin" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="center" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_list_margin" /> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml new file mode 100644 index 000000000000..7b43a0315c10 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + android:layout_marginStart="12dp" + android:layout_marginEnd="2dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp"> + +</TextView>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml new file mode 100644 index 000000000000..8ab28ddb680d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml new file mode 100644 index 000000000000..d7439e167dd4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml new file mode 100644 index 000000000000..7a628bb65433 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important_outline.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml deleted file mode 100644 index 4a731b35b423..000000000000 --- a/packages/SystemUI/res/drawable/ic_star.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml deleted file mode 100644 index 9ede40be3b7b..000000000000 --- a/packages/SystemUI/res/drawable/ic_star_border.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml new file mode 100644 index 000000000000..be3c5983bb2e --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0" + android:fillColor="@android:color/white"/> + <path + android:fillColor="@color/GM2_grey_500" + android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 68c824698b2d..823bbcd6e68c 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -72,8 +72,8 @@ <CheckBox android:id="@+id/favorite" android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml new file mode 100644 index 000000000000..a7379bedebef --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:paddingTop="@dimen/controls_management_top_padding" + android:paddingStart="@dimen/controls_management_side_padding" + android:paddingEnd="@dimen/controls_management_side_padding" > + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="@dimen/controls_title_size" + android:textAlignment="center" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_titles_margin" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="center" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <ViewStub + android:id="@+id/stub" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </androidx.core.widget.NestedScrollView> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="64dp"> + + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="4dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="See other apps" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="Done" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </androidx.constraintlayout.widget.ConstraintLayout> + </FrameLayout> + + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml new file mode 100644 index 000000000000..2bab433d21f3 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + +</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml new file mode 100644 index 000000000000..a36dd1247a04 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/text_favorites" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="FAVORITES" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider1" + app:layout_constraintTop_toTopOf="parent" + /> + + <View + android:id="@+id/divider1" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listFavorites" + app:layout_constraintTop_toBottomOf="@id/text_favorites" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listFavorites" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/text_all" + app:layout_constraintTop_toBottomOf="@id/divider1"/> + + <TextView + android:id="@+id/text_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:text="ALL" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider2" + app:layout_constraintTop_toBottomOf="@id/listFavorites" + /> + + <View + android:id="@+id/divider2" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listAll" + app:layout_constraintTop_toBottomOf="@id/text_all" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listAll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/divider2"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 40b2476941c2..2cd9505b8fe4 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -1,3 +1,18 @@ +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 1f7def2bc956..d0151fff95c4 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -55,10 +55,22 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:elevation="8dp" + android:elevation="@dimen/screenshot_preview_elevation" android:visibility="gone" android:background="@drawable/screenshot_rounded_corners" android:adjustViewBounds="true"/> + <FrameLayout + android:id="@+id/global_screenshot_dismiss_button" + android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" + android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" + android:elevation="9dp" + android:visibility="gone"> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/screenshot_dismiss_button_margin" + android:src="@drawable/screenshot_cancel"/> + </FrameLayout> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index a9d6e3575317..84606126086d 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -28,7 +28,7 @@ android:paddingStart="@*android:dimen/notification_content_margin_start"> <!-- Package Info --> - <RelativeLayout + <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="@dimen/notification_guts_conversation_header_height" @@ -41,16 +41,20 @@ android:layout_height="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_marginEnd="6dp" /> + android:layout_marginEnd="15dp" /> <LinearLayout android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_height="wrap_content" android:minHeight="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:gravity="center_vertical" - android:layout_toEndOf="@id/conversation_icon"> + android:layout_alignEnd="@id/conversation_icon" + android:layout_toEndOf="@id/conversation_icon" + android:layout_alignStart="@id/mute"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -107,67 +111,40 @@ android:layout_weight="1" style="@style/TextAppearance.NotificationImportanceChannel"/> </LinearLayout> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:layout_toEndOf="@id/pkg_divider" + android:maxLines="1" /> </LinearLayout> - <TextView - android:id="@+id/pkg_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:layout_toEndOf="@id/name" - android:text="@*android:string/notification_header_divider_symbol" /> - <TextView - android:id="@+id/delegate_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:ellipsize="end" - android:text="@string/notification_delegate_header" - android:layout_toEndOf="@id/pkg_divider" - android:maxLines="1" /> - <!-- end aligned fields --> <ImageButton - android:id="@+id/demote" - android:layout_width="@dimen/notification_importance_toggle_size" - android:layout_height="@dimen/notification_importance_toggle_size" - android:layout_centerVertical="true" - android:background="@drawable/ripple_drawable" - android:contentDescription="@string/demote" - android:src="@drawable/ic_demote_conversation" - android:layout_toStartOf="@id/app_settings" - android:tint="@color/notification_guts_link_icon_tint"/> - <!-- Optional link to app. Only appears if the channel is not disabled and the app -asked for it --> - <ImageButton - android:id="@+id/app_settings" + android:id="@+id/mute" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" - android:visibility="gone" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_app_settings" - android:src="@drawable/ic_info" - android:layout_toStartOf="@id/info" + android:layout_toStartOf="@id/fave" android:tint="@color/notification_guts_link_icon_tint"/> <ImageButton - android:id="@+id/info" + android:id="@+id/fave" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_more_settings" - android:src="@drawable/ic_settings" android:layout_alignParentEnd="true" android:tint="@color/notification_guts_link_icon_tint"/> - </RelativeLayout> + + </LinearLayout> <LinearLayout android:id="@+id/actions" @@ -182,29 +159,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> - <Button - android:id="@+id/bubble" - android:layout_height="@dimen/notification_guts_conversation_action_height" - android:layout_width="match_parent" - style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_favorite" - android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_create_bubble" - android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" - android:drawableTint="@color/notification_guts_link_icon_tint"/> - <View - android:layout_width="match_parent" - android:layout_height="0.5dp" - android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/home" + android:id="@+id/snooze" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_home_screen" + android:text="@string/notification_menu_snooze_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_add_to_home" + android:drawableStart="@drawable/ic_snooze" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -212,12 +175,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/fave" + android:id="@+id/bubble" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_favorite" android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_create_bubble" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -226,13 +192,13 @@ asked for it --> android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/snooze" + android:id="@+id/home" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_menu_snooze_action" + android:text="@string/notification_conversation_home_screen" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_snooze" + android:drawableStart="@drawable/ic_add_to_home" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -240,14 +206,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/mute" + android:id="@+id/info" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_mute" + android:drawableStart="@drawable/ic_settings" + android:text="@string/notification_menu_settings_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_notifications_silence" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7a3395c229a0..f0e4e53dfc53 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -103,7 +103,8 @@ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item> <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen> - <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen> + <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end + </dimen> <!-- max height of a notification such that the content can still fade out when closing --> <dimen name="max_notification_fadeout_height">100dp</dimen> @@ -267,7 +268,9 @@ <dimen name="status_bar_icon_drawing_size">15dp</dimen> <!-- size at which Notification icons will be drawn on Ambient Display --> - <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen> + <dimen name="status_bar_icon_drawing_size_dark"> + @*android:dimen/notification_header_icon_size_ambient + </dimen> <!-- size of notification icons when the notifications are hidden --> <dimen name="hidden_shelf_icon_size">16dp</dimen> @@ -299,8 +302,11 @@ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_preview_elevation">8dp</dimen> <dimen name="screenshot_offset_y">48dp</dimen> <dimen name="screenshot_offset_x">16dp</dimen> + <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen> + <dimen name="screenshot_dismiss_button_margin">8dp</dimen> <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding_vertical">10dp</dimen> @@ -597,10 +603,14 @@ <!-- The height of the divider between the individual notifications in a notification group. --> - <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen> + <dimen name="notification_children_container_divider_height"> + @dimen/notification_divider_height + </dimen> <!-- The top margin for the notification children container in its non-expanded form. --> - <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen> + <dimen name="notification_children_container_margin_top"> + @*android:dimen/notification_content_margin_top + </dimen> <!-- The height of a notification header --> <dimen name="notification_header_height">53dp</dimen> @@ -967,6 +977,9 @@ Equal to pip_action_size - pip_action_padding. --> <dimen name="pip_expand_container_edge_margin">30dp</dimen> + <!-- The touchable/draggable edge size for PIP resize. --> + <dimen name="pip_resize_edge_size">30dp</dimen> + <dimen name="default_gear_space">18dp</dimen> <dimen name="cell_overlay_padding">18dp</dimen> @@ -1061,9 +1074,12 @@ <integer name="wireless_charging_scale_dots_duration">83</integer> <integer name="wireless_charging_num_dots">16</integer> <!-- Starting text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item> + <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen"> + 0 + </item> <!-- Ending text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item> + <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24 + </item> <!-- time until battery info is at full opacity--> <integer name="wireless_charging_anim_opacity_offset">80</integer> <!-- duration batteryLevel opacity goes from 0 to 1 duration --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b85b51e4f2cc..8f9e934c97e5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1829,23 +1829,23 @@ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification --> <string name="demote">Mark this notification as not a conversation</string> - <!-- [CHAR LIMIT=100] Mark this conversation as a favorite --> - <string name="notification_conversation_favorite">Mark as important</string> + <!-- [CHAR LIMIT=100] This conversation is marked as important --> + <string name="notification_conversation_favorite">Important conversation</string> - <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> - <string name="notification_conversation_unfavorite">Mark as unimportant</string> + <!-- [CHAR LIMIT=100] This conversation is not marked as important --> + <string name="notification_conversation_unfavorite">Not an important conversation</string> - <!-- [CHAR LIMIT=100] Mute this conversation --> - <string name="notification_conversation_mute">Silence</string> + <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)--> + <string name="notification_conversation_mute">Silenced</string> - <!-- [CHAR LIMIT=100] Umute this conversation --> + <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)--> <string name="notification_conversation_unmute">Alerting</string> <!-- [CHAR LIMIT=100] Show notification as bubble --> - <string name="notification_conversation_bubble">Show as bubble</string> + <string name="notification_conversation_bubble">Show bubble</string> <!-- [CHAR LIMIT=100] Turn off bubbles for notification --> - <string name="notification_conversation_unbubble">Turn off bubbles</string> + <string name="notification_conversation_unbubble">Remove bubbles</string> <!-- [CHAR LIMIT=100] Add this conversation to home screen --> <string name="notification_conversation_home_screen">Add to home screen</string> @@ -1860,7 +1860,10 @@ <string name="notification_menu_snooze_description">notification snooze options</string> <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> - <string name="notification_menu_snooze_action">Snooze</string> + <string name="notification_menu_snooze_action">Remind me</string> + + <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> + <string name="notification_menu_settings_action">Settings</string> <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]--> <string name="snooze_undo">UNDO</string> @@ -2029,6 +2032,9 @@ <!-- Label for feature switch [CHAR LIMIT=30] --> <string name="switch_bar_off">Off</string> + <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] --> + <string name="tile_unavailable">Unavailable</string> + <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] --> <string name="nav_bar">Navigation bar</string> @@ -2583,6 +2589,4 @@ <string name="controls_favorite_default_title">Controls</string> <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] --> <string name="controls_favorite_subtitle">Choose controls for quick access</string> - - </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index a37861068e48..b8997c29dd52 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -135,8 +135,7 @@ public class PluginInstanceManager<T extends Plugin> { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { - disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); - disableAny = true; + disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); } } return disableAny; @@ -144,10 +143,11 @@ public class PluginInstanceManager<T extends Plugin> { public boolean disableAll() { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + boolean disabledAny = false; for (int i = 0; i < plugins.size(); i++) { - disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); + disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } - return plugins.size() != 0; + return disabledAny; } private boolean isPluginWhitelisted(ComponentName pluginName) { @@ -166,7 +166,7 @@ public class PluginInstanceManager<T extends Plugin> { return false; } - private void disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { + private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { // Live by the sword, die by the sword. // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. @@ -176,10 +176,12 @@ public class PluginInstanceManager<T extends Plugin> { // assuming one of them must be bad. if (isPluginWhitelisted(pluginComponent)) { // Don't disable whitelisted plugins as they are a part of the OS. - return; + return false; } Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); mManager.getPluginEnabler().setDisabled(pluginComponent, reason); + + return true; } public <T> boolean dependsOn(Plugin p, Class<T> cls) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index bea55c820b40..2d55a1ddf654 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -146,7 +146,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V int viewType) { BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.bubble_view, parent, false); - view.setPadding(15, 15, 15, 15); return new ViewHolder(view); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index 53841e2f144b..49a16d892ef4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -20,6 +20,6 @@ import android.service.controls.Control data class ControlStatus( val control: Control, - val favorite: Boolean, + var favorite: Boolean, val removed: Boolean = false )
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index b3ba2b22f6df..fce504120b62 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -26,10 +26,18 @@ interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> - fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit) + fun loadForComponent( + componentName: ComponentName, + callback: (List<ControlStatus>, List<String>) -> Unit + ) + fun subscribeToFavorites() fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) - fun countFavoritesForComponent(componentName: ComponentName): Int = 0 + fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>) + + fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> + fun countFavoritesForComponent(componentName: ComponentName): Int + fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) fun refreshStatus(componentName: ComponentName, control: Control) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 7de1557ebc65..e611197b78ae 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -70,9 +70,10 @@ class ControlsControllerImpl @Inject constructor ( } // Map of map: ComponentName -> (String -> ControlInfo). - // Only for current user + // @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>() + .withDefault { mutableListOf() } private var userChanging: Boolean = true @@ -180,15 +181,14 @@ class ControlsControllerImpl @Inject constructor ( val infos = persistenceWrapper.readFavorites() synchronized(currentFavorites) { infos.forEach { - currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() }) - .put(it.controlId, it) + currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it) } } } override fun loadForComponent( componentName: ComponentName, - callback: (List<ControlStatus>) -> Unit + callback: (List<ControlStatus>, List<String>) -> Unit ) { if (!confirmAvailability()) { if (userChanging) { @@ -200,29 +200,34 @@ class ControlsControllerImpl @Inject constructor ( TimeUnit.MILLISECONDS ) } else { - callback(emptyList()) + callback(emptyList(), emptyList()) } return } bindingController.bindAndLoad(componentName) { synchronized(currentFavorites) { - val favoritesForComponentKeys: Set<String> = - currentFavorites.get(componentName)?.keys ?: emptySet() - val changed = updateFavoritesLocked(componentName, it) + val favoritesForComponentKeys: List<String> = + currentFavorites.getValue(componentName).map { it.controlId } + val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } - val removed = findRemovedLocked(favoritesForComponentKeys, it) - callback(removed.map { currentFavorites.getValue(componentName).getValue(it) } - .map(::createRemovedStatus) + - it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }) + val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it) + val controlsWithFavorite = + it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) } + callback( + currentFavorites.getValue(componentName) + .filter { it.controlId in removed } + .map(::createRemovedStatus) + controlsWithFavorite, + favoritesForComponentKeys + ) } } } private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus { val intent = Intent(context, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component) + putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP } val pendingIntent = PendingIntent.getActivity(context, @@ -243,17 +248,24 @@ class ControlsControllerImpl @Inject constructor ( } @GuardedBy("currentFavorites") - private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean { - val favorites = currentFavorites.get(componentName) ?: mutableMapOf() - val favoriteKeys = favorites.keys + private fun updateFavoritesLocked( + componentName: ComponentName, + list: List<Control>, + favoriteKeys: List<String> + ): Boolean { + val favorites = currentFavorites.get(componentName) ?: mutableListOf() if (favoriteKeys.isEmpty()) return false // early return var changed = false - list.forEach { - if (it.controlId in favoriteKeys) { - val value = favorites.getValue(it.controlId) - if (value.controlTitle != it.title || value.deviceType != it.deviceType) { - favorites[it.controlId] = value.copy(controlTitle = it.title, - deviceType = it.deviceType) + list.forEach { control -> + if (control.controlId in favoriteKeys) { + val index = favorites.indexOfFirst { it.controlId == control.controlId } + val value = favorites[index] + if (value.controlTitle != control.title || + value.deviceType != control.deviceType) { + favorites[index] = value.copy( + controlTitle = control.title, + deviceType = control.deviceType + ) changed = true } } @@ -263,14 +275,14 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private fun favoritesAsListLocked(): List<ControlInfo> { - return currentFavorites.flatMap { it.value.values } + return currentFavorites.flatMap { it.value } } override fun subscribeToFavorites() { if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { - currentFavorites.flatMap { it.value.values.toList() } + currentFavorites.flatMap { it.value } } bindingController.subscribe(favorites) } @@ -286,22 +298,19 @@ class ControlsControllerImpl @Inject constructor ( val listOfControls = synchronized(currentFavorites) { if (state) { if (controlInfo.component !in currentFavorites) { - currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>()) + currentFavorites.put(controlInfo.component, mutableListOf()) changed = true } val controlsForComponent = currentFavorites.getValue(controlInfo.component) - if (controlInfo.controlId !in controlsForComponent) { - controlsForComponent.put(controlInfo.controlId, controlInfo) + if (controlsForComponent.firstOrNull { + it.controlId == controlInfo.controlId + } == null) { + controlsForComponent.add(controlInfo) changed = true - } else { - if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) { - controlsForComponent.put(controlInfo.controlId, controlInfo) - changed = true - } } } else { changed = currentFavorites.get(controlInfo.component) - ?.remove(controlInfo.controlId) != null + ?.remove(controlInfo) != null } favoritesAsListLocked() } @@ -310,6 +319,19 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun replaceFavoritesForComponent( + componentName: ComponentName, + favorites: List<ControlInfo> + ) { + if (!confirmAvailability()) return + val filtered = favorites.filter { it.component == componentName } + val listOfControls = synchronized(currentFavorites) { + currentFavorites.put(componentName, filtered.toMutableList()) + favoritesAsListLocked() + } + persistenceWrapper.storeFavorites(listOfControls) + } + override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") @@ -317,7 +339,13 @@ class ControlsControllerImpl @Inject constructor ( } executor.execute { synchronized(currentFavorites) { - val changed = updateFavoritesLocked(componentName, listOf(control)) + val favoriteKeysForComponent = + currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList() + val changed = updateFavoritesLocked( + componentName, + listOf(control), + favoriteKeysForComponent + ) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } @@ -361,6 +389,12 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> { + return synchronized(currentFavorites) { + currentFavorites.get(componentName) ?: emptyList() + } + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") pw.println(" Available: $available") @@ -368,10 +402,8 @@ class ControlsControllerImpl @Inject constructor ( pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { - currentFavorites.forEach { - it.value.forEach { - pw.println(" ${it.value}") - } + favoritesAsListLocked().forEach { + pw.println(" ${ it }") } } pw.println(bindingController.toString()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index b12243964fc1..89caaceebb5c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R +import java.text.Collator import java.util.concurrent.Executor /** @@ -44,23 +45,27 @@ import java.util.concurrent.Executor * @param onAppSelected a callback to indicate that an app has been selected in the list. */ class AppAdapter( + backgroundExecutor: Executor, uiExecutor: Executor, lifecycle: Lifecycle, controlsListingController: ControlsListingController, private val layoutInflater: LayoutInflater, private val onAppSelected: (ComponentName?) -> Unit = {}, - private val favoritesRenderer: FavoritesRenderer + private val favoritesRenderer: FavoritesRenderer, + private val resources: Resources ) : RecyclerView.Adapter<AppAdapter.Holder>() { private var listOfServices = emptyList<CandidateInfo>() private val callback = object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(list: List<CandidateInfo>) { - uiExecutor.execute { - listOfServices = list.sortedBy { - it.loadLabel().toString() + backgroundExecutor.execute { + val collator = Collator.getInstance(resources.getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() } - notifyDataSetChanged() + listOfServices = list.sortedWith(localeComparator) + uiExecutor.execute(::notifyDataSetChanged) } } } 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 65dcc2b193d5..d3cabe67790e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -25,101 +25,150 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R -import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.ui.RenderInfo +private typealias ModelFavoriteChanger = (String, Boolean) -> Unit + /** * Adapter for binding [Control] information to views. * + * The model for this adapter is provided by a [FavoriteModel] that is set using + * [changeFavoritesModel]. This allows for updating the model if there's a reload. + * * @param layoutInflater an inflater for the views in the containing [RecyclerView] - * @param favoriteCallback a callback to be called when the favorite status of a [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite - * status. + * @param onlyFavorites set to true to only display favorites instead of all controls */ class ControlAdapter( private val layoutInflater: LayoutInflater, - private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit -) : RecyclerView.Adapter<ControlAdapter.Holder>() { + private val onlyFavorites: Boolean = false +) : RecyclerView.Adapter<Holder>() { + + companion object { + private const val TYPE_ZONE = 0 + private const val TYPE_CONTROL = 1 + } + + val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (getItemViewType(position) == TYPE_ZONE) 2 else 1 + } + } - var listOfControls = emptyList<ControlStatus>() + var modelList: List<ElementWrapper> = emptyList() + private var favoritesModel: FavoriteModel? = null - override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { - layoutParams.apply { - width = ViewGroup.LayoutParams.MATCH_PARENT + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return when (viewType) { + TYPE_CONTROL -> { + ControlHolder( + layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { + layoutParams.apply { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + elevation = 15f + }, + { id, favorite -> + favoritesModel?.changeFavoriteStatus(id, favorite) + }) + } + TYPE_ZONE -> { + ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) } - elevation = 15f - }) + else -> throw IllegalStateException("Wrong viewType: $viewType") + } } - override fun getItemCount() = listOfControls.size + fun changeFavoritesModel(favoritesModel: FavoriteModel) { + this.favoritesModel = favoritesModel + if (onlyFavorites) { + modelList = favoritesModel.favorites + } else { + modelList = favoritesModel.all + } + notifyDataSetChanged() + } + + override fun getItemCount() = modelList.size override fun onBindViewHolder(holder: Holder, index: Int) { - holder.bindData(listOfControls[index], favoriteCallback) + holder.bindData(modelList[index]) } + override fun getItemViewType(position: Int): Int { + return when (modelList[position]) { + is ZoneNameWrapper -> TYPE_ZONE + is ControlWrapper -> TYPE_CONTROL + } + } +} + +/** + * Holder for binding views in the [RecyclerView]- + * @param view the [View] for this [Holder] + */ +sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { + /** - * Holder for binding views in the [RecyclerView]- + * Bind the data from the model into the view */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { - private val icon: ImageView = itemView.requireViewById(R.id.icon) - private val title: TextView = itemView.requireViewById(R.id.title) - private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) - private val removed: TextView = itemView.requireViewById(R.id.status) - private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { - visibility = View.VISIBLE - } + abstract fun bindData(wrapper: ElementWrapper) +} - /** - * Bind data to the view - * @param data information about the [Control] - * @param callback a callback to be called when the favorite status of the [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite status. - */ - fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { - val renderInfo = getRenderInfo(data.control.deviceType, data.favorite) - title.text = data.control.title - subtitle.text = data.control.subtitle - favorite.isChecked = data.favorite - removed.text = if (data.removed) "Removed" else "" - favorite.setOnClickListener { - val infoBuilder = ControlInfo.Builder().apply { - controlId = data.control.controlId - controlTitle = data.control.title - deviceType = data.control.deviceType - } - callback(infoBuilder, favorite.isChecked) - } - itemView.setOnClickListener { - favorite.performClick() - } - applyRenderInfo(renderInfo) - } +/** + * Holder for using with [ZoneNameWrapper] to display names of zones. + */ +private class ZoneHolder(view: View) : Holder(view) { + private val zone: TextView = itemView as TextView - private fun getRenderInfo( - @DeviceTypes.DeviceType deviceType: Int, - favorite: Boolean - ): RenderInfo { - return RenderInfo.lookup(deviceType, favorite) - } + override fun bindData(wrapper: ElementWrapper) { + wrapper as ZoneNameWrapper + zone.text = wrapper.zoneName + } +} - private fun applyRenderInfo(ri: RenderInfo) { - val context = itemView.context - val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) +/** + * Holder for using with [ControlWrapper] to display names of zones. + * @param favoriteCallback this callback will be called whenever the favorite state of the + * [Control] this view represents changes. + */ +private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) { + private val icon: ImageView = itemView.requireViewById(R.id.icon) + private val title: TextView = itemView.requireViewById(R.id.title) + private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) + private val removed: TextView = itemView.requireViewById(R.id.status) + private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { + visibility = View.VISIBLE + } - icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) - icon.setImageTintList(fg) + override fun bindData(wrapper: ElementWrapper) { + wrapper as ControlWrapper + val data = wrapper.controlStatus + val renderInfo = getRenderInfo(data.control.deviceType) + title.text = data.control.title + subtitle.text = data.control.subtitle + favorite.isChecked = data.favorite + removed.text = if (data.removed) "Removed" else "" + favorite.setOnClickListener { + favoriteCallback(data.control.controlId, favorite.isChecked) } + applyRenderInfo(renderInfo) } - fun setItems(list: List<ControlStatus>) { - listOfControls = list - notifyDataSetChanged() + private fun getRenderInfo( + @DeviceTypes.DeviceType deviceType: Int + ): RenderInfo { + return RenderInfo.lookup(deviceType, true) + } + + private fun applyRenderInfo(ri: RenderInfo) { + val context = itemView.context + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) } } 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 be5258344492..1e5237148188 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -18,10 +18,14 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub +import android.widget.Button import android.widget.TextView import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher @@ -41,13 +45,36 @@ class ControlsFavoritingActivity @Inject constructor( companion object { private const val TAG = "ControlsFavoritingActivity" const val EXTRA_APP = "extra_app_label" - const val EXTRA_COMPONENT = "extra_component" } - private lateinit var recyclerView: RecyclerView - private lateinit var adapter: ControlAdapter + private lateinit var recyclerViewAll: RecyclerView + private lateinit var adapterAll: ControlAdapter + private lateinit var recyclerViewFavorites: RecyclerView + private lateinit var adapterFavorites: ControlAdapter private var component: ComponentName? = null + private var currentModel: FavoriteModel? = null + private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + /* dragDirs */ ItemTouchHelper.UP + or ItemTouchHelper.DOWN + or ItemTouchHelper.LEFT + or ItemTouchHelper.RIGHT, + /* swipeDirs */0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return currentModel?.onMoveItem( + viewHolder.adapterPosition, target.adapterPosition) != null + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun isItemViewSwipeEnabled() = false + } + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -62,41 +89,77 @@ class ControlsFavoritingActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_favorites + inflate() + } val app = intent.getCharSequenceExtra(EXTRA_APP) - component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) - - // If we have no component name, there's not much we can do. - val callback = component?.let { - { infoBuilder: ControlInfo.Builder, status: Boolean -> - infoBuilder.componentName = it - controller.changeFavoriteStatus(infoBuilder.build(), status) - } - } ?: { _, _ -> Unit } + component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - recyclerView = requireViewById(R.id.list) - adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) - recyclerView.adapter = adapter - recyclerView.layoutManager = GridLayoutManager(applicationContext, 2) - val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) - recyclerView.addItemDecoration(MarginItemDecorator(margin, margin)) + setUpRecyclerViews() requireViewById<TextView>(R.id.title).text = app?.let { it } ?: resources.getText(R.string.controls_favorite_default_title) requireViewById<TextView>(R.id.subtitle).text = resources.getText(R.string.controls_favorite_subtitle) - currentUserTracker.startTracking() - } + requireViewById<Button>(R.id.done).setOnClickListener { + if (component == null) return@setOnClickListener + val favoritesForStorage = currentModel?.favorites?.map { + with(it.controlStatus.control) { + ControlInfo(component!!, controlId, title, deviceType) + } + } + if (favoritesForStorage != null) { + controller.replaceFavoritesForComponent(component!!, favoritesForStorage) + finishAffinity() + } + } - override fun onResume() { - super.onResume() component?.let { - controller.loadForComponent(it) { + controller.loadForComponent(it) { allControls, favoriteKeys -> executor.execute { - adapter.setItems(it) + val favoriteModel = FavoriteModel( + allControls, + favoriteKeys, + allAdapter = adapterAll, + favoritesAdapter = adapterFavorites) + adapterAll.changeFavoritesModel(favoriteModel) + adapterFavorites.changeFavoritesModel(favoriteModel) + currentModel = favoriteModel } } } + + currentUserTracker.startTracking() + } + + private fun setUpRecyclerViews() { + val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + val layoutInflater = LayoutInflater.from(applicationContext) + + adapterAll = ControlAdapter(layoutInflater) + recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply { + adapter = adapterAll + layoutManager = GridLayoutManager(applicationContext, 2).apply { + spanSizeLookup = adapterAll.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + + adapterFavorites = ControlAdapter(layoutInflater, true) + recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply { + layoutManager = GridLayoutManager(applicationContext, 2) + adapter = adapterFavorites + addItemDecoration(itemDecorator) + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites) + } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 645e929d6a10..ad4bdefdff3e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -63,11 +64,21 @@ class ControlsProviderSelectorActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_apps + inflate() + } recyclerView = requireViewById(R.id.list) - recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, - LayoutInflater.from(this), ::launchFavoritingActivity, - FavoritesRenderer(resources, controlsController::countFavoritesForComponent)) + recyclerView.adapter = AppAdapter( + backExecutor, + executor, + lifecycle, + listingController, + LayoutInflater.from(this), + ::launchFavoritingActivity, + FavoritesRenderer(resources, controlsController::countFavoritesForComponent), + resources) recyclerView.layoutManager = LinearLayoutManager(applicationContext) requireViewById<TextView>(R.id.title).text = @@ -89,11 +100,16 @@ class ControlsProviderSelectorActivity @Inject constructor( .apply { putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra(Intent.EXTRA_COMPONENT_NAME, it) + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(intent) } } } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt new file mode 100644 index 000000000000..6bade0aeb998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 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.management + +import android.text.TextUtils +import android.util.Log +import com.android.systemui.controls.ControlStatus +import java.util.Collections +import java.util.Comparator + +/** + * Model for keeping track of current favorites and their order. + * + * This model is to be used with two [ControlAdapter] one that shows only favorites in the current + * order and another that shows all controls, separated by zone. When the favorite state of any + * control is modified or when the favorites are reordered, the adapters are notified of the change. + * + * @param listControls list of all the [ControlStatus] to display. This includes controls currently + * marked as favorites as well as those that have been removed (not returned + * from load) + * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those + * that have been removed. + * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites + * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls + */ +class FavoriteModel( + private val listControls: List<ControlStatus>, + listFavoritesIds: List<String>, + private val favoritesAdapter: ControlAdapter, + private val allAdapter: ControlAdapter +) { + + companion object { + private const val TAG = "FavoriteModel" + } + + /** + * List of favorite controls ([ControlWrapper]) in order. + * + * Initially, this list will give a list of wrappers in the order specified by the constructor + * variable `listFavoriteIds`. + * + * As the favorites are added, removed or moved, this list will keep track of those changes. + */ + val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> + ControlWrapper(listControls.first { it.control.controlId == id }) + }.toMutableList() + + /** + * List of all controls by zones. + * + * Lists all the controls with the zone names interleaved as a flat list. After each zone name, + * the controls in that zone are listed. Zones are listed in alphabetical order + */ + val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } + .mapKeys { it.key ?: "" } // map null to empty + .toSortedMap(CharSequenceComparator()) + .flatMap { + val controls = it.value.map { ControlWrapper(it) } + if (!TextUtils.isEmpty(it.key)) { + listOf(ZoneNameWrapper(it.key)) + controls + } else { + controls + } + } + + /** + * Change the favorite status of a [Control]. + * + * This can be invoked from any of the [ControlAdapter]. It will change the status of that + * control and either add it to the list of favorites (at the end) or remove it from it. + * + * Removing the favorite status from a Removed control will make it disappear completely if + * changes are saved. + * + * @param controlId the id of the [Control] to change the status + * @param favorite `true` if and only if it's set to be a favorite. + */ + fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + favorites as MutableList + val index = all.indexOfFirst { + it is ControlWrapper && it.controlStatus.control.controlId == controlId + } + val control = (all[index] as ControlWrapper).controlStatus + if (control.favorite == favorite) { + Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") + return + } else { + control.favorite = favorite + } + allAdapter.notifyItemChanged(index) + if (favorite) { + favorites.add(all[index] as ControlWrapper) + favoritesAdapter.notifyItemInserted(favorites.size - 1) + } else { + val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } + favorites.removeAt(i) + favoritesAdapter.notifyItemRemoved(i) + } + } + + /** + * Move items in the model and notify the [favoritesAdapter]. + */ + fun onMoveItem(from: Int, to: Int) { + if (from < to) { + for (i in from until to) { + Collections.swap(favorites, i, i + 1) + } + } else { + for (i in from downTo to + 1) { + Collections.swap(favorites, i, i - 1) + } + } + favoritesAdapter.notifyItemMoved(from, to) + } +} + +/** + * Compares [CharSequence] as [String]. + * + * It will have empty strings as the first element + */ +class CharSequenceComparator : Comparator<CharSequence> { + override fun compare(p0: CharSequence?, p1: CharSequence?): Int { + if (p0 == null && p1 == null) return 0 + else if (p0 == null && p1 != null) return -1 + else if (p0 != null && p1 == null) return 1 + return p0.toString().compareTo(p1.toString()) + } +} + +/** + * Wrapper classes for the different types of elements shown in the [RecyclerView]s in + * [ControlsFavoritingActivity]. + */ +sealed class ElementWrapper +data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() +data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 53a23b89f943..0ec739fecaa7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -30,6 +30,7 @@ import android.view.IWindowManager; import android.view.LayoutInflater; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.qualifiers.Background; @@ -191,6 +192,12 @@ public class DependencyProvider { return new AlwaysOnDisplayPolicy(context); } + /***/ + @Provides + public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { + return new NotificationMessagingUtil(context); + } + /** */ @Provides public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7b541991088c..f068d9c10e86 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -68,7 +69,9 @@ import dagger.Provides; NotificationsModule.class, PeopleHubModule.class, }, - subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) + subcomponents = {StatusBarComponent.class, + NotificationRowComponent.class, + ExpandableNotificationRowComponent.class}) public abstract class SystemUIModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 4e887262659e..ca2226f29528 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -167,6 +167,10 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi // again, it will only show after the brightness sensor has stabilized, // avoiding a potential flicker. scrimOpacity = 255; + } else if (!mScreenOff && mLightSensor == null) { + // No light sensor but previous state turned the screen black. Make the scrim + // transparent and below views visible. + scrimOpacity = 0; } else if (brightnessReady) { // Only unblank scrim once brightness is ready. scrimOpacity = computeScrimOpacity(sensorValue); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b3fc027d1ac7..a3cd5fdd771b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1045,7 +1045,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); view.setOnClickListener(v -> onClickItem(position)); - view.setOnLongClickListener(v -> onLongClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } return view; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java index ae380b72f5e0..6498b9161be7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java @@ -93,7 +93,6 @@ public class WorkLockActivityController { return mIatm.startActivityAsUser( mContext.getIApplicationThread() /*caller*/, mContext.getBasePackageName() /*callingPackage*/, - mContext.getFeatureId() /*callingFeatureId*/, intent /*intent*/, intent.resolveTypeIfNeeded(mContext.getContentResolver()) /*resolvedType*/, null /*resultTo*/, diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 6f03f18ef64b..41b31306a931 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -345,6 +345,14 @@ public class PipBoundsHandler { } /** + * Sets the current bound with the currently store aspect ratio. + * @param stackBounds + */ + public void transformBoundsToAspectRatio(Rect stackBounds) { + transformBoundsToAspectRatio(stackBounds, mAspectRatio, true); + } + + /** * Set the current bounds (or the default bounds if there are no current bounds) with the * specified aspect ratio. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index f9b18cf17abe..c7bfc06829b3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -280,7 +280,9 @@ public class PipMenuActivityController { if (mToActivityMessenger != null) { Bundle data = new Bundle(); data.putInt(EXTRA_MENU_STATE, menuState); - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + if (stackBounds != null) { + data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + } data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds); data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java new file mode 100644 index 000000000000..9fb623471bf7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2020 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.pip.phone; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_USER_RESIZE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.input.InputManager; +import android.os.Looper; +import android.provider.DeviceConfig; +import android.util.DisplayMetrics; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; +import android.view.MotionEvent; + +import com.android.internal.policy.TaskResizingAlgorithm; +import com.android.systemui.R; +import com.android.systemui.pip.PipBoundsHandler; + +import java.util.concurrent.Executor; + +/** + * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to + * trigger dynamic resize. + */ +public class PipResizeGestureHandler { + + private static final String TAG = "PipResizeGestureHandler"; + + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private final PipBoundsHandler mPipBoundsHandler; + private final PipTouchHandler mPipTouchHandler; + private final PipMotionHelper mMotionHelper; + private final int mDisplayId; + private final Executor mMainExecutor; + private final Region mTmpRegion = new Region(); + + private final PointF mDownPoint = new PointF(); + private final Point mMaxSize = new Point(); + private final Point mMinSize = new Point(); + private final Rect mTmpBounds = new Rect(); + private final int mDelta; + + private boolean mAllowGesture = false; + private boolean mIsAttached; + private boolean mIsEnabled; + private boolean mEnablePipResize; + + private InputMonitor mInputMonitor; + private InputEventReceiver mInputEventReceiver; + + private int mCtrlType; + + public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, + PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper) { + final Resources res = context.getResources(); + context.getDisplay().getMetrics(mDisplayMetrics); + mDisplayId = context.getDisplayId(); + mMainExecutor = context.getMainExecutor(); + mPipBoundsHandler = pipBoundsHandler; + mPipTouchHandler = pipTouchHandler; + mMotionHelper = motionHelper; + + context.getDisplay().getRealSize(mMaxSize); + mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); + + mEnablePipResize = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_USER_RESIZE, + /* defaultValue = */ false); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor, + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(PIP_USER_RESIZE)) { + mEnablePipResize = properties.getBoolean( + PIP_USER_RESIZE, /* defaultValue = */ false); + } + } + }); + } + + private void disposeInputChannel() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + void onActivityPinned() { + mIsAttached = true; + updateIsEnabled(); + } + + void onActivityUnpinned() { + mIsAttached = false; + updateIsEnabled(); + } + + private void updateIsEnabled() { + boolean isEnabled = mIsAttached && mEnablePipResize; + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + disposeInputChannel(); + + if (mIsEnabled) { + // Register input event receiver + mInputMonitor = InputManager.getInstance().monitorGestureInput( + "pip-resize", mDisplayId); + mInputEventReceiver = new SysUiInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper()); + } + } + + private void onInputEvent(InputEvent ev) { + if (ev instanceof MotionEvent) { + onMotionEvent((MotionEvent) ev); + } + } + + private boolean isWithinTouchRegion(int x, int y) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + if (currentPipBounds == null) { + return false; + } + + mTmpBounds.set(currentPipBounds); + mTmpBounds.inset(-mDelta, -mDelta); + + mTmpRegion.set(mTmpBounds); + mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE); + + if (mTmpRegion.contains(x, y)) { + if (x < currentPipBounds.left) { + mCtrlType |= CTRL_LEFT; + } + if (x > currentPipBounds.right) { + mCtrlType |= CTRL_RIGHT; + } + if (y < currentPipBounds.top) { + mCtrlType |= CTRL_TOP; + } + if (y > currentPipBounds.bottom) { + mCtrlType |= CTRL_BOTTOM; + } + return true; + } + return false; + } + + private void onMotionEvent(MotionEvent ev) { + int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + if (mAllowGesture) { + mDownPoint.set(ev.getX(), ev.getY()); + } + + } else if (mAllowGesture) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + Rect newSize = TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x, + mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, + true, true); + mPipBoundsHandler.transformBoundsToAspectRatio(newSize); + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + // We do not support multi touch for resizing via drag + mAllowGesture = false; + break; + case MotionEvent.ACTION_MOVE: + // Capture inputs + mInputMonitor.pilferPointers(); + //TODO: Actually do resize here. + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + //TODO: Finish resize operation here. + mMotionHelper.synchronizePinnedStackBounds(); + mCtrlType = CTRL_NONE; + mAllowGesture = false; + break; + } + } + } + + void updateMaxSize(int maxX, int maxY) { + mMaxSize.set(maxX, maxY); + } + + void updateMiniSize(int minX, int minY) { + mMinSize.set(minX, minY); + } + + class SysUiInputEventReceiver extends InputEventReceiver { + SysUiInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + public void onInputEvent(InputEvent event) { + PipResizeGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 65cc666d5164..924edb6fe312 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -73,6 +73,7 @@ public class PipTouchHandler { private final ViewConfiguration mViewConfig; private final PipMenuListener mMenuListener = new PipMenuListener(); private final PipBoundsHandler mPipBoundsHandler; + private final PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; @@ -188,6 +189,8 @@ public class PipTouchHandler { mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager, mMenuController, mSnapAlgorithm, mFlingAnimationUtils); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper); mTouchState = new PipTouchState(mViewConfig, mHandler, () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), mMovementBounds, true /* allowMenuTimeout */, willResizeMenu())); @@ -227,6 +230,7 @@ public class PipTouchHandler { public void onActivityPinned() { cleanUp(); mShowPipMenuOnAnimationEnd = true; + mPipResizeGestureHandler.onActivityPinned(); } public void onActivityUnpinned(ComponentName topPipActivity) { @@ -234,11 +238,14 @@ public class PipTouchHandler { // Clean up state after the last PiP activity is removed cleanUp(); } + mPipResizeGestureHandler.onActivityUnpinned(); } public void onPinnedStackAnimationEnded() { // Always synchronize the motion helper bounds once PiP animations finish mMotionHelper.synchronizePinnedStackBounds(); + mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(), + mMotionHelper.getBounds().height()); if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -279,6 +286,7 @@ public class PipTouchHandler { Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y); mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); + mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, bottomOffset); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index ae6162219afa..c118630fe91a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -191,6 +191,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile.setLabel(tile.getLabel()); mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); + mTile.setStateDescription(tile.getStateDescription()); mTile.setState(tile.getState()); } @@ -345,6 +346,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.contentDescription = state.label; } + if (mTile.getStateDescription() != null) { + state.stateDescription = mTile.getStateDescription(); + } else { + state.stateDescription = null; + } + if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 2fe64d26f3ac..8feee10c7e83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -63,7 +63,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private String mAccessibilityClass; private boolean mTileState; private boolean mCollapsedView; - private boolean mClicked; private boolean mShowRippleEffect = true; private final ImageView mBg; @@ -230,13 +229,35 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); + final StringBuilder stateDescription = new StringBuilder(); + switch (state.state) { + case Tile.STATE_UNAVAILABLE: + stateDescription.append(mContext.getString(R.string.tile_unavailable)); + break; + case Tile.STATE_INACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_off)); + } + break; + case Tile.STATE_ACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_on)); + } + break; + default: + break; + } + if (!TextUtils.isEmpty(state.stateDescription)) { + stateDescription.append(", "); + stateDescription.append(state.stateDescription); + } + setStateDescription(stateDescription.toString()); mAccessibilityClass = state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { boolean newState = ((BooleanState) state).value; if (mTileState != newState) { - mClicked = false; mTileState = newState; } } @@ -288,23 +309,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } @Override - public boolean performClick() { - mClicked = true; - return super.performClick(); - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); if (!TextUtils.isEmpty(mAccessibilityClass)) { event.setClassName(mAccessibilityClass); - if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); - event.setContentDescription(label); - event.setChecked(b); - } } } @@ -316,11 +324,13 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { if (!TextUtils.isEmpty(mAccessibilityClass)) { info.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); + String label = getResources().getString( + mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); + // Set the text here for tests in + // android.platform.test.scenario.sysui.quicksettings. Can be removed when + // UiObject2 has a new getStateDescription() API and tests are updated. info.setText(label); - info.setChecked(b); + info.setChecked(mTileState); info.setCheckable(true); if (isLongClickable()) { info.addAction( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index e1b61c670a12..e559694df375 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -109,10 +109,31 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + /** + * Provides a new {@link TState} of the appropriate type to use between this tile and the + * corresponding view. + * + * @return new state to use by the tile. + */ public abstract TState newTileState(); + /** + * Handles clicks by the user. + * + * Calls to the controller should be made here to set the new state of the device. + */ abstract protected void handleClick(); + /** + * Update state of the tile based on device state + * + * Called whenever the state of the tile needs to be updated, either after user + * interaction or from callbacks from the controller. It populates {@code state} with the + * information to display to the user. + * + * @param state {@link TState} to populate with information to display + * @param arg additional arguments needed to populate {@code state} + */ abstract protected void handleUpdateState(TState state, Object arg); /** @@ -177,6 +198,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return mHost; } + /** + * Return the {@link QSIconView} to be used by this tile's view. + * + * @param context view context for the view + * @return icon view for this tile + */ public QSIconView createTileView(Context context) { return new QSIconViewImpl(context); } @@ -298,11 +325,20 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mCallbacks.clear(); } + /** + * Handles secondary click on the tile. + * + * Defaults to {@link QSTileImpl#handleClick} + */ protected void handleSecondaryClick() { // Default to normal click. handleClick(); } + /** + * Handles long click on the tile by launching the {@link Intent} defined in + * {@link QSTileImpl#getLongClickIntent} + */ protected void handleLongClick() { if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) { showDetail(true); @@ -312,6 +348,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy getLongClickIntent(), 0); } + /** + * Returns an intent to be launched when the tile is long pressed. + * + * @return the intent to launch + */ public abstract Intent getLongClickIntent(); protected void handleRefreshState(Object arg) { @@ -427,6 +468,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * Provides a default label for the tile. + * @return default label for the tile. + */ public abstract CharSequence getTileLabel(); public static int getColorForState(Context context, int state) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9282a2e3b312..361b6c1b1260 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,25 +134,27 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.secondaryLabel = TextUtils.emptyIfNull( getSecondaryLabel(enabled, connecting, connected, state.isTransient)); + state.contentDescription = state.label; + state.stateDescription = ""; if (enabled) { if (connected) { state.icon = new BluetoothConnectedTileIcon(); if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) { state.label = mController.getConnectedDeviceName(); } - state.contentDescription = + state.stateDescription = mContext.getString(R.string.accessibility_bluetooth_name, state.label) + ", " + state.secondaryLabel; } else if (state.isTransient) { state.icon = ResourceIcon.get( com.android.internal.R.drawable.ic_bluetooth_transient_animation); - state.contentDescription = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } else { state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_bluetooth) + "," - + mContext.getString(R.string.accessibility_not_connected); + R.string.accessibility_quick_settings_bluetooth); + state.stateDescription = mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 32b051e35604..58de0575fa75 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -183,6 +183,7 @@ public class CastTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; + state.stateDescription = ""; state.value = false; final List<CastDevice> devices = mController.getCastDevices(); boolean connecting = false; @@ -192,8 +193,9 @@ public class CastTile extends QSTileImpl<BooleanState> { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); - state.contentDescription = state.contentDescription + "," - + mContext.getString(R.string.accessibility_cast_name, state.label); + state.stateDescription = state.stateDescription + "," + + mContext.getString( + R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { @@ -217,9 +219,8 @@ public class CastTile extends QSTileImpl<BooleanState> { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; - state.contentDescription = state.contentDescription + ", " + mContext.getString( - R.string.accessibility_quick_settings_not_available, noWifi); } + state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 22470c7f5af5..d5f86c951407 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -194,17 +194,13 @@ public class CellularTile extends QSTileImpl<SignalState> { state.secondaryLabel = r.getString(R.string.cell_data_off); } - - // TODO(b/77881974): Instead of switching out the description via a string check for - // we need to have two strings provided by the MobileIconGroup. - final CharSequence contentDescriptionSuffix; + state.contentDescription = state.label; if (state.state == Tile.STATE_INACTIVE) { - contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description); + // This information is appended later by converting the Tile.STATE_INACTIVE state. + state.stateDescription = ""; } else { - contentDescriptionSuffix = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } - - state.contentDescription = state.label + ", " + contentDescriptionSuffix; } private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 52d1a5b3b991..9215da4cda9a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -240,6 +240,8 @@ public class DndTile extends QSTileImpl<BooleanState> { zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); + // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier + // to understand. switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: state.contentDescription = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index dafdd89ee62c..792c36477962 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -102,14 +102,13 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); state.secondaryLabel = ""; + state.stateDescription = ""; if (!mFlashlightController.isAvailable()) { state.icon = mIcon; state.slash.isSlashed = true; state.secondaryLabel = mContext.getString( R.string.quick_settings_flashlight_camera_in_use); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_flashlight_unavailable) - + ", " + state.secondaryLabel; + state.stateDescription = state.secondaryLabel; state.state = Tile.STATE_UNAVAILABLE; return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 42f80109e045..91b3ae480af9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -148,6 +148,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.secondaryLabel = getSecondaryLabel( isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); + state.stateDescription = state.secondaryLabel; } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index fbdca3ba1c7b..e617867eb10e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -105,15 +105,8 @@ public class LocationTile extends QSTileImpl<BooleanState> { } state.icon = mIcon; state.slash.isSlashed = !state.value; - if (locationEnabled) { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_on); - } else { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_off); - } + state.label = mContext.getString(R.string.quick_settings_location_label); + state.contentDescription = state.label; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index b7ce101cacab..6e8dcf36bacc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -195,6 +195,7 @@ public class WifiTile extends QSTileImpl<SignalState> { state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; final StringBuffer minimalContentDescription = new StringBuffer(); + final StringBuffer minimalStateDescription = new StringBuffer(); final Resources r = mContext.getResources(); if (isTransient) { state.icon = ResourceIcon.get( @@ -219,13 +220,14 @@ public class WifiTile extends QSTileImpl<SignalState> { mContext.getString(R.string.quick_settings_wifi_label)).append(","); if (state.value) { if (wifiConnected) { - minimalContentDescription.append(cb.wifiSignalContentDescription).append(","); + minimalStateDescription.append(cb.wifiSignalContentDescription); minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); if (!TextUtils.isEmpty(state.secondaryLabel)) { minimalContentDescription.append(",").append(state.secondaryLabel); } } } + state.stateDescription = minimalStateDescription.toString(); state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 7853dc388bcb..e54ee51fb9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -103,14 +103,11 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements state.icon = mIcon; if (state.value) { state.slash.isSlashed = false; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_on); } else { state.slash.isSlashed = true; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_off); } state.label = mContext.getString(R.string.quick_settings_work_mode_label); + state.contentDescription = state.label; state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 880b8f8776e8..4f38a15824a4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -143,7 +144,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final float BACKGROUND_ALPHA = 0.5f; private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f; private static final float ROUNDED_CORNER_RADIUS = .05f; - private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000; + private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000; private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; @@ -162,6 +163,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final HorizontalScrollView mActionsContainer; private final LinearLayout mActionsView; private final ImageView mBackgroundProtection; + private final FrameLayout mDismissButton; private Bitmap mScreenBitmap; private AnimatorSet mScreenshotAnimation; @@ -170,6 +172,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private float mScreenshotOffsetYPx; private float mScreenshotHeightPx; private float mCornerScale; + private float mDismissButtonSize; private AsyncTask<Void, Void, Void> mSaveInBgTask; @@ -216,19 +219,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); + mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); + mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotLayout.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - clearScreenshot("tap_outside"); - } - // Intercept and ignore all touch events - return true; - }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -254,6 +252,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale) / (float) mDisplayMetrics.widthPixels; + mDismissButtonSize = resources.getDimensionPixelSize( + R.dimen.screenshot_dismiss_button_tappable_size); // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); @@ -271,6 +271,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Rect actionsRect = new Rect(); mActionsContainer.getBoundsOnScreen(actionsRect); touchRegion.op(actionsRect, Region.Op.UNION); + Rect dismissRect = new Rect(); + mDismissButton.getBoundsOnScreen(dismissRect); + touchRegion.op(dismissRect, Region.Op.UNION); inoutInfo.touchableRegion.set(touchRegion); } @@ -408,6 +411,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsContainer.setVisibility(View.GONE); mBackgroundView.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -615,6 +619,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotView.setTranslationX(t * finalPos.x); mScreenshotView.setTranslationY(t * finalPos.y); }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Rect bounds = new Rect(); + mScreenshotView.getBoundsOnScreen(bounds); + mDismissButton.setX(bounds.right - mDismissButtonSize / 2f); + mDismissButton.setY(bounds.top - mDismissButtonSize / 2f); + mDismissButton.setVisibility(View.VISIBLE); + } + }); return anim; } @@ -686,14 +701,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection.setAlpha(t); mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mScreenshotView.requestFocus(); - mScreenshotView.setElevation(50); - } - }); return animator; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 41c1b7b5fae8..006d40ddbac5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; @@ -153,6 +154,7 @@ public final class NotificationEntry extends ListEntry { private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view + private ExpandableNotificationRowController mRowController; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; @@ -424,6 +426,14 @@ public final class NotificationEntry extends ListEntry { this.row = row; } + public ExpandableNotificationRowController getRowController() { + return mRowController; + } + + public void setRowController(ExpandableNotificationRowController controller) { + mRowController = controller; + } + @Nullable public List<NotificationEntry> getChildren() { if (row == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index ecf62db4680b..e8a62e48e75e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.Nullable; @@ -40,19 +39,19 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; @@ -67,35 +66,28 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationGroupManager mGroupManager; - private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final Context mContext; private final NotifBindPipeline mNotifBindPipeline; private final RowContentBindStage mRowContentBindStage; private final NotificationMessagingUtil mMessagingUtil; - private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = - this::logNotificationExpansion; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; - private final boolean mAllowLongPress; - private final KeyguardBypassController mKeyguardBypassController; - private final StatusBarStateController mStatusBarStateController; private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private HeadsUpManager mHeadsUpManager; private NotificationRowContentBinder.InflationCallback mInflationCallback; - private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; private final Provider<RowInflaterTask> mRowInflaterTaskProvider; - private final NotificationLogger mNotificationLogger; + private final ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; @Inject public NotificationRowBinderImpl( Context context, + NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotifBindPipeline notifBindPipeline, @@ -107,21 +99,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationGutsManager notificationGutsManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, - NotificationLogger logger) { + ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) { mContext = context; mNotifBindPipeline = notifBindPipeline; mRowContentBindStage = rowContentBindStage; - mMessagingUtil = new NotificationMessagingUtil(context); + mMessagingUtil = notificationMessagingUtil; mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mAllowLongPress = allowLongPress; - mKeyguardBypassController = keyguardBypassController; - mStatusBarStateController = statusBarStateController; - mGroupManager = notificationGroupManager; - mGutsManager = notificationGutsManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mRowInflaterTaskProvider = rowInflaterTaskProvider; - mNotificationLogger = logger; + mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder; } /** @@ -129,13 +116,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { */ public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, - HeadsUpManager headsUpManager, BindRowCallback bindRowCallback) { mPresenter = presenter; mListContainer = listContainer; - mHeadsUpManager = headsUpManager; mBindRowCallback = bindRowCallback; - mOnAppOpsClickListener = mGutsManager::openGuts; } public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { @@ -150,9 +134,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { * Inflates the views for the given entry (possibly asynchronously). */ @Override - public void inflateViews( - NotificationEntry entry, - Runnable onDismissRunnable) + public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable) throws InflationException { ViewGroup parent = mListContainer.getViewParentForNotification(entry); PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, @@ -163,12 +145,26 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.updateIcons(mContext, sbn); entry.reset(); updateNotification(entry, pmUser, sbn, entry.getRow()); - entry.getRow().setOnDismissRunnable(onDismissRunnable); + entry.getRowController().setOnDismissRunnable(onDismissRunnable); } else { entry.createIcons(mContext, sbn); mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, row -> { - bindRow(entry, pmUser, sbn, row, onDismissRunnable); + // Setup the controller for the view. + ExpandableNotificationRowComponent component = + mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(row) + .notificationEntry(entry) + .onDismissRunnable(onDismissRunnable) + .inflationCallback(mInflationCallback) + .rowContentBindStage(mRowContentBindStage) + .onExpandClickListener(mPresenter) + .build(); + ExpandableNotificationRowController rowController = + component.getExpandableNotificationRowController(); + rowController.init(); + entry.setRowController(rowController); + bindRow(entry, pmUser, sbn, row); updateNotification(entry, pmUser, sbn, row); }); } @@ -176,55 +172,12 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { //TODO: This method associates a row with an entry, but eventually needs to not do that private void bindRow(NotificationEntry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row, - Runnable onDismissRunnable) { - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBindStage, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } + StatusBarNotification sbn, ExpandableNotificationRow row) { mListContainer.bindRow(row); mNotificationRemoteInputManager.bindRow(row); - - row.setOnDismissRunnable(onDismissRunnable); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - entry.setRow(row); row.setEntry(entry); mNotifBindPipeline.manageRow(entry, row); - mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -307,10 +260,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Objects.requireNonNull(mNotificationClicker).register(row, sbn); } - private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mNotificationLogger.onExpansionChanged(key, userAction, expanded); - } - /** Callback for when a row is bound to an entry. */ public interface BindRowCallback { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 254b64ffcd90..3e0bcbb796bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -90,7 +90,6 @@ class NotificationsControllerImpl @Inject constructor( notificationRowBinder.setUpWithPresenter( presenter, listContainer, - headsUpManager, bindRowCallback) if (featureFlags.isNewNotifPipelineEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 50a20374fee5..3eac229af3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -33,16 +33,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import com.android.systemui.Dependency; +import com.android.systemui.Gefingerpoken; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.DoubleTapHelper; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} @@ -94,14 +92,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private int mTintedRippleColor; - protected int mNormalRippleColor; - private final AccessibilityManager mAccessibilityManager; - private final DoubleTapHelper mDoubleTapHelper; + private int mNormalRippleColor; + private Gefingerpoken mTouchHandler; private boolean mDimmed; - protected int mBgTint = NO_COLOR; - private float mBgAlpha = 1f; + int mBgTint = NO_COLOR; /** * Flag to indicate that the notification has been touched once and the second touch will @@ -116,7 +112,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; private Interpolator mCurrentAlphaInterpolator; - protected NotificationBackgroundView mBackgroundNormal; + NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; private RectF mAppearAnimationRect = new RectF(); @@ -130,7 +126,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mLastInSection; private boolean mFirstInSection; private boolean mIsBelowSpeedBump; - private final FalsingManager mFalsingManager; private float mNormalBackgroundVisibilityAmount; private float mDimmedBackgroundFadeInAmount = -1; @@ -154,38 +149,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private boolean mNeedsDimming; private int mDimmedAlpha; - private boolean mBlockNextTouch; private boolean mIsHeadsUpAnimation; private int mHeadsUpAddStartLocation; private float mHeadsUpLocation; private boolean mIsAppearing; private boolean mDismissed; private boolean mRefocusOnDismiss; + private OnDimmedListener mOnDimmedListener; + private AccessibilityManager mAccessibilityManager; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); - mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller. setClipChildren(false); setClipToPadding(false); updateColors(); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - - mDoubleTapHelper = new DoubleTapHelper(this, (active) -> { - if (active) { - makeActive(); - } else { - makeInactive(true /* animate */); - } - }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); initDimens(); } - public FalsingManager getFalsingManager() { - return mFalsingManager; - } - private void updateColors() { mNormalColor = mContext.getColor(R.color.notification_material_background_color); mTintedRippleColor = mContext.getColor( @@ -236,32 +218,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); } - private final Runnable mTapTimeoutRunnable = new Runnable() { - @Override - public void run() { - makeInactive(true /* animate */); - } - }; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN - && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - if (!mActivated) { - return true; - } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { - mBlockNextTouch = true; - makeInactive(true /* animate */); - return true; - } + if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { + return true; } return super.onInterceptTouchEvent(ev); } - private boolean isTouchExplorationEnabled() { - return mAccessibilityManager.isTouchExplorationEnabled(); - } - protected boolean disallowSingleClick(MotionEvent ev) { return false; } @@ -270,25 +235,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return false; } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result; - if (mBlockNextTouch) { - mBlockNextTouch = false; - return false; - } - if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { - boolean wasActivated = mActivated; - result = handleTouchEventDimmed(event); - if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { - removeCallbacks(mTapTimeoutRunnable); - } - } else { - result = super.onTouchEvent(event); - } - return result; - } - /** * @return whether this view is interactive and can be double tapped */ @@ -313,28 +259,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - public void setRippleAllowed(boolean allowed) { + void setRippleAllowed(boolean allowed) { mBackgroundNormal.setPressedAllowed(allowed); } - private boolean handleTouchEventDimmed(MotionEvent event) { - if (mNeedsDimming && !mDimmed) { - // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple - super.onTouchEvent(event); - } - return mDoubleTapHelper.onTouchEvent(event, getActualHeight()); - } - - @Override - public boolean performClick() { - if (!mNeedsDimming || isTouchExplorationEnabled()) { - return super.performClick(); - } - return false; - } - - private void makeActive() { - mFalsingManager.onNotificationActive(); + void makeActive() { startActivateAnimation(false /* reverse */); mActivated = true; if (mOnActivatedListener != null) { @@ -388,19 +317,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.animate() .alpha(reverse ? 0f : 1f) .setInterpolator(alphaInterpolator) - .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - if (reverse) { - animatedFraction = 1.0f - animatedFraction; - } - setNormalBackgroundVisibilityAmount(animatedFraction); + .setUpdateListener(animation -> { + float animatedFraction = animation.getAnimatedFraction(); + if (reverse) { + animatedFraction = 1.0f - animatedFraction; } + setNormalBackgroundVisibilityAmount(animatedFraction); }) .setDuration(ACTIVATE_ANIMATION_LENGTH); } + @Override + public boolean performClick() { + if (!mNeedsDimming || (mAccessibilityManager != null + && mAccessibilityManager.isTouchExplorationEnabled())) { + return super.performClick(); + } + return false; + } + /** * Cancels the hotspot and makes the notification inactive. */ @@ -418,11 +353,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mOnActivatedListener != null) { mOnActivatedListener.onActivationReset(this); } - removeCallbacks(mTapTimeoutRunnable); } public void setDimmed(boolean dimmed, boolean fade) { mNeedsDimming = dimmed; + if (mOnDimmedListener != null) { + mOnDimmedListener.onSetDimmed(dimmed); + } dimmed &= isDimmable(); if (mDimmed != dimmed) { mDimmed = dimmed; @@ -439,13 +376,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return true; } + public boolean isDimmed() { + return mDimmed; + } + private void updateOutlineAlpha() { float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); setOutlineAlpha(alpha); } - public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { + private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; updateOutlineAlpha(); } @@ -473,14 +414,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView /** * Sets the tint color of the background */ - public void setTintColor(int color) { + protected void setTintColor(int color) { setTintColor(color, false); } /** * Sets the tint color of the background */ - public void setTintColor(int color, boolean animated) { + void setTintColor(int color, boolean animated) { if (color != mBgTint) { mBgTint = color; updateBackgroundTint(animated); @@ -562,13 +503,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mStartTint = mCurrentBackgroundTint; mTargetTint = color; mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, - animation.getAnimatedFraction()); - setBackgroundTintColor(newColor); - } + mBackgroundColorAnimator.addUpdateListener(animation -> { + int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, + animation.getAnimatedFraction()); + setBackgroundTintColor(newColor); }); mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); @@ -643,11 +581,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected void updateBackgroundAlpha(float transformationAmount) { - mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; + float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; if (mDimmedBackgroundFadeInAmount != -1) { - mBgAlpha *= mDimmedBackgroundFadeInAmount; + bgAlpha *= mDimmedBackgroundFadeInAmount; } - mBackgroundDimmed.setAlpha(mBgAlpha); + mBackgroundDimmed.setAlpha(bgAlpha); } protected void resetBackgroundAlpha() { @@ -671,7 +609,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); mBackgroundNormal.setAlpha(1f); - removeCallbacks(mTapTimeoutRunnable); // make in inactive to avoid it sticking around active makeInactive(false /* animate */); } @@ -783,14 +720,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.setInterpolator(Interpolators.LINEAR); mAppearAnimator.setDuration( (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); - mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mAppearAnimationFraction = (float) animation.getAnimatedValue(); - updateAppearAnimationAlpha(); - updateAppearRect(); - invalidate(); - } + mAppearAnimator.addUpdateListener(animation -> { + mAppearAnimationFraction = (float) animation.getAnimatedValue(); + updateAppearAnimationAlpha(); + updateAppearRect(); + invalidate(); }); if (animationListener != null) { mAppearAnimator.addListener(animationListener); @@ -921,7 +855,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView getCurrentBackgroundRadiusBottom()); } - protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + private void applyBackgroundRoundness(float topRadius, float bottomRadius) { mBackgroundDimmed.setRoundness(topRadius, bottomRadius); mBackgroundNormal.setRoundness(topRadius, bottomRadius); } @@ -963,7 +897,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - protected int getRippleColor() { + private int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else { @@ -1010,10 +944,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mOnActivatedListener = onActivatedListener; } - public boolean hasSameBgColor(ActivatableNotificationView otherView) { - return calculateBgColor() == otherView.calculateBgColor(); - } - @Override public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation) { @@ -1071,8 +1001,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return mRefocusOnDismiss || isAccessibilityFocused(); } + void setTouchHandler(Gefingerpoken touchHandler) { + mTouchHandler = touchHandler; + } + + void setOnDimmedListener(OnDimmedListener onDimmedListener) { + mOnDimmedListener = onDimmedListener; + } + + public void setAccessibilityManager(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); } + + interface OnDimmedListener { + void onSetDimmed(boolean dimmed); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java index 18993ffec357..8465658079f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.notification.row; +import android.view.MotionEvent; +import android.view.View; import android.view.accessibility.AccessibilityManager; +import com.android.systemui.Gefingerpoken; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.DoubleTapHelper; import javax.inject.Inject; @@ -27,21 +31,102 @@ import javax.inject.Inject; */ public class ActivatableNotificationViewController { private final ActivatableNotificationView mView; + private final ExpandableOutlineViewController mExpandableOutlineViewController; private final AccessibilityManager mAccessibilityManager; private final FalsingManager mFalsingManager; + private DoubleTapHelper mDoubleTapHelper; + private boolean mNeedsDimming; + + private TouchHandler mTouchHandler = new TouchHandler(); @Inject public ActivatableNotificationViewController(ActivatableNotificationView view, + ExpandableOutlineViewController expandableOutlineViewController, AccessibilityManager accessibilityManager, FalsingManager falsingManager) { mView = view; + mExpandableOutlineViewController = expandableOutlineViewController; mAccessibilityManager = accessibilityManager; mFalsingManager = falsingManager; + + mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() { + @Override + public void onActivated(ActivatableNotificationView view) { + mFalsingManager.onNotificationActive(); + } + + @Override + public void onActivationReset(ActivatableNotificationView view) { + } + }); } /** * Initialize the controller, setting up handlers and other behavior. */ public void init() { + mExpandableOutlineViewController.init(); + mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> { + if (active) { + mView.makeActive(); + mFalsingManager.onNotificationActive(); + } else { + mView.makeInactive(true /* animate */); + } + }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + mView.setOnTouchListener(mTouchHandler); + mView.setTouchHandler(mTouchHandler); + mView.setOnDimmedListener(dimmed -> { + mNeedsDimming = dimmed; + }); + mView.setAccessibilityManager(mAccessibilityManager); + } + + class TouchHandler implements Gefingerpoken, View.OnTouchListener { + private boolean mBlockNextTouch; + + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean result; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return true; + } + if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled() + && mView.isInteractive()) { + if (mNeedsDimming && !mView.isDimmed()) { + // We're actually dimmed, but our content isn't dimmable, + // let's ensure we have a ripple + return false; + } + result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight()); + } else { + return false; + } + return result; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN + && mView.disallowSingleClick(ev) + && !mAccessibilityManager.isTouchExplorationEnabled()) { + if (!mView.isActivated()) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + mView.makeInactive(true /* animate */); + return true; + } + } + return false; + } + /** + * Use {@link #onTouch(View, MotionEvent) instead}. + */ + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } } } 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 c34bba782ad5..551731824570 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 @@ -42,7 +42,6 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -72,11 +71,11 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; @@ -199,6 +198,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationEntry mEntry; private String mAppName; + private FalsingManager mFalsingManager; /** * Whether or not the notification is using the heads up view and should peek from the top. @@ -1087,20 +1087,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mEntry.setInitializationTime(SystemClock.elapsedRealtime()); - Dependency.get(PluginManager.class).addPluginListener(this, - NotificationMenuRowPlugin.class, false /* Allow multiple */); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); - } - - @Override public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; if (existed) { @@ -1439,7 +1425,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsBlockingHelperShowing && mNotificationTranslationFinished; } - public void setOnDismissRunnable(Runnable onDismissRunnable) { + void setOnDismissRunnable(Runnable onDismissRunnable) { mOnDismissRunnable = onDismissRunnable; } @@ -1597,7 +1583,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); - mMediaManager = Dependency.get(NotificationMediaManager.class); initDimens(); } @@ -1612,7 +1597,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationGroupManager groupManager, HeadsUpManager headsUpManager, RowContentBindStage rowContentBindStage, - OnExpandClickListener onExpandClickListener) { + OnExpandClickListener onExpandClickListener, + NotificationMediaManager notificationMediaManager, + OnAppOpsClickListener onAppOpsClickListener, + FalsingManager falsingManager, + StatusBarStateController statusBarStateController) { mAppName = appName; if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); @@ -1625,9 +1614,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mHeadsUpManager = headsUpManager; mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; - } - - public void setStatusBarStateController(StatusBarStateController statusBarStateController) { + mMediaManager = notificationMediaManager; + setAppOpsOnClickListener(onAppOpsClickListener); + mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; } @@ -1719,7 +1708,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mOnAppOpsClickListener; } - public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { + void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { mOnAppOpsClickListener = v -> { createMenu(); NotificationMenuRowPlugin provider = getProvider(); @@ -2188,7 +2177,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - getFalsingManager().setNotificationExpanded(); + mFalsingManager.setNotificationExpanded(); if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn()); 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 new file mode 100644 index 000000000000..39fab439ad07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row; + +import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.dagger.AppName; +import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable; +import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.time.SystemClock; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Controller for {@link ExpandableNotificationRow}. + */ +@NotificationRowScope +public class ExpandableNotificationRowController { + private final ExpandableNotificationRow mView; + private final ActivatableNotificationViewController mActivatableNotificationViewController; + private final NotificationMediaManager mMediaManager; + private final PluginManager mPluginManager; + private final SystemClock mClock; + private final String mAppName; + private final String mNotificationKey; + private final KeyguardBypassController mKeyguardBypassController; + private final NotificationGroupManager mNotificationGroupManager; + private final RowContentBindStage mRowContentBindStage; + private final NotificationLogger mNotificationLogger; + private final HeadsUpManager mHeadsUpManager; + private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener; + private final StatusBarStateController mStatusBarStateController; + private final NotificationRowContentBinder.InflationCallback mInflationCallback; + + private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = + this::logNotificationExpansion; + private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; + private final NotificationGutsManager mNotificationGutsManager; + private Runnable mOnDismissRunnable; + private final FalsingManager mFalsingManager; + private final boolean mAllowLongPress; + + @Inject + public ExpandableNotificationRowController(ExpandableNotificationRow view, + ActivatableNotificationViewController activatableNotificationViewController, + NotificationMediaManager mediaManager, PluginManager pluginManager, + SystemClock clock, @AppName String appName, @NotificationKey String notificationKey, + KeyguardBypassController keyguardBypassController, + NotificationGroupManager notificationGroupManager, + RowContentBindStage rowContentBindStage, + NotificationLogger notificationLogger, HeadsUpManager headsUpManager, + ExpandableNotificationRow.OnExpandClickListener onExpandClickListener, + StatusBarStateController statusBarStateController, + NotificationRowContentBinder.InflationCallback inflationCallback, + NotificationGutsManager notificationGutsManager, + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, + @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) { + mView = view; + mActivatableNotificationViewController = activatableNotificationViewController; + mMediaManager = mediaManager; + mPluginManager = pluginManager; + mClock = clock; + mAppName = appName; + mNotificationKey = notificationKey; + mKeyguardBypassController = keyguardBypassController; + mNotificationGroupManager = notificationGroupManager; + mRowContentBindStage = rowContentBindStage; + mNotificationLogger = notificationLogger; + mHeadsUpManager = headsUpManager; + mOnExpandClickListener = onExpandClickListener; + mStatusBarStateController = statusBarStateController; + mInflationCallback = inflationCallback; + mNotificationGutsManager = notificationGutsManager; + mOnDismissRunnable = onDismissRunnable; + mOnAppOpsClickListener = mNotificationGutsManager::openGuts; + mAllowLongPress = allowLongPress; + mFalsingManager = falsingManager; + } + + /** + * Initialize the controller. + */ + public void init() { + mActivatableNotificationViewController.init(); + mView.initialize( + mAppName, + mNotificationKey, + mExpansionLogger, + mKeyguardBypassController, + mNotificationGroupManager, + mHeadsUpManager, + mRowContentBindStage, + mOnExpandClickListener, + mMediaManager, + mOnAppOpsClickListener, + mFalsingManager, + mStatusBarStateController + ); + mView.setOnDismissRunnable(mOnDismissRunnable); + mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (mAllowLongPress) { + mView.setLongPressListener(mNotificationGutsManager::openGuts); + } + if (ENABLE_REMOTE_INPUT) { + mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + mPluginManager.addPluginListener(mView, + NotificationMenuRowPlugin.class, false /* Allow multiple */); + } + + @Override + public void onViewDetachedFromWindow(View v) { + mPluginManager.removePluginListener(mView); + } + }); + } + + private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mNotificationLogger.onExpansionChanged(key, userAction, expanded); + } + + /** */ + public void setOnDismissRunnable(Runnable onDismissRunnable) { + mOnDismissRunnable = onDismissRunnable; + mView.setOnDismissRunnable(onDismissRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java new file mode 100644 index 000000000000..75c9d1e6f2fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableOutlineView}. + */ +public class ExpandableOutlineViewController { + private final ExpandableOutlineView mView; + private final ExpandableViewController mExpandableViewController; + + @Inject + public ExpandableOutlineViewController(ExpandableOutlineView view, + ExpandableViewController expandableViewController) { + mView = view; + mExpandableViewController = expandableViewController; + } + + /** + * Initialize the controller. + */ + public void init() { + mExpandableViewController.init(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java new file mode 100644 index 000000000000..e14ca8c4e590 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableView}. + */ +public class ExpandableViewController { + private final ExpandableView mView; + + @Inject + public ExpandableViewController(ExpandableView view) { + mView = view; + } + + /** + * Initialize the controller. + */ + public void init() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index bb0681ce8a44..a0af4ace05b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -23,14 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE; - import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; @@ -69,11 +61,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.phone.ShadeController; import java.lang.annotation.Retention; import java.util.Arrays; @@ -92,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements ShortcutManager mShortcutManager; private PackageManager mPm; private VisualStabilityManager mVisualStabilityManager; + private ShadeController mShadeController; private String mPackageName; private String mAppName; @@ -103,24 +98,30 @@ public class NotificationConversationInfo extends LinearLayout implements private NotificationEntry mEntry; private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; - private int mStartingChannelImportance; private boolean mStartedAsBubble; private boolean mIsBubbleable; - // TODO: remove when launcher api works - @VisibleForTesting - boolean mShowHomeScreen = false; - private @UpdateChannelRunnable.Action int mSelectedAction = -1; + private @Action int mSelectedAction = -1; private OnSnoozeClickListener mOnSnoozeClickListener; private OnSettingsClickListener mOnSettingsClickListener; - private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; private BubbleController mBubbleController; @VisibleForTesting boolean mSkipPost = false; + @Retention(SOURCE) + @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE}) + private @interface Action {} + static final int ACTION_BUBBLE = 0; + static final int ACTION_HOME = 1; + static final int ACTION_FAVORITE = 2; + static final int ACTION_SNOOZE = 3; + static final int ACTION_MUTE = 4; + static final int ACTION_SETTINGS = 5; + static final int ACTION_UNBUBBLE = 6; + private OnClickListener mOnBubbleClick = v -> { mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { @@ -136,12 +137,14 @@ public class NotificationConversationInfo extends LinearLayout implements private OnClickListener mOnHomeClick = v -> { mSelectedAction = ACTION_HOME; mShortcutManager.requestPinShortcut(mShortcutInfo, null); + mShadeController.animateCollapsePanels(); closeControls(v, true); }; private OnClickListener mOnFavoriteClick = v -> { mSelectedAction = ACTION_FAVORITE; - closeControls(v, true); + updateChannel(); + }; private OnClickListener mOnSnoozeClick = v -> { @@ -151,13 +154,8 @@ public class NotificationConversationInfo extends LinearLayout implements }; private OnClickListener mOnMuteClick = v -> { - mSelectedAction = ACTION_MUTE; - closeControls(v, true); - }; - - private OnClickListener mOnDemoteClick = v -> { - mSelectedAction = ACTION_DEMOTE; - closeControls(v, true); + mSelectedAction = ACTION_MUTE; + updateChannel(); }; public NotificationConversationInfo(Context context, AttributeSet attrs) { @@ -197,15 +195,14 @@ public class NotificationConversationInfo extends LinearLayout implements mEntry = entry; mSbn = entry.getSbn(); mPm = pm; - mAppSettingsClickListener = onAppSettingsClick; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; mNotificationChannel = notificationChannel; - mStartingChannelImportance = mNotificationChannel.getImportance(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; + mShadeController = Dependency.get(ShadeController.class); mShortcutManager = shortcutManager; mLauncherApps = launcherApps; @@ -251,9 +248,6 @@ public class NotificationConversationInfo extends LinearLayout implements mNotificationChannel = mINotificationManager.getConversationNotificationChannel( mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, mNotificationChannel.getId(), false, mConversationId); - - // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a - // time } catch (RemoteException e) { Slog.e(TAG, "Could not create conversation channel", e); } @@ -274,40 +268,24 @@ public class NotificationConversationInfo extends LinearLayout implements Button home = findViewById(R.id.home); home.setOnClickListener(mOnHomeClick); - home.setVisibility(mShowHomeScreen && mShortcutInfo != null + home.setVisibility(mShortcutInfo != null && mShortcutManager.isRequestPinShortcutSupported() ? VISIBLE : GONE); - Button favorite = findViewById(R.id.fave); + View favorite = findViewById(R.id.fave); favorite.setOnClickListener(mOnFavoriteClick); - if (mNotificationChannel.isImportantConversation()) { - favorite.setText(R.string.notification_conversation_unfavorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star), null, null, null); - } else { - favorite.setText(R.string.notification_conversation_favorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star_border), null, null, null); - } Button snooze = findViewById(R.id.snooze); snooze.setOnClickListener(mOnSnoozeClick); - Button mute = findViewById(R.id.mute); + View mute = findViewById(R.id.mute); mute.setOnClickListener(mOnMuteClick); - if (mStartingChannelImportance >= IMPORTANCE_DEFAULT - || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) { - mute.setText(R.string.notification_conversation_mute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null); - } else { - mute.setText(R.string.notification_conversation_unmute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null); - } - ImageButton demote = findViewById(R.id.demote); - demote.setOnClickListener(mOnDemoteClick); + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + + updateToggleActions(); } private void bindHeader() { @@ -315,26 +293,6 @@ public class NotificationConversationInfo extends LinearLayout implements // Delegate bindDelegate(); - - // Set up app settings link (i.e. Customize) - View settingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, - mNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (settingsIntent != null - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - settingsLinkView.setVisibility(VISIBLE); - settingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - settingsLinkView.setVisibility(View.GONE); - } - - // System Settings button. - final View settingsButton = findViewById(R.id.info); - settingsButton.setOnClickListener(getSettingsOnClickListener()); - settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); } private OnClickListener getSettingsOnClickListener() { @@ -424,15 +382,12 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindDelegate() { TextView delegateView = findViewById(R.id.delegate_name); - TextView dividerView = findViewById(R.id.pkg_divider); if (!TextUtils.equals(mPackageName, mDelegatePkg)) { // this notification was posted by a delegate! delegateView.setVisibility(View.VISIBLE); - dividerView.setVisibility(View.VISIBLE); } else { delegateView.setVisibility(View.GONE); - dividerView.setVisibility(View.GONE); } } @@ -492,26 +447,37 @@ public class NotificationConversationInfo extends LinearLayout implements } } - private Intent getAppSettingsIntent(PackageManager pm, String packageName, - NotificationChannel channel, int id, String tag) { - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) - .setPackage(packageName); - final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ); - if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { - return null; + private void updateToggleActions() { + ImageButton favorite = findViewById(R.id.fave); + if (mNotificationChannel.isImportantConversation()) { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_favorite)); + favorite.setImageResource(R.drawable.ic_important); + } else { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_unfavorite)); + favorite.setImageResource(R.drawable.ic_important_outline); } - final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; - intent.setClassName(activityInfo.packageName, activityInfo.name); - if (channel != null) { - intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + + ImageButton mute = findViewById(R.id.mute); + if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT + || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_unmute)); + mute.setImageResource(R.drawable.ic_notifications_alert); + } else { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_mute)); + mute.setImageResource(R.drawable.ic_notifications_silence); } - intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); - intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); - return intent; + } + + private void updateChannel() { + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateChannelRunnable(mINotificationManager, mPackageName, + mAppUid, mSelectedAction, mNotificationChannel)); + mVisualStabilityManager.temporarilyAllowReordering(); } /** @@ -556,11 +522,7 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public boolean handleCloseControls(boolean save, boolean force) { if (save && mSelectedAction > -1) { - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post( - new UpdateChannelRunnable(mINotificationManager, mPackageName, - mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); + updateChannel(); } return false; } @@ -575,19 +537,7 @@ public class NotificationConversationInfo extends LinearLayout implements return false; } - static class UpdateChannelRunnable implements Runnable { - - @Retention(SOURCE) - @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE, - ACTION_DEMOTE}) - private @interface Action {} - static final int ACTION_BUBBLE = 0; - static final int ACTION_HOME = 1; - static final int ACTION_FAVORITE = 2; - static final int ACTION_SNOOZE = 3; - static final int ACTION_MUTE = 4; - static final int ACTION_DEMOTE = 5; - static final int ACTION_UNBUBBLE = 6; + class UpdateChannelRunnable implements Runnable { private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -633,10 +583,6 @@ public class NotificationConversationInfo extends LinearLayout implements mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT)); } break; - case ACTION_DEMOTE: - mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted()); - break; - } if (channelSettingChanged) { @@ -646,13 +592,7 @@ public class NotificationConversationInfo extends LinearLayout implements } catch (RemoteException e) { Log.e(TAG, "Unable to update notification channel", e); } + ThreadUtils.postOnMainThread(() -> updateToggleActions()); } } - - @Retention(SOURCE) - @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE}) - private @interface AlertingBehavior {} - private static final int BEHAVIOR_ALERTING = 0; - private static final int BEHAVIOR_SILENT = 1; - private static final int BEHAVIOR_BUBBLE = 2; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6789c814dcee..352abcfc9214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -384,6 +385,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx guts.resetFalsingCheck(); mOnSettingsClickListener.onSettingsClick(sbn.getKey()); startAppNotificationSettingsActivity(packageName, appUid, channel, row); + notificationInfoView.closeControls(v, false); }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index c173b4dbaebe..6feffe654630 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -26,7 +26,6 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import javax.inject.Inject; @@ -37,7 +36,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; - private final NotificationRowComponent.Builder mNotificationRowComponentBuilder; private RowInflationFinishedListener mListener; private NotificationEntry mEntry; @@ -45,10 +43,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; @Inject - public RowInflaterTask( - NotificationRowComponent.Builder notificationRowComponentBuilder) { - super(); - mNotificationRowComponentBuilder = notificationRowComponentBuilder; + public RowInflaterTask() { } /** @@ -75,12 +70,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { try { - // Setup the controller for the view. - NotificationRowComponent component = mNotificationRowComponentBuilder - .activatableNotificationView((ActivatableNotificationView) view) - .build(); - component.getActivatableNotificationViewController().init(); - mEntry.onInflationTaskFinished(); mListener.onInflationFinished((ExpandableNotificationRow) view); } catch (Throwable t) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java new file mode 100644 index 000000000000..a3dfa608c709 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableOutlineView; +import com.android.systemui.statusbar.notification.row.ExpandableView; + +import dagger.Binds; +import dagger.Module; + +/** + * Module for NotificationRowComponent. + */ +@Module +public interface ActivatableNotificationViewModule { + /** ExpandableView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableView bindExpandableView(ActivatableNotificationView view); + /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java new file mode 100644 index 000000000000..1dbca0cd5527 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface AppName { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java new file mode 100644 index 000000000000..433114224289 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DismissRunnable { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java new file mode 100644 index 000000000000..6d6d3e446f53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; +import com.android.systemui.statusbar.phone.StatusBar; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Dagger Component for a {@link ExpandableNotificationRow}. + */ +@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class, + ActivatableNotificationViewModule.class}) +@NotificationRowScope +public interface ExpandableNotificationRowComponent { + + /** + * Builder for {@link NotificationRowComponent}. + */ + @Subcomponent.Builder + interface Builder { + // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it + // should be possible to pull one from the other, but they aren't connected at the time + // this component is constructed. + @BindsInstance + Builder expandableNotificationRow(ExpandableNotificationRow view); + @BindsInstance + Builder notificationEntry(NotificationEntry entry); + @BindsInstance + Builder onDismissRunnable(@DismissRunnable Runnable runnable); + @BindsInstance + Builder rowContentBindStage(RowContentBindStage rowContentBindStage); + @BindsInstance + Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback); + @BindsInstance + Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter); + ExpandableNotificationRowComponent build(); + } + + /** + * Creates a ExpandableNotificationRowController. + */ + @NotificationRowScope + ExpandableNotificationRowController getExpandableNotificationRowController(); + + /** + * Dagger Module that extracts interesting properties from an ExpandableNotificationRow. + */ + @Module + abstract class ExpandableNotificationRowModule { + + /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */ + @Binds + abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view); + + @Provides + static StatusBarNotification provideStatusBarNotification( + NotificationEntry notificationEntry) { + return notificationEntry.getSbn(); + } + + @Provides + @NotificationKey + static String provideNotificationKey(StatusBarNotification statusBarNotification) { + return statusBarNotification.getKey(); + } + + @Provides + @AppName + static String provideAppName(Context context, StatusBarNotification statusBarNotification) { + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + PackageManager pmUser = StatusBar.getPackageManagerForUser( + context, statusBarNotification.getUser().getIdentifier()); + final String pkg = statusBarNotification.getPackageName(); + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + return String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + + return pkg; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java new file mode 100644 index 000000000000..b1fff383cd5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationKey { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java index f16ea7ae23e9..1f535c5e3f56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification.row.dagger; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Scope; - import dagger.BindsInstance; import dagger.Subcomponent; /** * Dagger subcomponent for Notification related views. */ -@Subcomponent(modules = {}) -@NotificationRowComponent.NotificationRowScope +@Subcomponent(modules = {ActivatableNotificationViewModule.class}) +@NotificationRowScope public interface NotificationRowComponent { /** * Builder for {@link NotificationRowComponent}. @@ -46,14 +39,6 @@ public interface NotificationRowComponent { } /** - * Scope annotation for singleton items within the StatusBarComponent. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface NotificationRowScope {} - - /** * Creates a ActivatableNotificationViewController. */ @NotificationRowScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java new file mode 100644 index 000000000000..4555b839a3f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +/** + * Scope annotation for singleton items within the StatusBarComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface NotificationRowScope {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 44a320419309..745843deeddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -375,6 +375,7 @@ public class EdgeBackGestureHandler implements DisplayListener, mDownPoint.set(ev.getX(), ev.getY()); mThresholdCrossed = false; } + } else if (mAllowGesture) { if (!mThresholdCrossed) { if (action == MotionEvent.ACTION_POINTER_DOWN) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 72dfa18ca991..d3e44eaf17b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -492,7 +492,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL try { result = ActivityTaskManager.getService().startActivityAsUser( null, getContext().getBasePackageName(), - getContext().getFeatureId(), intent, + intent, intent.resolveTypeIfNeeded(getContext().getContentResolver()), null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(), UserHandle.CURRENT.getIdentifier()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 9e64748f2e65..3f5215e1a639 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -269,6 +269,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { + mForceNavBarHandleOpaque = properties.getBoolean( + NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); + } + } + }; + @Inject public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, @@ -298,21 +309,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDivider = divider; mRecentsOptional = recentsOptional; mHandler = mainHandler; - - mForceNavBarHandleOpaque = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - NAV_BAR_HANDLE_FORCE_OPAQUE, - /* defaultValue = */ true); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { - mForceNavBarHandleOpaque = properties.getBoolean( - NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); - } - } - }); } // ----- Fragment Lifecycle Callbacks ----- @@ -338,6 +334,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback // Respect the latest disabled-flags. mCommandQueue.recomputeDisableFlags(mDisplayId, false); + + mForceNavBarHandleOpaque = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + NAV_BAR_HANDLE_FORCE_OPAQUE, + /* defaultValue = */ true); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); } @Override @@ -346,6 +349,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavigationModeController.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mAssistContentObserver); + + DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); } @Override 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 4f01cc191acd..c68d9942419b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2567,7 +2567,7 @@ public class StatusBar extends SystemUI implements DemoMode, } try { result = ActivityTaskManager.getService().startActivityAsUser( - null, mContext.getBasePackageName(), mContext.getFeatureId(), + null, mContext.getBasePackageName(), intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index e3bcdc8b0b60..751217f03fa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -225,7 +226,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } reset(persistenceWrapper) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -293,11 +294,13 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false val control = builderFromInfo(TEST_CONTROL_INFO).build() - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(ControlStatus(control, false), controlStatus) + + assertTrue(favorites.isEmpty()) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -315,14 +318,17 @@ class ControlsControllerImplTest : SysuiTestCase() { val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build() controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(2, it.size) - val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID } + assertEquals(2, controls.size) + val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID } assertEquals(ControlStatus(control, true), controlStatus) - val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 } + val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 } assertEquals(ControlStatus(control2, false), controlStatus2) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -338,13 +344,16 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId) assertTrue(controlStatus.favorite) assertTrue(controlStatus.removed) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -361,7 +370,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -483,4 +492,81 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) } + + @Test + fun testGetFavoritesForComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testGetFavoritesForComponent_otherComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty()) + } + + @Test + fun testGetFavoritesForComponent_multipleInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(controlInfo, true) + + assertEquals(listOf(TEST_CONTROL_INFO, controlInfo), + controller.getFavoritesForComponent(TEST_COMPONENT)) + + controller.clearFavorites() + + controller.changeFavoriteStatus(controlInfo, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(listOf(controlInfo, TEST_CONTROL_INFO), + controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_noFavorites() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, + listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_oldFavoritesRemoved() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + assertNotEquals(TEST_CONTROL_INFO, controlInfo) + + controller.changeFavoriteStatus(controlInfo, true) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_favoritesInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT)) + + val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt new file mode 100644 index 000000000000..9ffc29e0eb7e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2020 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.management + +import android.app.PendingIntent +import android.service.controls.Control +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +open class FavoriteModelTest : SysuiTestCase() { + + @Mock + lateinit var pendingIntent: PendingIntent + @Mock + lateinit var allAdapter: ControlAdapter + @Mock + lateinit var favoritesAdapter: ControlAdapter + + val idPrefix = "controlId" + val favoritesIndices = listOf(7, 3, 1, 9) + val favoritesList = favoritesIndices.map { "controlId$it" } + lateinit var controls: List<ControlStatus> + + lateinit var model: FavoriteModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // controlId0 --> zone = 0 + // controlId1 --> zone = 1, favorite + // controlId2 --> zone = 2 + // controlId3 --> zone = 0, favorite + // controlId4 --> zone = 1 + // controlId5 --> zone = 2 + // controlId6 --> zone = 0 + // controlId7 --> zone = 1, favorite + // controlId8 --> zone = 2 + // controlId9 --> zone = 0, favorite + controls = (0..9).map { + ControlStatus( + Control.StatelessBuilder("$idPrefix$it", pendingIntent) + .setZone((it % 3).toString()) + .build(), + it in favoritesIndices + ) + } + + model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter) + } +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FavoriteModelNonParametrizedTests : FavoriteModelTest() { + @Test + fun testAll() { + // Zones are sorted alphabetically + val expected = listOf( + ZoneNameWrapper("0"), + ControlWrapper(controls[0]), + ControlWrapper(controls[3]), + ControlWrapper(controls[6]), + ControlWrapper(controls[9]), + ZoneNameWrapper("1"), + ControlWrapper(controls[1]), + ControlWrapper(controls[4]), + ControlWrapper(controls[7]), + ZoneNameWrapper("2"), + ControlWrapper(controls[2]), + ControlWrapper(controls[5]), + ControlWrapper(controls[8]) + ) + assertEquals(expected, model.all) + } + + @Test + fun testFavoritesInOrder() { + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + } + + @Test + fun testChangeFavoriteStatus_addFavorite() { + val controlToAdd = 6 + model.changeFavoriteStatus("$idPrefix$controlToAdd", true) + + val pair = model.all.findControl(controlToAdd) + pair?.let { + assertTrue(it.second.favorite) + assertEquals(it.second, model.favorites.last().controlStatus) + verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_removeFavorite() { + val controlToRemove = 3 + model.changeFavoriteStatus("$idPrefix$controlToRemove", false) + + val pair = model.all.findControl(controlToRemove) + pair?.let { + assertFalse(it.second.favorite) + assertTrue(model.favorites.none { + it.controlStatus.control.controlId == "$idPrefix$controlToRemove" + }) + verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove)) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_sameStatus() { + model.changeFavoriteStatus("${idPrefix}7", true) + model.changeFavoriteStatus("${idPrefix}6", false) + + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } + + private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? { + val index = indexOfFirst { + it is ControlWrapper && + it.controlStatus.control.controlId == "$idPrefix$controlIndex" + } + return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus + } +} + +@SmallTest +@RunWith(Parameterized::class) +class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0} -> {1}") + fun data(): Collection<Array<Int>> { + return (0..3).flatMap { from -> + (0..3).map { to -> + arrayOf(from, to) + } + }.filterNot { it[0] == it[1] } + } + } + + @Test + fun testMoveItem() { + val originalFavorites = model.favorites.toList() + val originalFavoritesIds = + model.favorites.map { it.controlStatus.control.controlId }.toSet() + model.onMoveItem(from, to) + assertEquals(originalFavorites[from], model.favorites[to]) + // Check that we still have the same favorites + assertEquals(originalFavoritesIds, + model.favorites.map { it.controlStatus.control.controlId }.toSet()) + + verify(favoritesAdapter).notifyItemMoved(from, to) + + verifyNoMoreInteractions(allAdapter, favoritesAdapter) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 399f723f4d62..9117ea8f9fc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -173,6 +173,21 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception { + mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, + null /* sensor */, mBroadcastDispatcher, mDozeHost, null /* handler */, + DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY, + true /* debuggable */); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE); + reset(mDozeHost); + + mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE); + + verify(mDozeHost).setAodDimmingScrim(eq(0f)); + } + + @Test public void testDockedAod_usesLightSensor() { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD_DOCKED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java index 3439fe53c48f..d26ae6aaad79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java @@ -119,7 +119,6 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { doReturn(code).when(mIActivityTaskManager).startActivityAsUser( eq((IApplicationThread) null), eq((String) null), - eq((String) null), any(Intent.class), eq((String) null), eq((IBinder) null), @@ -135,7 +134,6 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { verify(mIActivityTaskManager).startActivityAsUser( eq((IApplicationThread) null), eq((String) null), - eq((String) null), any(Intent.class), eq((String) null), eq((IBinder) null), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 07f6936ece07..5a0a495e1f85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -58,10 +58,13 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.NotificationVisibility; +import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -83,12 +86,13 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -96,6 +100,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -106,6 +111,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -141,8 +147,13 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationEntryManagerLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @Mock private LeakDetector mLeakDetector; - @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; - @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder; + @Mock private NotificationMediaManager mNotificationMediaManager; + @Mock private ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; + @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent; + @Mock private FalsingManager mFalsingManager; + @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private StatusBarStateController mStatusBarStateController; private int mId; private NotificationEntry mEntry; @@ -191,7 +202,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(SmartReplyController.class); - mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); @@ -207,28 +217,23 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - when(mNotificationRowComponentBuilder.activatableNotificationView(any())) - .thenReturn(mNotificationRowComponentBuilder); - when(mNotificationRowComponentBuilder.build()).thenReturn( - () -> mActivatableNotificationViewController); - RowContentBindStage bindStage = mock(RowContentBindStage.class); when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); - NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, + new NotificationMessagingUtil(mContext), mRemoteInputManager, mLockscreenUserManager, mock(NotifBindPipeline.class), bindStage, true, /* allowLongPress */ - mock(KeyguardBypassController.class), - mock(StatusBarStateController.class), + mKeyguardBypassController, + mStatusBarStateController, mGroupManager, mGutsManager, mNotificationInterruptionStateProvider, - () -> new RowInflaterTask(mNotificationRowComponentBuilder), - mock(NotificationLogger.class)); + RowInflaterTask::new, + mExpandableNotificationRowComponentBuilder); when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); @@ -236,7 +241,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mLogger, mGroupManager, new NotificationRankingManager( - () -> mock(NotificationMediaManager.class), + () -> mNotificationMediaManager, mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), @@ -255,13 +260,55 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor); - notificationRowBinder.setUpWithPresenter( - mPresenter, mListContainer, mHeadsUpManager, mBindCallback); + notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback); notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); setUserSentiment( mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); + + ArgumentCaptor<ExpandableNotificationRow> viewCaptor = + ArgumentCaptor.forClass(ExpandableNotificationRow.class); + when(mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(viewCaptor.capture())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .notificationEntry(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onDismissRunnable(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .inflationCallback(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onExpandClickListener(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + + when(mExpandableNotificationRowComponentBuilder.build()) + .thenReturn(mExpandableNotificationRowComponent); + when(mExpandableNotificationRowComponent.getExpandableNotificationRowController()) + .thenAnswer((Answer<ExpandableNotificationRowController>) invocation -> + new ExpandableNotificationRowController( + viewCaptor.getValue(), + mock(ActivatableNotificationViewController.class), + mNotificationMediaManager, + mock(PluginManager.class), + new FakeSystemClock(), + "FOOBAR", "FOOBAR", + mKeyguardBypassController, + mGroupManager, + bindStage, + mock(NotificationLogger.class), + mHeadsUpManager, + mPresenter, + mStatusBarStateController, + mEntryManager, + mGutsManager, + true, + null, + mFalsingManager + )); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index d8cf6ed9a47b..a8918103c4a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -194,9 +194,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testClickSound() throws Exception { assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); - StatusBarStateController mock = mock(StatusBarStateController.class); + StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); when(mock.isDozing()).thenReturn(true); - mGroupRow.setStatusBarStateController(mock); mGroupRow.setSecureStateProvider(()-> false); assertFalse("Shouldn't play sounds when dark and trusted.", mGroupRow.isSoundEffectsEnabled()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index f080d67bfffb..e8de10f7392b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.phone.ShadeController; import org.junit.Before; import org.junit.Rule; @@ -131,6 +132,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private ShortcutManager mShortcutManager; @Mock private NotificationGuts mNotificationGuts; + @Mock + private ShadeController mShadeController; @Before public void setUp() throws Exception { @@ -139,12 +142,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(BubbleController.class, mBubbleController); + mDependency.injectTestDependency(ShadeController.class, mShadeController); // Inflate the layout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate( R.layout.notification_conversation_info, null); - mNotificationInfo.mShowHomeScreen = true; mNotificationInfo.setGutsParent(mNotificationGuts); doAnswer((Answer<Object>) invocation -> { mNotificationInfo.handleCloseControls(true, false); @@ -173,7 +176,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { when(mShortcutInfo.getShortLabel()).thenReturn("Convo name"); List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); - mImage = mContext.getDrawable(R.drawable.ic_star); + mImage = mContext.getDrawable(R.drawable.ic_remove); when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo), anyInt())).thenReturn(mImage); @@ -333,8 +336,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(GONE, dividerView.getVisibility()); } @Test @@ -364,8 +365,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(VISIBLE, dividerView.getVisibility()); } @Test @@ -502,6 +501,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); + verify(mShadeController).animateCollapsePanels(); } @Test @@ -644,9 +644,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_favorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -677,9 +677,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_favorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -692,34 +692,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test - public void testDemote() throws Exception { - mNotificationInfo.bindNotification( - mShortcutManager, - mLauncherApps, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - true); - - - ImageButton demote = mNotificationInfo.findViewById(R.id.demote); - demote.performClick(); - mTestableLooper.processAllMessages(); - - ArgumentCaptor<NotificationChannel> captor = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().isDemoted()); - } - - @Test public void testMute_mute() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mConversationChannel.setImportance(IMPORTANCE_DEFAULT); @@ -738,9 +710,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_mute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_unmute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); @@ -774,9 +746,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_unmute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_mute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); 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 9b2e0c375e87..35b55087873b 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 @@ -45,6 +45,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -91,6 +92,7 @@ public class NotificationTestHelper { private final NotifBindPipeline mBindPipeline; private final NotificationEntryListener mBindPipelineEntryListener; private final RowContentBindStage mBindStage; + private StatusBarStateController mStatusBarStateController; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -98,9 +100,9 @@ public class NotificationTestHelper { dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(SmartReplyController.class); - StatusBarStateController stateController = mock(StatusBarStateController.class); - mGroupManager = new NotificationGroupManager(stateController); - mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController, + mStatusBarStateController = mock(StatusBarStateController.class); + mGroupManager = new NotificationGroupManager(mStatusBarStateController); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController, mock(KeyguardBypassController.class)); mHeadsUpManager.setUp(null, mGroupManager, null, null); mGroupManager.setHeadsUpManager(mHeadsUpManager); @@ -321,6 +323,10 @@ public class NotificationTestHelper { return notificationBuilder.build(); } + public StatusBarStateController getStatusBarStateController() { + return mStatusBarStateController; + } + private ExpandableNotificationRow generateRow( Notification notification, String pkg, @@ -382,7 +388,11 @@ public class NotificationTestHelper { mGroupManager, mHeadsUpManager, mBindStage, - mock(OnExpandClickListener.class)); + mock(OnExpandClickListener.class), + mock(NotificationMediaManager.class), + mock(ExpandableNotificationRow.OnAppOpsClickListener.class), + mock(FalsingManager.class), + mStatusBarStateController); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); inflateAndWait(entry, mBindStage); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index e84f14a6a2c1..2d1bc7890aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -30,7 +30,6 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -59,8 +58,6 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; @Mock - private StatusBarStateController mStatusBarStateController; - @Mock private KeyguardBypassController mBypassController; @Before @@ -150,13 +147,12 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency) - .createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); - when(mStatusBarStateController.isDozing()).thenReturn(true); - row.setStatusBarStateController(mStatusBarStateController); + when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); row.setHeadsUp(true); mRoundnessManager.onHeadsUpStateChanged(entry, true); Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 285f4ab6cdf5..4efe93439b42 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -28,6 +28,7 @@ java_defaults { "netd_aidl_interface-unstable-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", + "android.hardware.tetheroffload.config-V1.0-java", "android.hardware.tetheroffload.control-V1.0-java", "net-utils-framework-common", ], @@ -48,25 +49,26 @@ android_library { // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK). cc_library { name: "libtetherutilsjni", + sdk_version: "current", srcs: [ "jni/android_net_util_TetheringUtils.cpp", ], shared_libs: [ - "libcgrouprc", - "libnativehelper_compat_libc++", - "libvndksupport", - ], - static_libs: [ - "android.hardware.tetheroffload.config@1.0", "liblog", - "libbase", - "libcutils", - "libhidlbase", - "libjsoncpp", - "libprocessgroup", - "libutils", + "libnativehelper_compat_libc++", ], + // We cannot use plain "libc++" here to link libc++ dynamically because it results in: + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found + // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't + // build because soong complains of: + // module Tethering missing dependencies: libc++_shared + // + // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries + // we depend on do not dynamically link libc++. This is currently the case, because liblog is + // C-only and libnativehelper_compat_libc also uses stl: "c++_static". + stl: "c++_static", + cflags: [ "-Wall", "-Werror", @@ -85,9 +87,8 @@ java_defaults { // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies // explicitly. jni_libs: [ - "libcgrouprc", + "liblog", "libnativehelper_compat_libc++", - "libvndksupport", "libtetherutilsjni", ], resource_dirs: [ diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp index 1cf8f988432c..549344064405 100644 --- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp +++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp @@ -16,123 +16,18 @@ #include <errno.h> #include <error.h> -#include <hidl/HidlSupport.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <linux/netfilter/nfnetlink.h> -#include <linux/netlink.h> #include <net/if.h> #include <netinet/icmp6.h> #include <sys/socket.h> -#include <android-base/unique_fd.h> -#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h> #define LOG_TAG "TetheringUtils" -#include <utils/Log.h> +#include <android/log.h> namespace android { -using hardware::hidl_handle; -using hardware::hidl_string; -using hardware::tetheroffload::config::V1_0::IOffloadConfig; - -namespace { - -inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) { - return reinterpret_cast<const sockaddr *>(nladdr); -} - -int conntrackSocket(unsigned groups) { - base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)); - if (s.get() < 0) return -errno; - - const struct sockaddr_nl bind_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) { - return -errno; - } - - const struct sockaddr_nl kernel_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) { - return -errno; - } - - return s.release(); -} - -// Return a hidl_handle that owns the file descriptor owned by fd, and will -// auto-close it (otherwise there would be double-close problems). -// -// Rely upon the compiler to eliminate the constexprs used for clarity. -hidl_handle handleFromFileDescriptor(base::unique_fd fd) { - hidl_handle h; - - static constexpr int kNumFds = 1; - static constexpr int kNumInts = 0; - native_handle_t *nh = native_handle_create(kNumFds, kNumInts); - nh->data[0] = fd.release(); - - static constexpr bool kTakeOwnership = true; - h.setTo(nh, kTakeOwnership); - - return h; -} - -} // namespace - -static jboolean android_net_util_configOffload( - JNIEnv* /* env */) { - sp<IOffloadConfig> configInterface = IOffloadConfig::getService(); - if (configInterface.get() == nullptr) { - ALOGD("Could not find IOffloadConfig service."); - return false; - } - - // Per the IConfigOffload definition: - // - // fd1 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). - // - // fd2 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - base::unique_fd - fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)), - fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY)); - if (fd1.get() < 0 || fd2.get() < 0) { - ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno)); - return false; - } - - hidl_handle h1(handleFromFileDescriptor(std::move(fd1))), - h2(handleFromFileDescriptor(std::move(fd2))); - - bool rval(false); - hidl_string msg; - const auto status = configInterface->setHandles(h1, h2, - [&rval, &msg](bool success, const hidl_string& errMsg) { - rval = success; - msg = errMsg; - }); - if (!status.isOk() || !rval) { - ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", - status.description().c_str(), msg.c_str()); - // If status is somehow not ok, make sure rval captures this too. - rval = false; - } - - return rval; -} - static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -229,7 +124,6 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "configOffload", "()Z", (void*) android_net_util_configOffload }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket }, }; @@ -242,7 +136,7 @@ int register_android_net_util_TetheringUtils(JNIEnv* env) { extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - ALOGE("ERROR: GetEnv failed"); + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed"); return JNI_ERR; } diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index fa543bdce735..5a6d5c1cbfb0 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -24,14 +24,6 @@ import java.net.SocketException; * {@hide} */ public class TetheringUtils { - - /** - * Offload management process need to know conntrack rules to support NAT, but it may not have - * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and - * share them with offload management process. - */ - public static native boolean configOffload(); - /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 90b9d3f148dc..b54571720857 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -18,19 +18,28 @@ package com.android.server.connectivity.tethering; import static android.net.util.TetheringUtils.uint16; +import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.NetlinkSocket; import android.net.util.SharedLog; -import android.net.util.TetheringUtils; +import android.net.util.SocketUtils; import android.os.Handler; +import android.os.NativeHandle; import android.os.RemoteException; +import android.system.ErrnoException; +import android.system.Os; import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; import java.util.ArrayList; @@ -49,6 +58,10 @@ public class OffloadHardwareInterface { private static final String NO_INTERFACE_NAME = ""; private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; + // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h + private static final int NF_NETLINK_CONNTRACK_NEW = 1; + private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; private final Handler mHandler; private final SharedLog mLog; @@ -121,9 +134,103 @@ public class OffloadHardwareInterface { return DEFAULT_TETHER_OFFLOAD_DISABLED; } - /** Configure offload management process. */ + /** + * Offload management process need to know conntrack rules to support NAT, but it may not have + * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and + * share them with offload management process. + */ public boolean initOffloadConfig() { - return TetheringUtils.configOffload(); + IOffloadConfig offloadConfig; + try { + offloadConfig = IOffloadConfig.getService(); + } catch (RemoteException e) { + mLog.e("getIOffloadConfig error " + e); + return false; + } + if (offloadConfig == null) { + mLog.e("Could not find IOffloadConfig service"); + return false; + } + // Per the IConfigOffload definition: + // + // h1 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). + // + // h2 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). + final NativeHandle h1 = createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + if (h1 == null) return false; + + final NativeHandle h2 = createConntrackSocket( + NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); + if (h2 == null) { + closeFdInNativeHandle(h1); + return false; + } + + final CbResults results = new CbResults(); + try { + offloadConfig.setHandles(h1, h2, + (boolean success, String errMsg) -> { + results.mSuccess = success; + results.mErrMsg = errMsg; + }); + } catch (RemoteException e) { + record("initOffloadConfig, setHandles fail", e); + return false; + } + // Explicitly close FDs. + closeFdInNativeHandle(h1); + closeFdInNativeHandle(h2); + + record("initOffloadConfig, setHandles results:", results); + return results.mSuccess; + } + + private void closeFdInNativeHandle(final NativeHandle h) { + try { + h.close(); + } catch (IOException | IllegalStateException e) { + // IllegalStateException means fd is already closed, do nothing here. + // Also nothing we can do if IOException. + } + } + + private NativeHandle createConntrackSocket(final int groups) { + FileDescriptor fd; + try { + fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); + } catch (ErrnoException e) { + mLog.e("Unable to create conntrack socket " + e); + return null; + } + + final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); + try { + Os.bind(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + try { + Os.connect(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("connect to kernel fail for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + + return new NativeHandle(fd, true); } /** Initialize the tethering offload HAL. */ diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java index c67fe9ffa916..1e8109cb393c 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java @@ -23,6 +23,8 @@ import android.util.Log; public class PacNative { private static final String TAG = "PacProxy"; + private static final PacNative sInstance = new PacNative(); + private String mCurrentPac; private boolean mIsActive; @@ -39,8 +41,12 @@ public class PacNative { System.loadLibrary("jni_pacprocessor"); } - PacNative() { + private PacNative() { + + } + public static PacNative getInstance() { + return sInstance; } public synchronized boolean startPacSupport() { diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 74391eb676d1..b006d6e1fa7b 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -31,43 +31,27 @@ import java.net.URL; public class PacService extends Service { private static final String TAG = "PacService"; - private PacNative mPacNative; - private ProxyServiceStub mStub; + private PacNative mPacNative = PacNative.getInstance(); + private ProxyServiceStub mStub = new ProxyServiceStub(); @Override public void onCreate() { super.onCreate(); - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } + mPacNative.startPacSupport(); } @Override public void onDestroy() { + mPacNative.stopPacSupport(); super.onDestroy(); - if (mPacNative != null) { - mPacNative.stopPacSupport(); - mPacNative = null; - mStub = null; - } } @Override public IBinder onBind(Intent intent) { - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } return mStub; } - private static class ProxyServiceStub extends IProxyService.Stub { - private final PacNative mPacNative; - - public ProxyServiceStub(PacNative pacNative) { - mPacNative = pacNative; - } + private class ProxyServiceStub extends IProxyService.Stub { @Override public String resolvePacFile(String host, String url) throws RemoteException { @@ -102,20 +86,12 @@ public class PacService extends Service { @Override public void startPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call startPacSystem"); - throw new SecurityException(); - } - mPacNative.startPacSupport(); + //TODO: remove } @Override public void stopPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call stopPacSystem"); - throw new SecurityException(); - } - mPacNative.stopPacSupport(); + //TODO: remove } } } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 54b420191deb..63dd99e215ab 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -244,6 +244,10 @@ message SystemMessage { // Package: android NOTE_SOFTAP_AUTO_DISABLED = 58; + // Notify the user that their admin has changed location settings. + // Package: android + NOTE_LOCATION_CHANGED = 59; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 565ee63d89ab..75ec4b04eeed 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1128,8 +1128,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { synchronized (mLock) { - // TODO: Add a method to lookup window ID by given leash token. - return -1; + return mA11yWindowManager.getWindowIdLocked(token); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7b495ce19015..ba29bc88f145 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -761,11 +761,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { return mA11yWindowManager.addAccessibilityInteractionConnection( - windowToken, connection, packageName, userId); + windowToken, leashToken, connection, packageName, userId); } @Override @@ -2643,6 +2643,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + synchronized (mLock) { + mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); + } + } + + @Override + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + synchronized (mLock) { + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); + } + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 96e345ea9b89..d98e31eadb22 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -443,8 +443,8 @@ public class AccessibilitySecurityPolicy { return false; } } - // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy. - if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) { + if (mAccessibilityWindowManager.resolveParentWindowIdLocked(windowId) + == mAccessibilityWindowManager.getActiveWindowId(userId)) { return true; } return mAccessibilityWindowManager.findA11yWindowInfoByIdLocked(windowId) != null; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index a6041e0ee91c..8c0058130510 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -31,6 +31,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -100,6 +101,19 @@ public class AccessibilityWindowManager { new SparseArray<>(); /** + * Map of host view and embedded hierarchy, mapping from leash token of its ViewRootImpl. + * The key is the token from embedded hierarchy, and the value is the token from its host. + */ + private final ArrayMap<IBinder, IBinder> mHostEmbeddedMap = new ArrayMap<>(); + + /** + * Map of window id and view hierarchy. + * The key is the window id when the ViewRootImpl register to accessibility, and the value is + * its leash token. + */ + private final SparseArray<IBinder> mWindowIdMap = new SparseArray<>(); + + /** * This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to * receive {@link WindowInfo}s from window manager when there's an accessibility change in * window and holds window lists information per display. @@ -913,19 +927,21 @@ public class AccessibilityWindowManager { * Adds accessibility interaction connection according to given window token, package name and * window token. * - * @param windowToken The window token of accessibility interaction connection + * @param window The window token of accessibility interaction connection + * @param leashToken The leash token of accessibility interaction connection * @param connection The accessibility interaction connection * @param packageName The package name * @param userId The userId * @return The windowId of added connection * @throws RemoteException */ - public int addAccessibilityInteractionConnection(@NonNull IWindow windowToken, - @NonNull IAccessibilityInteractionConnection connection, @NonNull String packageName, - int userId) throws RemoteException { + public int addAccessibilityInteractionConnection(@NonNull IWindow window, + @NonNull IBinder leashToken, @NonNull IAccessibilityInteractionConnection connection, + @NonNull String packageName, int userId) throws RemoteException { final int windowId; boolean shouldComputeWindows = false; - final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken.asBinder()); + final IBinder token = window.asBinder(); + final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -947,35 +963,33 @@ public class AccessibilityWindowManager { windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL); wrapper.linkToDeath(); mGlobalInteractionConnections.put(windowId, wrapper); - mGlobalWindowTokens.put(windowId, windowToken.asBinder()); + mGlobalWindowTokens.put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and token: " - + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } else { RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( windowId, connection, packageName, resolvedUid, resolvedUserId); wrapper.linkToDeath(); getInteractionConnectionsForUserLocked(resolvedUserId).put(windowId, wrapper); - getWindowTokensForUserLocked(resolvedUserId).put(windowId, windowToken.asBinder()); + getWindowTokensForUserLocked(resolvedUserId).put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId - + " and token: " + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } if (isTrackingWindowsLocked(displayId)) { shouldComputeWindows = true; } + registerIdLocked(leashToken, windowId); } if (shouldComputeWindows) { mWindowManagerInternal.computeWindowsForAccessibility(displayId); } - mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( - windowToken.asBinder(), windowId); + mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId); return windowId; } @@ -1098,6 +1112,7 @@ public class AccessibilityWindowManager { * Invoked when accessibility interaction connection of window is removed. * * @param windowId Removed windowId + * @param binder Removed window token */ private void onAccessibilityInteractionConnectionRemovedLocked( int windowId, @Nullable IBinder binder) { @@ -1110,6 +1125,7 @@ public class AccessibilityWindowManager { mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); } + unregisterIdLocked(windowId); } /** @@ -1132,7 +1148,7 @@ public class AccessibilityWindowManager { * Returns the userId that owns the given window token, {@link UserHandle#USER_NULL} * if not found. * - * @param windowToken The winodw token + * @param windowToken The window token * @return The userId */ public int getWindowOwnerUserId(@NonNull IBinder windowToken) { @@ -1161,6 +1177,50 @@ public class AccessibilityWindowManager { } /** + * Establish the relationship between the host and the embedded view hierarchy. + * + * @param host The token of host hierarchy + * @param embedded The token of the embedded hierarchy + */ + public void associateEmbeddedHierarchyLocked(@NonNull IBinder host, @NonNull IBinder embedded) { + // Use embedded window as key, since one host window may have multiple embedded windows. + associateLocked(embedded, host); + } + + /** + * Clear the relationship by given token. + * + * @param token The token + */ + public void disassociateEmbeddedHierarchyLocked(@NonNull IBinder token) { + disassociateLocked(token); + } + + /** + * Gets the parent windowId of the window according to the specified windowId. + * + * @param windowId The windowId to check + * @return The windowId of the parent window, or self if no parent exists + */ + public int resolveParentWindowIdLocked(int windowId) { + final IBinder token = getTokenLocked(windowId); + if (token == null) { + return windowId; + } + final IBinder resolvedToken = resolveTopParentTokenLocked(token); + final int resolvedWindowId = getWindowIdLocked(resolvedToken); + return resolvedWindowId != -1 ? resolvedWindowId : windowId; + } + + private IBinder resolveTopParentTokenLocked(IBinder token) { + final IBinder hostToken = getHostTokenLocked(token); + if (hostToken == null) { + return token; + } + return resolveTopParentTokenLocked(hostToken); + } + + /** * Computes partial interactive region of given windowId. * * @param windowId The windowId @@ -1357,6 +1417,7 @@ public class AccessibilityWindowManager { */ @Nullable public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) { + windowId = resolveParentWindowIdLocked(windowId); final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); if (observer != null) { return observer.findA11yWindowInfoByIdLocked(windowId); @@ -1584,6 +1645,88 @@ public class AccessibilityWindowManager { } /** + * Associate the token of the embedded view hierarchy to the host view hierarchy. + * + * @param embedded The leash token from the view root of embedded hierarchy + * @param host The leash token from the view root of host hierarchy + */ + void associateLocked(IBinder embedded, IBinder host) { + mHostEmbeddedMap.put(embedded, host); + } + + /** + * Clear the relationship of given token. + * + * @param token The leash token + */ + void disassociateLocked(IBinder token) { + mHostEmbeddedMap.remove(token); + for (int i = mHostEmbeddedMap.size() - 1; i >= 0; i--) { + if (mHostEmbeddedMap.valueAt(i).equals(token)) { + mHostEmbeddedMap.removeAt(i); + } + } + } + + /** + * Register the leash token with its windowId. + * + * @param token The token. + * @param windowId The windowID. + */ + void registerIdLocked(IBinder token, int windowId) { + mWindowIdMap.put(windowId, token); + } + + /** + * Unregister the windowId and also disassociate its token. + * + * @param windowId The windowID + */ + void unregisterIdLocked(int windowId) { + final IBinder token = mWindowIdMap.get(windowId); + if (token == null) { + return; + } + disassociateLocked(token); + mWindowIdMap.remove(windowId); + } + + /** + * Get the leash token by given windowID. + * + * @param windowId The windowID. + * @return The token, or {@code NULL} if this windowID doesn't exist + */ + IBinder getTokenLocked(int windowId) { + return mWindowIdMap.get(windowId); + } + + /** + * Get the windowId by given leash token. + * + * @param token The token + * @return The windowID, or -1 if the token doesn't exist + */ + int getWindowIdLocked(IBinder token) { + final int index = mWindowIdMap.indexOfValue(token); + if (index == -1) { + return index; + } + return mWindowIdMap.keyAt(index); + } + + /** + * Get the leash token of the host hierarchy by given token. + * + * @param token The token + * @return The token of host hierarchy, or {@code NULL} if no host exists + */ + IBinder getHostTokenLocked(IBinder token) { + return mHostEmbeddedMap.get(token); + } + + /** * Dumps all {@link AccessibilityWindowInfo}s here. */ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java index 5844f9873001..1c4db1214d3b 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java @@ -18,12 +18,14 @@ package com.android.server.appprediction; import static android.Manifest.permission.MANAGE_APP_PREDICTIONS; import static android.Manifest.permission.PACKAGE_USAGE_STATS; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.content.Context.APP_PREDICTION_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTargetEvent; @@ -34,7 +36,6 @@ import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.UserHandle; import android.util.Slog; import com.android.server.LocalServices; @@ -108,21 +109,21 @@ public class AppPredictionManagerService extends @Override public void createPredictionSession(@NonNull AppPredictionContext context, @NonNull AppPredictionSessionId sessionId) { - runForUserLocked("createPredictionSession", + runForUserLocked("createPredictionSession", sessionId, (service) -> service.onCreatePredictionSessionLocked(context, sessionId)); } @Override public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, @NonNull AppTargetEvent event) { - runForUserLocked("notifyAppTargetEvent", + runForUserLocked("notifyAppTargetEvent", sessionId, (service) -> service.notifyAppTargetEventLocked(sessionId, event)); } @Override public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId, @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - runForUserLocked("notifyLaunchLocationShown", (service) -> + runForUserLocked("notifyLaunchLocationShown", sessionId, (service) -> service.notifyLaunchLocationShownLocked(sessionId, launchLocation, targetIds)); } @@ -130,32 +131,32 @@ public class AppPredictionManagerService extends public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, @NonNull ParceledListSlice targets, IPredictionCallback callback) { - runForUserLocked("sortAppTargets", + runForUserLocked("sortAppTargets", sessionId, (service) -> service.sortAppTargetsLocked(sessionId, targets, callback)); } @Override public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - runForUserLocked("registerPredictionUpdates", + runForUserLocked("registerPredictionUpdates", sessionId, (service) -> service.registerPredictionUpdatesLocked(sessionId, callback)); } public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - runForUserLocked("unregisterPredictionUpdates", + runForUserLocked("unregisterPredictionUpdates", sessionId, (service) -> service.unregisterPredictionUpdatesLocked(sessionId, callback)); } @Override public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { - runForUserLocked("requestPredictionUpdate", + runForUserLocked("requestPredictionUpdate", sessionId, (service) -> service.requestPredictionUpdateLocked(sessionId)); } @Override public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { - runForUserLocked("onDestroyPredictionSession", + runForUserLocked("onDestroyPredictionSession", sessionId, (service) -> service.onDestroyPredictionSessionLocked(sessionId)); } @@ -167,9 +168,12 @@ public class AppPredictionManagerService extends .exec(this, in, out, err, args, callback, resultReceiver); } - private void runForUserLocked(@NonNull String func, - @NonNull Consumer<AppPredictionPerUserService> c) { - final int userId = UserHandle.getCallingUserId(); + private void runForUserLocked(@NonNull final String func, + @NonNull final AppPredictionSessionId sessionId, + @NonNull final Consumer<AppPredictionPerUserService> c) { + ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class); + final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + sessionId.getUserId(), false, ALLOW_NON_FULL, null, null); Context ctx = getContext(); if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index e3d2dcc8141f..6247a635233a 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -1080,12 +1080,8 @@ public class UserBackupManagerService { } } - public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) { - return mBackupPreferences.getExcludedRestoreKeysForPackages(packages); - } - - public Map<String, Set<String>> getAllExcludedRestoreKeys() { - return mBackupPreferences.getAllExcludedRestoreKeys(); + public Set<String> getExcludedRestoreKeys(String packageName) { + return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName); } /** Used for generating random salts or passwords. */ @@ -3356,8 +3352,7 @@ public class UserBackupManagerService { restoreSet, packageName, token, - listener, - getExcludedRestoreKeys(packageName)); + listener); mBackupHandler.sendMessage(msg); } catch (Exception e) { // Calling into the transport broke; back off and proceed with the installation. diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java index 41b9719d273b..bb8bf52187c5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupPreferences.java +++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java @@ -48,16 +48,7 @@ public class UserBackupPreferences { mEditor.commit(); } - Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) { - Map<String, Set<String>> excludedKeys = new HashMap<>(); - for (String packageName : packages) { - excludedKeys.put(packageName, - mPreferences.getStringSet(packageName, Collections.emptySet())); - } - return excludedKeys; - } - - Map<String, Set<String>> getAllExcludedRestoreKeys() { - return (Map<String, Set<String>>) mPreferences.getAll(); + Set<String> getExcludedRestoreKeysForPackage(String packageName) { + return mPreferences.getStringSet(packageName, Collections.emptySet()); } } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 05396f36b364..87a8e4982529 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -299,8 +299,7 @@ public class BackupHandler extends Handler { params.pmToken, params.isSystemRestore, params.filterSet, - params.listener, - params.excludedKeys); + params.listener); synchronized (backupManagerService.getPendingRestores()) { if (backupManagerService.isRestoreInProgress()) { diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java index 09b7e3535e2e..a6fea6cc75a0 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java @@ -37,7 +37,6 @@ public class RestoreParams { public final boolean isSystemRestore; @Nullable public final String[] filterSet; public final OnTaskFinishedListener listener; - public final Map<String, Set<String>> excludedKeys; /** * No kill after restore. @@ -48,8 +47,7 @@ public class RestoreParams { IBackupManagerMonitor monitor, long token, PackageInfo packageInfo, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -59,8 +57,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ false, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -73,8 +70,7 @@ public class RestoreParams { long token, String packageName, int pmToken, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { String[] filterSet = {packageName}; return new RestoreParams( transportClient, @@ -85,8 +81,7 @@ public class RestoreParams { pmToken, /* isSystemRestore */ false, filterSet, - listener, - excludedKeys); + listener); } /** @@ -97,8 +92,7 @@ public class RestoreParams { IRestoreObserver observer, IBackupManagerMonitor monitor, long token, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -108,8 +102,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ true, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -122,8 +115,7 @@ public class RestoreParams { long token, String[] filterSet, boolean isSystemRestore, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -133,8 +125,7 @@ public class RestoreParams { /* pmToken */ 0, isSystemRestore, filterSet, - listener, - excludedKeys); + listener); } private RestoreParams( @@ -146,8 +137,7 @@ public class RestoreParams { int pmToken, boolean isSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.transportClient = transportClient; this.observer = observer; this.monitor = monitor; @@ -157,6 +147,5 @@ public class RestoreParams { this.isSystemRestore = isSystemRestore; this.filterSet = filterSet; this.listener = listener; - this.excludedKeys = excludedKeys; } } diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index c0f76c39e04f..5a57cdc39402 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -178,8 +178,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { observer, monitor, token, - listener, - mBackupManagerService.getAllExcludedRestoreKeys()), + listener), "RestoreSession.restoreAll()"); } finally { Binder.restoreCallingIdentity(oldId); @@ -272,8 +271,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { token, packages, /* isSystemRestore */ packages.length > 1, - listener, - mBackupManagerService.getExcludedRestoreKeys(packages)), + listener), "RestoreSession.restorePackages(" + packages.length + " packages)"); } finally { Binder.restoreCallingIdentity(oldId); @@ -365,8 +363,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { monitor, token, app, - listener, - mBackupManagerService.getExcludedRestoreKeys(app.packageName)), + listener), "RestoreSession.restorePackage(" + packageName + ")"); } finally { Binder.restoreCallingIdentity(oldId); diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index f90d936a5f4d..3c37f737f8be 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -155,8 +155,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // When finished call listener private final OnTaskFinishedListener mListener; - private final Map<String, Set<String>> mExcludedKeys; - // Key/value: bookkeeping about staged data and files for agent access private File mBackupDataName; private File mStageName; @@ -168,14 +166,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final BackupAgentTimeoutParameters mAgentTimeoutParameters; @VisibleForTesting - PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) { - mExcludedKeys = excludedKeys; + PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) { mListener = null; mAgentTimeoutParameters = null; mTransportClient = null; mTransportManager = null; mEphemeralOpToken = 0; mUserId = 0; + this.backupManagerService = backupManagerService; } // This task can assume that the wakelock is properly held for it and doesn't have to worry @@ -190,8 +188,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.backupManagerService = backupManagerService; mUserId = backupManagerService.getUserId(); mTransportManager = backupManagerService.getTransportManager(); @@ -213,8 +210,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { backupManagerService.getAgentTimeoutParameters(), "Timeout parameters cannot be null"); - mExcludedKeys = excludedKeys; - if (targetPackage != null) { // Single package restore mAcceptSet = new ArrayList<>(); @@ -791,8 +786,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty(); } - private Set<String> getExcludedKeysForPackage(String packageName) { - return mExcludedKeys.getOrDefault(packageName, Collections.emptySet()); + @VisibleForTesting + Set<String> getExcludedKeysForPackage(String packageName) { + return backupManagerService.getExcludedRestoreKeys(packageName); } @VisibleForTesting diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 63d092456a26..bdcd832e4f4a 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -396,7 +396,6 @@ public abstract class PackageManagerInternal { * @param origIntent The original intent that triggered ephemeral resolution * @param resolvedType The resolved type of the intent * @param callingPkg The app requesting the ephemeral application - * @param callingFeatureId The feature in the package * @param isRequesterInstantApp Whether or not the app requesting the ephemeral application * is an instant app * @param verificationBundle Optional bundle to pass to the installer for additional @@ -405,8 +404,7 @@ public abstract class PackageManagerInternal { */ public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPkg, - @Nullable String callingFeatureId, boolean isRequesterInstantApp, - Bundle verificationBundle, int userId); + boolean isRequesterInstantApp, Bundle verificationBundle, int userId); /** * Grants implicit access based on an interaction between two apps. This grants the target app diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 63cddac0bfba..2c91a11c358c 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -43,6 +43,7 @@ import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -2287,12 +2288,14 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener, - String packageName, String featureId, String listenerIdentifier) { + public boolean addGnssMeasurementsListener(@Nullable GnssRequest request, + IGnssMeasurementsListener listener, + String packageName, String featureId, + String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( - listener, packageName, featureId, listenerIdentifier); + request, listener, packageName, featureId, listenerIdentifier); } @Override @@ -2419,6 +2422,17 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public void setLocationEnabledForUser(boolean enabled, int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, + null); + } + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS, + "Requires WRITE_SECURE_SETTINGS permission"); + mSettingsHelper.setLocationEnabled(enabled, userId); + } + + @Override public boolean isLocationEnabledForUser(int userId) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "isLocationEnabledForUser", null); diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java index 372e91e41772..ba1c81cdd975 100644 --- a/services/core/java/com/android/server/LocationManagerServiceUtils.java +++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java @@ -17,6 +17,7 @@ package com.android.server; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -37,22 +38,31 @@ public class LocationManagerServiceUtils { /** * Listener that can be linked to a binder. * @param <TListener> listener type + * @param <TRequest> request type */ - public static class LinkedListener<TListener> extends + public static class LinkedListener<TRequest, TListener> extends LinkedListenerBase { + @Nullable protected final TRequest mRequest; private final TListener mListener; private final Consumer<TListener> mBinderDeathCallback; public LinkedListener( + @Nullable TRequest request, @NonNull TListener listener, String listenerName, @NonNull CallerIdentity callerIdentity, @NonNull Consumer<TListener> binderDeathCallback) { super(callerIdentity, listenerName); mListener = listener; + mRequest = request; mBinderDeathCallback = binderDeathCallback; } + @Nullable + public TRequest getRequest() { + return mRequest; + } + @Override public void binderDied() { if (D) Log.d(TAG, "Remote " + mListenerName + " died."); diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 0eedf8a1f468..f2d5a9b685f4 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -302,15 +302,19 @@ final class UiModeManagerService extends SystemService { private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { - int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, - mNightMode, 0); - if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { - mode = MODE_NIGHT_YES; - } - SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + updateSystemProperties(); } }; + private void updateSystemProperties() { + int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, + mNightMode, 0); + if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { + mode = MODE_NIGHT_YES; + } + SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + } + @Override public void onSwitchUser(int userHandle) { super.onSwitchUser(userHandle); @@ -392,6 +396,7 @@ final class UiModeManagerService extends SystemService { context.getContentResolver().registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE), false, mDarkThemeObserver, 0); + updateSystemProperties(); } @VisibleForTesting @@ -1261,9 +1266,8 @@ final class UiModeManagerService extends SystemService { if (Sandman.shouldStartDockApp(getContext(), homeIntent)) { try { int result = ActivityTaskManager.getService().startActivityWithConfig( - null, getContext().getBasePackageName(), getContext().getFeatureId(), - homeIntent, null, null, null, 0, 0, mConfiguration, null, - UserHandle.USER_CURRENT); + null, getContext().getBasePackageName(), homeIntent, null, null, null, + 0, 0, mConfiguration, null, UserHandle.USER_CURRENT); if (ActivityManager.isStartResultSuccessful(result)) { dockAppStarted = true; } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 97b5eaaca756..982466d82d38 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -417,23 +417,22 @@ public final class ActiveServices { } private boolean appRestrictedAnyInBackground(final int uid, final String packageName) { - final int mode = mAm.getAppOpsManager().checkOpNoThrow( + final int mode = mAm.mAppOpsService.checkOperation( AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); return (mode != AppOpsManager.MODE_ALLOWED); } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, - int callingPid, int callingUid, boolean fgRequired, String callingPackage, - @Nullable String callingFeatureId, final int userId) + int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired, - callingPackage, callingFeatureId, userId, false); + callingPackage, userId, false); } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, - @Nullable String callingFeatureId, final int userId, - boolean allowBackgroundActivityStarts) throws TransactionTooLargeException { + final int userId, boolean allowBackgroundActivityStarts) + throws TransactionTooLargeException { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); @@ -489,7 +488,7 @@ public final class ActiveServices { // If this is a direct-to-foreground start, make sure it is allowed as per the app op. boolean forceSilentAbort = false; if (fgRequired) { - final int mode = mAm.getAppOpsManager().checkOpNoThrow( + final int mode = mAm.mAppOpsService.checkOperation( AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName); switch (mode) { case AppOpsManager.MODE_ALLOWED: @@ -567,7 +566,7 @@ public final class ActiveServices { // review is completed. // XXX This is not dealing with fgRequired! - if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage, callingFeatureId, + if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage, callingUid, service, callerFg, userId)) { return null; } @@ -674,8 +673,8 @@ public final class ActiveServices { } private boolean requestStartTargetPermissionsReviewIfNeededLocked(ServiceRecord r, - String callingPackage, @Nullable String callingFeatureId, int callingUid, - Intent service, boolean callerFg, final int userId) { + String callingPackage, int callingUid, Intent service, boolean callerFg, + final int userId) { if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired( r.packageName, r.userId)) { @@ -687,7 +686,7 @@ public final class ActiveServices { } IIntentSender target = mAm.mPendingIntentController.getIntentSender( - ActivityManager.INTENT_SENDER_SERVICE, callingPackage, callingFeatureId, + ActivityManager.INTENT_SENDER_SERVICE, callingPackage, callingUid, userId, null, null, 0, new Intent[]{service}, new String[]{service.resolveType(mAm.mContext.getContentResolver())}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT @@ -1288,7 +1287,7 @@ public final class ActiveServices { } // Instant apps need permission to create foreground services. if (r.appInfo.isInstantApp()) { - final int mode = mAm.getAppOpsManager().checkOpNoThrow( + final int mode = mAm.mAppOpsService.checkOperation( AppOpsManager.OP_INSTANT_APP_START_FOREGROUND, r.appInfo.uid, r.appInfo.packageName); @@ -1355,7 +1354,7 @@ public final class ActiveServices { try { boolean ignoreForeground = false; - final int mode = mAm.getAppOpsManager().checkOpNoThrow( + final int mode = mAm.mAppOpsService.checkOperation( AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName); switch (mode) { case AppOpsManager.MODE_ALLOWED: @@ -2290,7 +2289,7 @@ public final class ActiveServices { return new ServiceLookupResult(null, r.permission); } else if (r.permission != null && callingPackage != null) { final int opCode = AppOpsManager.permissionToOpCode(r.permission); - if (opCode != AppOpsManager.OP_NONE && mAm.getAppOpsManager().checkOpNoThrow( + if (opCode != AppOpsManager.OP_NONE && mAm.mAppOpsService.checkOperation( opCode, callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: Accessing service " + r.shortInstanceName + " from pid=" + callingPid diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d5a725339b90..b6dc3de908f0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -466,9 +466,18 @@ public class ActivityManagerService extends IActivityManager.Stub // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; + // How long we wait for an attached process to publish its content providers + // before we decide it must be hung. + static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; + // How long we wait to kill an application zygote, after the last process using // it has gone away. static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; + /** + * How long we wait for an provider to be published. Should be longer than + * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}. + */ + static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real, when the process was @@ -1231,7 +1240,6 @@ public class ActivityManagerService extends IActivityManager.Stub * Information about and control over application operations */ final AppOpsService mAppOpsService; - private AppOpsManager mAppOpsManager; /** * List of initialization arguments to pass to all processes when binding applications to them. @@ -2101,7 +2109,7 @@ public class ActivityManagerService extends IActivityManager.Stub new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) { - if (getAppOpsManager().checkOpNoThrow(op, uid, packageName) + if (mAppOpsService.checkOperation(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { runInBackgroundDisabled(uid); } @@ -2392,13 +2400,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - AppOpsManager getAppOpsManager() { - if (mAppOpsManager == null) { - mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - } - return mAppOpsManager; - } - /** * Provides the basic functionality for activity task related tests when a handler thread is * given to initialize the dependency members. @@ -2907,7 +2908,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void batterySendBroadcast(Intent intent) { synchronized (this) { - broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } @@ -3511,56 +3512,30 @@ public class ActivityManagerService extends IActivityManager.Stub } - /** - * @deprecated use {@link #startActivityWithFeature} instead - */ - @Deprecated @Override public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { - return mActivityTaskManager.startActivity(caller, callingPackage, null, intent, - resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions); - } - - @Override - public int startActivityWithFeature(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions) { - return mActivityTaskManager.startActivity(caller, callingPackage, callingFeatureId, intent, - resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions); + return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType, + resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions); } - /** - * @deprecated use {@link #startActivityAsUserWithFeature} instead - */ - @Deprecated @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { - return startActivityAsUserWithFeature(caller, callingPackage, null, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, userId); - } - @Override - public final int startActivityAsUserWithFeature(IApplicationThread caller, - String callingPackage, String callingFeatureId, Intent intent, String resolvedType, - IBinder resultTo, String resultWho, int requestCode, int startFlags, - ProfilerInfo profilerInfo, Bundle bOptions, int userId) { - return mActivityTaskManager.startActivityAsUser(caller, callingPackage, - callingFeatureId, intent, resolvedType, resultTo, resultWho, requestCode, - startFlags, profilerInfo, bOptions, userId); + return mActivityTaskManager.startActivityAsUser(caller, callingPackage, intent, + resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, + bOptions, userId); } WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, - @Nullable String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions, int userId) { - return mActivityTaskManager.startActivityAndWait(caller, callingPackage, - callingFeatureId, intent, resolvedType, resultTo, resultWho, requestCode, - startFlags, profilerInfo, bOptions, userId); + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { + return mActivityTaskManager.startActivityAndWait(caller, callingPackage, intent, + resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, + bOptions, userId); } @Override @@ -4131,12 +4106,12 @@ public class ActivityManagerService extends IActivityManager.Stub 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); + broadcastIntentInPackage("android", 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, + broadcastIntentInPackage("android", SYSTEM_UID, uid, pid, intent, null, + null, 0, null, null, null, null, false, false, resolvedUserId, false); } @@ -4584,7 +4559,7 @@ public class ActivityManagerService extends IActivityManager.Stub } intent.putExtra(Intent.EXTRA_UID, uid); intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid)); - broadcastIntentLocked(null, null, null, intent, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.getUserId(uid)); @@ -4959,8 +4934,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (providers != null && checkAppInLaunchingProvidersLocked(app)) { Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, - ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS); + mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); } checkTime(startTime, "attachApplicationLocked: before bindApplication"); @@ -5442,23 +5416,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * @deprecated Use {@link #getIntentSenderWithFeature} instead - */ - @Deprecated @Override public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions, int userId) { - return getIntentSenderWithFeature(type, packageName, null, token, resultWho, requestCode, - intents, resolvedTypes, flags, bOptions, userId); - } - @Override - public IIntentSender getIntentSenderWithFeature(int type, String packageName, String featureId, - IBinder token, String resultWho, int requestCode, Intent[] intents, - String[] resolvedTypes, int flags, Bundle bOptions, int userId) { // NOTE: The service lock isn't held in this method because nothing in the method requires // the service lock to be held. @@ -5520,13 +5483,12 @@ public class ActivityManagerService extends IActivityManager.Stub } if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { - return mAtmInternal.getIntentSender(type, packageName, featureId, callingUid, - userId, token, resultWho, requestCode, intents, resolvedTypes, flags, - bOptions); + return mAtmInternal.getIntentSender(type, packageName, callingUid, userId, + token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions); } - return mPendingIntentController.getIntentSender(type, packageName, featureId, - callingUid, userId, token, resultWho, requestCode, intents, resolvedTypes, - flags, bOptions); + return mPendingIntentController.getIntentSender(type, packageName, callingUid, + userId, token, resultWho, requestCode, intents, resolvedTypes, flags, + bOptions); } catch (RemoteException e) { throw new SecurityException(e); } @@ -6092,8 +6054,8 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManager.APP_START_MODE_DELAYED; } // Not in the RESTRICTED bucket so policy is based on AppOp check. - int appop = getAppOpsManager().noteOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, - uid, packageName, null, ""); + int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, + uid, packageName, null, false, ""); if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop); } @@ -7239,8 +7201,7 @@ public class ActivityManagerService extends IActivityManager.Stub } // Wait for the provider to be published... - final long timeout = - SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS; + final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT; boolean timedOut = false; synchronized (cpr) { while (cpr.provider == null) { @@ -7277,14 +7238,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (timedOut) { - // Note we do it after releasing the lock. + // Note we do it afer releasing the lock. String callerName = "unknown"; - if (caller != null) { - synchronized (this) { - final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); - if (record != null) { - callerName = record.processName; - } + synchronized (this) { + final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); + if (record != null) { + callerName = record.processName; } } @@ -8002,7 +7961,7 @@ public class ActivityManagerService extends IActivityManager.Stub } boolean isBackgroundRestrictedNoCheck(final int uid, final String packageName) { - final int mode = getAppOpsManager().checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, + final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); return mode != AppOpsManager.MODE_ALLOWED; } @@ -9480,14 +9439,14 @@ public class ActivityManagerService extends IActivityManager.Stub intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId); - broadcastIntentLocked(null, null, null, intent, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid, currentUserId); intent = new Intent(Intent.ACTION_USER_STARTING); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId); - broadcastIntentLocked(null, null, null, intent, null, + broadcastIntentLocked(null, null, intent, null, new IIntentReceiver.Stub() { @Override public void performReceive(Intent intent, int resultCode, @@ -11112,10 +11071,12 @@ public class ActivityManagerService extends IActivityManager.Stub } if (dumpAll || dumpPackage != null) { + final SparseArray<ProcessRecord> pidToProcess = new SparseArray<>(); synchronized (mPidsSelfLocked) { boolean printed = false; for (int i=0; i<mPidsSelfLocked.size(); i++) { ProcessRecord r = mPidsSelfLocked.valueAt(i); + pidToProcess.put(r.pid, r); if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } @@ -11129,6 +11090,32 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); } } + + synchronized (sActiveProcessInfoSelfLocked) { + boolean printed = false; + for (int i = 0; i < sActiveProcessInfoSelfLocked.size(); i++) { + ProcessInfo info = sActiveProcessInfoSelfLocked.valueAt(i); + ProcessRecord r = pidToProcess.get(sActiveProcessInfoSelfLocked.keyAt(i)); + if (r != null && dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { + continue; + } + if (!printed) { + if (needSep) pw.println(); + needSep = true; + pw.println(" Active process infos:"); + printed = true; + } + pw.print(" Pinfo PID #"); pw.print(sActiveProcessInfoSelfLocked.keyAt(i)); + pw.println(":"); + pw.print(" name="); pw.println(info.name); + if (info.deniedPermissions != null) { + for (int j = 0; j < info.deniedPermissions.size(); j++) { + pw.print(" deny: "); + pw.println(info.deniedPermissions.valueAt(i)); + } + } + } + } } if (mImportantProcesses.size() > 0) { @@ -14626,8 +14613,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType, boolean requireForeground, String callingPackage, - String callingFeatureId, int userId) + String resolvedType, boolean requireForeground, String callingPackage, int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("startService"); // Refuse possible leaked file descriptors @@ -14649,7 +14635,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, - requireForeground, callingPackage, callingFeatureId, userId); + requireForeground, callingPackage, userId); } finally { Binder.restoreCallingIdentity(origId); } @@ -15127,20 +15113,9 @@ public class ActivityManagerService extends IActivityManager.Stub return didSomething; } - /** - * @deprecated Use {@link #registerReceiverWithFeature} - */ - @Deprecated public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission, int userId, int flags) { - return registerReceiverWithFeature(caller, callerPackage, null, receiver, filter, - permission, userId, flags); - } - - public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage, - String callerFeatureId, IIntentReceiver receiver, IntentFilter filter, - String permission, int userId, int flags) { enforceNotIsolatedCaller("registerReceiver"); ArrayList<Intent> stickyIntents = null; ProcessRecord callerApp = null; @@ -15276,7 +15251,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " was previously registered for user " + rl.userId + " callerPackage is " + callerPackage); } - BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId, + BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission, callingUid, userId, instantApp, visibleToInstantApps); if (rl.containsFilter(filter)) { Slog.w(TAG, "Receiver with filter " + filter @@ -15301,7 +15276,7 @@ public class ActivityManagerService extends IActivityManager.Stub Intent intent = allSticky.get(i); BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, - null, null, -1, -1, false, null, null, OP_NONE, null, receivers, + null, -1, -1, false, null, null, OP_NONE, null, receivers, null, 0, null, null, false, true, true, -1, false, false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */); queue.enqueueParallelBroadcastLocked(r); @@ -15531,20 +15506,20 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final int broadcastIntentLocked(ProcessRecord callerApp, - String callerPackage, String callerFeatureId, Intent intent, String resolvedType, + String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid, int realCallingPid, int userId) { - return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent, - resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, - appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid, - realCallingPid, userId, false /* allowBackgroundActivityStarts */); + return broadcastIntentLocked(callerApp, callerPackage, intent, resolvedType, resultTo, + resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions, ordered, + sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId, + false /* allowBackgroundActivityStarts */); } @GuardedBy("this") - final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, - @Nullable String callerFeatureId, Intent intent, String resolvedType, + final int broadcastIntentLocked(ProcessRecord callerApp, + String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid, @@ -16081,8 +16056,8 @@ public class ActivityManagerService extends IActivityManager.Stub isProtectedBroadcast, registeredReceivers); } final BroadcastQueue queue = broadcastQueueForIntent(intent); - BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, - callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, timeoutExempt); @@ -16178,8 +16153,8 @@ public class ActivityManagerService extends IActivityManager.Stub if ((receivers != null && receivers.size() > 0) || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); - BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, - callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, timeoutExempt); @@ -16298,25 +16273,11 @@ public class ActivityManagerService extends IActivityManager.Stub return intent; } - /** - * @deprecated Use {@link #broadcastIntentWithFeature} - */ - @Deprecated public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions, boolean serialized, boolean sticky, int userId) { - return broadcastIntentWithFeature(caller, null, intent, resolvedType, resultTo, resultCode, - resultData, resultExtras, requiredPermissions, appOp, bOptions, serialized, sticky, - userId); - } - - public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId, - Intent intent, String resolvedType, IIntentReceiver resultTo, - int resultCode, String resultData, Bundle resultExtras, - String[] requiredPermissions, int appOp, Bundle bOptions, - boolean serialized, boolean sticky, int userId) { enforceNotIsolatedCaller("broadcastIntent"); synchronized(this) { intent = verifyBroadcastLocked(intent); @@ -16328,7 +16289,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long origId = Binder.clearCallingIdentity(); try { return broadcastIntentLocked(callerApp, - callerApp != null ? callerApp.info.packageName : null, callingFeatureId, + callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid, callingPid, userId); @@ -16338,9 +16299,9 @@ public class ActivityManagerService extends IActivityManager.Stub } } - int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid, - int realCallingUid, int realCallingPid, Intent intent, String resolvedType, - IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, + int broadcastIntentInPackage(String packageName, int uid, int realCallingUid, + int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions, boolean serialized, boolean sticky, int userId, boolean allowBackgroundActivityStarts) { synchronized(this) { @@ -16350,10 +16311,11 @@ public class ActivityManagerService extends IActivityManager.Stub String[] requiredPermissions = requiredPermission == null ? null : new String[] {requiredPermission}; try { - return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType, - resultTo, resultCode, resultData, resultExtras, requiredPermissions, - OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid, - realCallingPid, userId, allowBackgroundActivityStarts); + return broadcastIntentLocked(null, packageName, intent, resolvedType, + resultTo, resultCode, resultData, resultExtras, + requiredPermissions, OP_NONE, bOptions, serialized, + sticky, -1, uid, realCallingUid, realCallingPid, userId, + allowBackgroundActivityStarts); } finally { Binder.restoreCallingIdentity(origId); } @@ -18957,24 +18919,23 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid, - int realCallingUid, int realCallingPid, Intent intent, String resolvedType, - IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, - String requiredPermission, Bundle bOptions, boolean serialized, boolean sticky, - int userId, boolean allowBackgroundActivityStarts) { + public int broadcastIntentInPackage(String packageName, int uid, int realCallingUid, + int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle resultExtras, String requiredPermission, + Bundle bOptions, boolean serialized, boolean sticky, int userId, + boolean allowBackgroundActivityStarts) { synchronized (ActivityManagerService.this) { - return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId, - uid, realCallingUid, realCallingPid, intent, resolvedType, resultTo, - resultCode, resultData, resultExtras, requiredPermission, bOptions, - serialized, sticky, userId, allowBackgroundActivityStarts); + return ActivityManagerService.this.broadcastIntentInPackage(packageName, uid, + realCallingUid, realCallingPid, intent, resolvedType, resultTo, resultCode, + resultData, resultExtras, requiredPermission, bOptions, serialized, sticky, + userId, allowBackgroundActivityStarts); } } @Override public ComponentName startServiceInPackage(int uid, Intent service, String resolvedType, - boolean fgRequired, String callingPackage, @Nullable String callingFeatureId, - int userId, boolean allowBackgroundActivityStarts) - throws TransactionTooLargeException { + boolean fgRequired, String callingPackage, int userId, + boolean allowBackgroundActivityStarts) throws TransactionTooLargeException { synchronized(ActivityManagerService.this) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "startServiceInPackage: " + service + " type=" + resolvedType); @@ -18982,8 +18943,8 @@ public class ActivityManagerService extends IActivityManager.Stub ComponentName res; try { res = mServices.startServiceLocked(null, service, - resolvedType, -1, uid, fgRequired, callingPackage, - callingFeatureId, userId, allowBackgroundActivityStarts); + resolvedType, -1, uid, fgRequired, callingPackage, userId, + allowBackgroundActivityStarts); } finally { Binder.restoreCallingIdentity(origId); } @@ -19076,7 +19037,7 @@ public class ActivityManagerService extends IActivityManager.Stub | Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); - broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) { @@ -19087,7 +19048,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (initLocale || !mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } - broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } @@ -19102,7 +19063,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Typically only app stores will have this permission. String[] permissions = new String[] { android.Manifest.permission.INSTALL_PACKAGES }; - broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, permissions, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } @@ -19127,7 +19088,7 @@ public class ActivityManagerService extends IActivityManager.Stub intent.putExtra("reason", reason); } - broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index c0e98cd9b30a..53a967b0ce50 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -534,13 +534,13 @@ final class ActivityManagerShellCommand extends ShellCommand { options.setLockTaskEnabled(true); } if (mWaitOption) { - result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent, - mimeType, null, null, 0, mStartFlags, profilerInfo, + result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, intent, mimeType, + null, null, 0, mStartFlags, profilerInfo, options != null ? options.toBundle() : null, mUserId); res = result.result; } else { - res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null, - intent, mimeType, null, null, 0, mStartFlags, profilerInfo, + res = mInternal.startActivityAsUser(null, SHELL_PACKAGE_NAME, intent, mimeType, + null, null, 0, mStartFlags, profilerInfo, options != null ? options.toBundle() : null, mUserId); } final long endTime = SystemClock.uptimeMillis(); @@ -652,7 +652,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println("Starting service: " + intent); pw.flush(); ComponentName cn = mInterface.startService(null, intent, intent.getType(), - asForeground, SHELL_PACKAGE_NAME, null, mUserId); + asForeground, SHELL_PACKAGE_NAME, mUserId); if (cn == null) { err.println("Error: Not found; no service started."); return -1; @@ -742,9 +742,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println("Broadcasting: " + intent); pw.flush(); Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle(); - mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null, - requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false, - mUserId); + mInterface.broadcastIntent(null, intent, null, receiver, 0, null, null, requiredPermissions, + android.app.AppOpsManager.OP_NONE, bundle, true, false, mUserId); receiver.waitForFinish(); return 0; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 145f91bdb0b3..789f7199948d 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -798,7 +798,6 @@ class AppErrors { boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; - final String packageName; final int userId; synchronized (mService) { final ProcessRecord proc = data.proc; @@ -807,7 +806,6 @@ class AppErrors { Slog.e(TAG, "handleShowAppErrorUi: proc is null"); return; } - packageName = proc.info.packageName; userId = proc.userId; if (proc.getDialogController().hasCrashDialogs()) { Slog.e(TAG, "App already has crash dialog: " + proc); diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java index 578db6f9dba1..1ec8db0aeec9 100644 --- a/services/core/java/com/android/server/am/BroadcastFilter.java +++ b/services/core/java/com/android/server/am/BroadcastFilter.java @@ -27,7 +27,6 @@ final class BroadcastFilter extends IntentFilter { // Back-pointer to the list this filter is in. final ReceiverList receiverList; final String packageName; - final String featureId; final String requiredPermission; final int owningUid; final int owningUserId; @@ -35,12 +34,11 @@ final class BroadcastFilter extends IntentFilter { final boolean visibleToInstantApp; BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList, - String _packageName, String _featureId, String _requiredPermission, int _owningUid, int _userId, + String _packageName, String _requiredPermission, int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp) { super(_filter); receiverList = _receiverList; packageName = _packageName; - featureId = _featureId; requiredPermission = _requiredPermission; owningUid = _owningUid; owningUserId = _userId; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 26ef7073487d..6697b5ab7c85 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -648,10 +648,10 @@ public final class BroadcastQueue { skip = true; } else { final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission); + // TODO moltmann: Set featureId from caller if (opCode != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, - r.callerPackage, r.callerFeatureId, "") - != AppOpsManager.MODE_ALLOWED) { + && mService.mAppOpsService.noteOperation(opCode, r.callingUid, + r.callerPackage, null, false, "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" @@ -681,9 +681,10 @@ public final class BroadcastQueue { break; } int appOp = AppOpsManager.permissionToOpCode(requiredPermission); + // TODO moltmann: Set featureId from caller if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp - && mService.getAppOpsManager().noteOpNoThrow(appOp, - filter.receiverList.uid, filter.packageName, filter.featureId, "") + && mService.mAppOpsService.noteOperation(appOp, + filter.receiverList.uid, filter.packageName, null, false, "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: receiving " + r.intent.toString() @@ -713,9 +714,10 @@ public final class BroadcastQueue { skip = true; } } + // TODO moltmann: Set featureId from caller if (!skip && r.appOp != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(r.appOp, - filter.receiverList.uid, filter.packageName, filter.featureId, "") + && mService.mAppOpsService.noteOperation(r.appOp, + filter.receiverList.uid, filter.packageName, null, false, "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: receiving " + r.intent.toString() @@ -861,8 +863,7 @@ public final class BroadcastQueue { if (callerForeground && receiverRecord.intent.getComponent() != null) { IIntentSender target = mService.mPendingIntentController.getIntentSender( ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage, - receiverRecord.callerFeatureId, receiverRecord.callingUid, - receiverRecord.userId, null, null, 0, + receiverRecord.callingUid, receiverRecord.userId, null, null, 0, new Intent[]{receiverRecord.intent}, new String[]{receiverRecord.intent.resolveType(mService.mContext .getContentResolver())}, @@ -1370,9 +1371,10 @@ public final class BroadcastQueue { skip = true; } else if (!skip && info.activityInfo.permission != null) { final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission); + // TODO moltmann: Set featureId from caller if (opCode != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, r.callerPackage, - r.callerFeatureId, "") != AppOpsManager.MODE_ALLOWED) { + && mService.mAppOpsService.noteOperation(opCode, r.callingUid, r.callerPackage, + null, false, "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" @@ -1408,10 +1410,11 @@ public final class BroadcastQueue { break; } int appOp = AppOpsManager.permissionToOpCode(requiredPermission); + // TODO moltmann: Set featureId from caller if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp - && mService.getAppOpsManager().noteOpNoThrow(appOp, - info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, - null /* default featureId */, "") + && mService.mAppOpsService.noteOperation(appOp, + info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, null, + false, "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: receiving " + r.intent + " to " @@ -1425,10 +1428,11 @@ public final class BroadcastQueue { } } } + // TODO moltmann: Set featureId from caller if (!skip && r.appOp != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(r.appOp, - info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, - null /* default featureId */, "") + && mService.mAppOpsService.noteOperation(r.appOp, + info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, null, false, + "") != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: receiving " + r.intent + " to " diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 8ef67f97e8d4..f2638861d39b 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -16,7 +16,6 @@ package com.android.server.am; -import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.content.ComponentName; @@ -52,7 +51,6 @@ final class BroadcastRecord extends Binder { final ComponentName targetComp; // original component name set on the intent final ProcessRecord callerApp; // process that sent this final String callerPackage; // who sent this - final @Nullable String callerFeatureId; // which feature in the package sent this final int callingPid; // the pid of who sent this final int callingUid; // the uid of who sent this final boolean callerInstantApp; // caller is an Instant App? @@ -235,8 +233,7 @@ final class BroadcastRecord extends Binder { BroadcastRecord(BroadcastQueue _queue, Intent _intent, ProcessRecord _callerApp, String _callerPackage, - @Nullable String _callerFeatureId, int _callingPid, int _callingUid, - boolean _callerInstantApp, String _resolvedType, + int _callingPid, int _callingUid, boolean _callerInstantApp, String _resolvedType, String[] _requiredPermissions, int _appOp, BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId, @@ -249,7 +246,6 @@ final class BroadcastRecord extends Binder { targetComp = _intent.getComponent(); callerApp = _callerApp; callerPackage = _callerPackage; - callerFeatureId = _callerFeatureId; callingPid = _callingPid; callingUid = _callingUid; callerInstantApp = _callerInstantApp; @@ -284,7 +280,6 @@ final class BroadcastRecord extends Binder { callerApp = from.callerApp; callerPackage = from.callerPackage; - callerFeatureId = from.callerFeatureId; callingPid = from.callingPid; callingUid = from.callingUid; callerInstantApp = from.callerInstantApp; @@ -348,8 +343,8 @@ final class BroadcastRecord extends Binder { } // build a new BroadcastRecord around that single-target list - BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage, - callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, + BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId, allowBackgroundActivityStarts, timeoutExempt); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index d047a3ca993c..eec68dc7353e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -946,6 +946,15 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP, + pid, + name, + unfrozenDuration); + } } } @@ -994,6 +1003,16 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write( + FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP, + pid, + name, + frozenDuration); + } } } } diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index eacf08889b1d..df76713d58a6 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -23,7 +23,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManagerInternal; import android.app.AppGlobals; @@ -89,9 +88,9 @@ public class PendingIntentController { } } - public PendingIntentRecord getIntentSender(int type, String packageName, - @Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho, - int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) { + public PendingIntentRecord getIntentSender(int type, String packageName, int callingUid, + int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, + String[] resolvedTypes, int flags, Bundle bOptions) { synchronized (mLock) { if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid); @@ -110,8 +109,8 @@ public class PendingIntentController { flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT); - PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId, - token, resultWho, requestCode, intents, resolvedTypes, flags, + PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, token, + resultWho, requestCode, intents, resolvedTypes, flags, SafeActivityOptions.fromBundle(bOptions), userId); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index d54d2d7d2056..3ba2210504d6 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -17,16 +17,14 @@ package com.android.server.am; import static android.app.ActivityManager.START_SUCCESS; - import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.PendingIntent; -import android.content.IIntentReceiver; import android.content.IIntentSender; +import android.content.IIntentReceiver; +import android.app.PendingIntent; import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -74,7 +72,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { final static class Key { final int type; final String packageName; - final String featureId; final IBinder activity; final String who; final int requestCode; @@ -89,11 +86,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { private static final int ODD_PRIME_NUMBER = 37; - Key(int _t, String _p, @Nullable String _featureId, IBinder _a, String _w, + Key(int _t, String _p, IBinder _a, String _w, int _r, Intent[] _i, String[] _it, int _f, SafeActivityOptions _o, int _userId) { type = _t; packageName = _p; - featureId = _featureId; activity = _a; who = _w; requestCode = _r; @@ -144,9 +140,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { if (!Objects.equals(packageName, other.packageName)) { return false; } - if (!Objects.equals(featureId, other.featureId)) { - return false; - } if (activity != other.activity) { return false; } @@ -182,8 +175,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } public String toString() { - return "Key{" + typeName() - + " pkg=" + packageName + (featureId != null ? "/" + featureId : "") + return "Key{" + typeName() + " pkg=" + packageName + " intent=" + (requestIntent != null ? requestIntent.toShortString(false, true, false, false) : "<null>") @@ -411,20 +403,19 @@ public final class PendingIntentRecord extends IIntentSender.Stub { if (key.allIntents != null && key.allIntents.length > 1) { res = controller.mAtmInternal.startActivitiesInPackage( - uid, callingPid, callingUid, key.packageName, key.featureId, - allIntents, allResolvedTypes, resultTo, mergedOptions, userId, + uid, callingPid, callingUid, key.packageName, allIntents, + allResolvedTypes, resultTo, mergedOptions, userId, false /* validateIncomingUser */, this /* originatingPendingIntent */, mAllowBgActivityStartsForActivitySender.contains(whitelistToken)); } else { - res = controller.mAtmInternal.startActivityInPackage(uid, callingPid, - callingUid, key.packageName, key.featureId, finalIntent, + res = controller.mAtmInternal.startActivityInPackage( + uid, callingPid, callingUid, key.packageName, finalIntent, resolvedType, resultTo, resultWho, requestCode, 0, mergedOptions, userId, null, "PendingIntentRecord", false /* validateIncomingUser */, this /* originatingPendingIntent */, - mAllowBgActivityStartsForActivitySender.contains( - whitelistToken)); + mAllowBgActivityStartsForActivitySender.contains(whitelistToken)); } } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startActivity intent", e); @@ -439,12 +430,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // If a completion callback has been requested, require // that the broadcast be delivered synchronously int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName, - key.featureId, uid, callingUid, callingPid, finalIntent, - resolvedType, finishedReceiver, code, null, null, - requiredPermission, options, (finishedReceiver != null), false, - userId, + uid, callingUid, callingPid, finalIntent, resolvedType, + finishedReceiver, code, null, null, requiredPermission, options, + (finishedReceiver != null), false, userId, mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken) - || allowTrampoline); + || allowTrampoline); if (sent == ActivityManager.BROADCAST_SUCCESS) { sendFinish = false; } @@ -457,7 +447,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { try { controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType, key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE, - key.packageName, key.featureId, userId, + key.packageName, userId, mAllowBgActivityStartsForServiceSender.contains(whitelistToken) || allowTrampoline); } catch (RuntimeException e) { @@ -506,7 +496,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("uid="); pw.print(uid); pw.print(" packageName="); pw.print(key.packageName); - pw.print(" featureId="); pw.print(key.featureId); pw.print(" type="); pw.print(key.typeName()); pw.print(" flags=0x"); pw.println(Integer.toHexString(key.flags)); if (key.activity != null || key.who != null) { @@ -556,10 +545,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); sb.append(key.packageName); - if (key.featureId != null) { - sb.append('/'); - sb.append(key.featureId); - } sb.append(' '); sb.append(key.typeName()); if (whitelistDuration != null) { diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java index 747e8a89f8e7..beb0e4741c36 100644 --- a/services/core/java/com/android/server/am/PreBootBroadcaster.java +++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java @@ -108,8 +108,8 @@ public abstract class PreBootBroadcaster extends IIntentReceiver.Stub { mIntent.setComponent(componentName); synchronized (mService) { - mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null, - null, AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID, + mService.broadcastIntentLocked(null, null, mIntent, null, this, 0, null, null, null, + AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId); } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 2b4d15eda9f0..e63da9b1e80b 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -371,6 +371,15 @@ class ProcessRecord implements WindowProcessListener { } } pw.println("}"); + if (processInfo != null) { + pw.print(prefix); pw.println("processInfo:"); + if (processInfo.deniedPermissions != null) { + for (int i = 0; i < processInfo.deniedPermissions.size(); i++) { + pw.print(prefix); pw.print(" deny: "); + pw.println(processInfo.deniedPermissions.valueAt(i)); + } + } + } pw.print(prefix); pw.print("mRequiredAbi="); pw.print(mRequiredAbi); pw.print(" instructionSet="); pw.println(instructionSet); if (info.className != null) { @@ -1901,8 +1910,7 @@ class ProcessRecord implements WindowProcessListener { mWaitDialog = null; } - void forAllDialogs(List<? extends BaseErrorDialog> dialogs, - Consumer<BaseErrorDialog> c) { + void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) { for (int i = dialogs.size() - 1; i >= 0; i--) { c.accept(dialogs.get(i)); } @@ -1911,42 +1919,72 @@ class ProcessRecord implements WindowProcessListener { void showCrashDialogs(AppErrorDialog.Data data) { List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */); mCrashDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mCrashDialogs.add(new AppErrorDialog(c, mService, data)); } - mService.mUiHandler.post(() -> mCrashDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<AppErrorDialog> dialogs; + synchronized (mService) { + dialogs = mCrashDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showAnrDialogs(AppNotRespondingDialog.Data data) { List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */); mAnrDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data)); } - mService.mUiHandler.post(() -> mAnrDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<AppNotRespondingDialog> dialogs; + synchronized (mService) { + dialogs = mAnrDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showViolationDialogs(AppErrorResult res) { List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */); mViolationDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mViolationDialogs.add( new StrictModeViolationDialog(c, mService, res, ProcessRecord.this)); } - mService.mUiHandler.post(() -> mViolationDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<StrictModeViolationDialog> dialogs; + synchronized (mService) { + dialogs = mViolationDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showDebugWaitingDialogs() { List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */); final Context c = contexts.get(0); mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this); - mService.mUiHandler.post(() -> mWaitDialog.show()); + + mService.mUiHandler.post(() -> { + Dialog dialog; + synchronized (mService) { + dialog = mWaitDialog; + } + if (dialog != null) { + dialog.show(); + } + }); } /** diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index e575e10807bc..b2d0441bb2f8 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -414,6 +414,7 @@ class UserController implements Handler.Callback { Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT + | Intent.FLAG_RECEIVER_OFFLOAD | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mInjector.broadcastIntent(intent, null, resultTo, 0, null, null, new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, @@ -2447,10 +2448,10 @@ class UserController implements Handler.Callback { int realCallingPid, @UserIdInt int userId) { // TODO b/64165549 Verify that mLock is not held before calling AMS methods synchronized (mService) { - return mService.broadcastIntentLocked(null, null, null, intent, resolvedType, - resultTo, resultCode, resultData, resultExtras, requiredPermissions, appOp, - bOptions, ordered, sticky, callingPid, callingUid, realCallingUid, - realCallingPid, userId); + return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo, + resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions, + ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, + userId); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 1f998c377c7b..ef1bc835dea5 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -702,6 +702,10 @@ public class AudioDeviceInventory { delay = 0; } mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); + if (state == BluetoothHearingAid.STATE_CONNECTED) { + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE, + "HEARING_AID set to CONNECTED"); + } return delay; } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 07fc9b7a7e5f..26c94c5ab978 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricManager.Authenticators; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.UserSwitchObserver; @@ -83,6 +84,25 @@ public class BiometricService extends SystemService { static final String TAG = "BiometricService"; private static final boolean DEBUG = true; + private static final int BIOMETRIC_NO_HARDWARE = 0; + private static final int BIOMETRIC_OK = 1; + private static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 2; + private static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 3; + private static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 4; + private static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 5; + private static final int BIOMETRIC_NOT_ENROLLED = 6; + private static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 7; + + @IntDef({BIOMETRIC_NO_HARDWARE, + BIOMETRIC_OK, + BIOMETRIC_DISABLED_BY_DEVICE_POLICY, + BIOMETRIC_INSUFFICIENT_STRENGTH, + BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE, + BIOMETRIC_HARDWARE_NOT_DETECTED, + BIOMETRIC_NOT_ENROLLED, + BIOMETRIC_NOT_ENABLED_FOR_APPS}) + @interface BiometricStatus {} + private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; private static final int MSG_ON_AUTHENTICATION_REJECTED = 3; private static final int MSG_ON_ERROR = 4; @@ -206,7 +226,7 @@ public class BiometricService extends SystemService { } boolean isAllowDeviceCredential() { - return Utils.isDeviceCredentialAllowed(mBundle); + return Utils.isCredentialRequested(mBundle); } } @@ -372,16 +392,20 @@ public class BiometricService extends SystemService { * strength. * @return a bitfield, see {@link Authenticators} */ - public int getActualStrength() { + int getActualStrength() { return OEMStrength | updatedStrength; } + boolean isDowngraded() { + return OEMStrength != updatedStrength; + } + /** * Stores the updated strength, which takes effect whenever {@link #getActualStrength()} * is checked. * @param newStrength */ - public void updateStrength(int newStrength) { + void updateStrength(int newStrength) { String log = "updateStrength: Before(" + toString() + ")"; updatedStrength = newStrength; log += " After(" + toString() + ")"; @@ -1007,6 +1031,79 @@ public class BiometricService extends SystemService { return isBiometricDisabled; } + private static int biometricStatusToBiometricConstant(@BiometricStatus int status) { + switch (status) { + case BIOMETRIC_NO_HARDWARE: + return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + case BIOMETRIC_OK: + return BiometricConstants.BIOMETRIC_SUCCESS; + case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + case BIOMETRIC_INSUFFICIENT_STRENGTH: + return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE: + return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + case BIOMETRIC_HARDWARE_NOT_DETECTED: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + case BIOMETRIC_NOT_ENROLLED: + return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS; + case BIOMETRIC_NOT_ENABLED_FOR_APPS: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + default: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + } + } + + /** + * Returns the status of the authenticator, with errors returned in a specific priority order. + * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned + * if it has enrollments, and is enabled for apps. + * + * We should only return the modality if the authenticator should be exposed. e.g. + * BIOMETRIC_NOT_ENROLLED_FOR_APPS should not expose the authenticator's type. + * + * @return A Pair with `first` being modality, and `second` being @BiometricStatus + */ + private Pair<Integer, Integer> getStatusForBiometricAuthenticator( + AuthenticatorWrapper authenticator, int userId, String opPackageName, + boolean checkDevicePolicyManager, int requestedStrength) { + if (checkDevicePolicyManager) { + if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { + return new Pair<>(TYPE_NONE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY); + } + } + + final boolean wasStrongEnough = + Utils.isAtLeastStrength(authenticator.OEMStrength, requestedStrength); + final boolean isStrongEnough = + Utils.isAtLeastStrength(authenticator.getActualStrength(), requestedStrength); + + if (wasStrongEnough && !isStrongEnough) { + return new Pair<>(authenticator.modality, + BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE); + } else if (!wasStrongEnough) { + return new Pair<>(TYPE_NONE, BIOMETRIC_INSUFFICIENT_STRENGTH); + } + + try { + if (!authenticator.impl.isHardwareDetected(opPackageName)) { + return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED); + } + + if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { + return new Pair<>(authenticator.modality, BIOMETRIC_NOT_ENROLLED); + } + } catch (RemoteException e) { + return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED); + } + + if (!isEnabledForApp(authenticator.modality, userId)) { + return new Pair<>(TYPE_NONE, BIOMETRIC_NOT_ENABLED_FOR_APPS); + } + + return new Pair<>(authenticator.modality, BIOMETRIC_OK); + } + /** * Depending on the requested authentication (credential/biometric combination), checks their * availability. @@ -1029,10 +1126,9 @@ public class BiometricService extends SystemService { private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle, String opPackageName, boolean checkDevicePolicyManager) throws RemoteException { - final boolean biometricRequested = Utils.isBiometricAllowed(bundle); - final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle); + final boolean biometricRequested = Utils.isBiometricRequested(bundle); + final boolean credentialRequested = Utils.isCredentialRequested(bundle); - final boolean biometricOk; final boolean credentialOk = mTrustManager.isDeviceSecure(userId); // Assuming that biometric authenticators are listed in priority-order, the rest of this @@ -1041,96 +1137,56 @@ public class BiometricService extends SystemService { // the correct error. Error strings that are modality-specific should also respect the // priority-order. - // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled. - boolean disabledByDevicePolicy = false; - boolean hasSufficientStrength = false; - boolean isHardwareDetected = false; - boolean hasTemplatesEnrolled = false; - boolean enabledForApps = false; + int firstBiometricModality = TYPE_NONE; + @BiometricStatus int firstBiometricStatus = BIOMETRIC_NO_HARDWARE; + + int biometricModality = TYPE_NONE; + @BiometricStatus int biometricStatus = BIOMETRIC_NO_HARDWARE; - int modality = TYPE_NONE; - int firstHwAvailable = TYPE_NONE; for (AuthenticatorWrapper authenticator : mAuthenticators) { - final int actualStrength = authenticator.getActualStrength(); final int requestedStrength = Utils.getPublicBiometricStrength(bundle); + Pair<Integer, Integer> result = getStatusForBiometricAuthenticator( + authenticator, userId, opPackageName, checkDevicePolicyManager, + requestedStrength); - if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { - disabledByDevicePolicy = true; - continue; - } - disabledByDevicePolicy = false; - - if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) { - continue; - } - hasSufficientStrength = true; + biometricStatus = result.second; - if (!authenticator.impl.isHardwareDetected(opPackageName)) { - continue; - } - isHardwareDetected = true; + Slog.d(TAG, "Authenticator ID: " + authenticator.id + + " Modality: " + authenticator.modality + + " ReportedModality: " + result.first + + " Status: " + biometricStatus); - if (firstHwAvailable == TYPE_NONE) { - // Store the first one since we want to return the error in correct - // priority order. - firstHwAvailable = authenticator.modality; + if (firstBiometricModality == TYPE_NONE) { + firstBiometricModality = result.first; + firstBiometricStatus = biometricStatus; } - if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { - continue; - } - hasTemplatesEnrolled = true; - - if (!isEnabledForApp(authenticator.modality, userId)) { - continue; + if (biometricStatus == BIOMETRIC_OK) { + biometricModality = result.first; + break; } - enabledForApps = true; - modality = authenticator.modality; - break; } - biometricOk = !disabledByDevicePolicy - && hasSufficientStrength && isHardwareDetected - && hasTemplatesEnrolled && enabledForApps; - - Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId - + " checkDevicePolicyManager=" + checkDevicePolicyManager - + " isHardwareDetected=" + isHardwareDetected - + " hasTemplatesEnrolled=" + hasTemplatesEnrolled - + " enabledForApps=" + enabledForApps - + " disabledByDevicePolicy=" + disabledByDevicePolicy); - if (biometricRequested && credentialRequested) { - if (credentialOk || biometricOk) { - if (!biometricOk) { + if (credentialOk || biometricStatus == BIOMETRIC_OK) { + if (biometricStatus != BIOMETRIC_OK) { // If there's a problem with biometrics but device credential is // allowed, only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); } - return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); + return new Pair<>(biometricModality, BiometricConstants.BIOMETRIC_SUCCESS); } else { - return new Pair<>(firstHwAvailable, + return new Pair<>(firstBiometricModality, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } } else if (biometricRequested) { - if (biometricOk) { - return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); - } else if (disabledByDevicePolicy) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); - } else if (!hasSufficientStrength) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); - } else if (!isHardwareDetected) { - return new Pair<>(firstHwAvailable, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); - } else if (!hasTemplatesEnrolled) { - return new Pair<>(firstHwAvailable, - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); - } else if (!enabledForApps) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + if (biometricStatus == BIOMETRIC_OK) { + return new Pair<>(biometricModality, + biometricStatusToBiometricConstant(biometricStatus)); } else { - Slog.e(TAG, "Unexpected case"); - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(firstBiometricModality, + biometricStatusToBiometricConstant(firstBiometricStatus)); } } else if (credentialRequested) { if (credentialOk) { @@ -1403,16 +1459,14 @@ public class BiometricService extends SystemService { return; } - if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - if (message == null) { - Slog.w(TAG, "Ignoring null message: " + acquiredInfo); - return; - } - try { - mStatusBarService.onBiometricHelp(message); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } + if (message == null) { + Slog.w(TAG, "Ignoring null message: " + acquiredInfo); + return; + } + try { + mStatusBarService.onBiometricHelp(message); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); } } diff --git a/services/core/java/com/android/server/biometrics/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/LoggableMonitor.java index c03c77f41f57..c50ab175199d 100644 --- a/services/core/java/com/android/server/biometrics/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/LoggableMonitor.java @@ -20,6 +20,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; @@ -69,8 +70,12 @@ public abstract class LoggableMonitor { protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, int targetUserId) { - if (statsModality() == BiometricsProtoEnums.MODALITY_FACE) { - if (acquiredInfo == FaceManager.FACE_ACQUIRED_START) { + + final boolean isFace = statsModality() == BiometricsProtoEnums.MODALITY_FACE; + final boolean isFingerprint = statsModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT; + if (isFace || isFingerprint) { + if ((isFingerprint && acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_START) + || (isFace && acquiredInfo == FaceManager.FACE_ACQUIRED_START)) { mFirstAcquireTimeMs = System.currentTimeMillis(); } } else if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { @@ -124,7 +129,7 @@ public abstract class LoggableMonitor { error, vendorCode, Utils.isDebugEnabled(context, targetUserId), - latency); + sanitizeLatency(latency)); } protected final void logOnAuthenticated(Context context, boolean authenticated, @@ -165,7 +170,7 @@ public abstract class LoggableMonitor { statsClient(), requireConfirmation, authState, - latency, + sanitizeLatency(latency), Utils.isDebugEnabled(context, targetUserId)); } @@ -183,8 +188,16 @@ public abstract class LoggableMonitor { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, statsModality(), targetUserId, - latency, + sanitizeLatency(latency), enrollSuccessful); } + private long sanitizeLatency(long latency) { + if (latency < 0) { + Slog.w(TAG, "found a negative latency : " + latency); + return -1; + } + return latency; + } + } diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 2d4ab6308a8b..8f3fd36f411b 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -80,7 +80,7 @@ public class Utils { * @param authenticators composed of one or more values from {@link Authenticators} * @return true if device credential is allowed. */ - public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) { + public static boolean isCredentialRequested(@Authenticators.Types int authenticators) { return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0; } @@ -88,8 +88,8 @@ public class Utils { * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} * @return true if device credential is allowed. */ - public static boolean isDeviceCredentialAllowed(Bundle bundle) { - return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + public static boolean isCredentialRequested(Bundle bundle) { + return isCredentialRequested(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); } /** @@ -120,7 +120,7 @@ public class Utils { * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} * @return true if biometric authentication is allowed. */ - public static boolean isBiometricAllowed(Bundle bundle) { + public static boolean isBiometricRequested(Bundle bundle) { return getPublicBiometricStrength(bundle) != 0; } @@ -169,7 +169,7 @@ public class Utils { // should be set. final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH; if (biometricBits == Authenticators.EMPTY_SET - && isDeviceCredentialAllowed(authenticators)) { + && isCredentialRequested(authenticators)) { return true; } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) { return true; @@ -209,6 +209,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; break; + case BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 57d1867b3aca..0a6198863b00 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -41,7 +41,7 @@ import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; -import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintClientActiveCallback; @@ -601,6 +601,11 @@ public class FingerprintService extends BiometricServiceBase { @Override public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) { + onAcquired_2_2(deviceId, acquiredInfo, vendorCode); + } + + @Override + public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { mHandler.post(() -> { FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode); }); diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING new file mode 100644 index 000000000000..0c30c790c5dd --- /dev/null +++ b/services/core/java/com/android/server/compat/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + // Unit tests + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.compat" + } + ] + }, + // Tests for the TestRule + { + "name": "PlatformCompatGating" + }, + // CTS tests + { + "name": "CtsAppCompatHostTestCases#" + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java index b0e2e6432c77..6a5e963064bb 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -16,9 +16,7 @@ package com.android.server.incremental; -import static android.content.pm.InstallationFile.FILE_TYPE_OBB; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; -import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB; import android.annotation.IntDef; import android.annotation.NonNull; @@ -183,10 +181,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { session = packageInstaller.openSession(sessionId); for (int i = 0; i < numFiles; i++) { InstallationFile file = installationFiles.get(i); - final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB - : LOCATION_DATA_APP; - session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), - null); + session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(), + file.getMetadata(), file.getSignature()); } session.commit(localReceiver.getIntentSender()); final Intent result = localReceiver.getResult(); @@ -304,7 +300,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { } final byte[] metadata = String.valueOf(index).getBytes( StandardCharsets.UTF_8); - fileList.add(new InstallationFile(name, size, metadata)); + fileList.add( + new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null)); break; } default: diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java index c3899638f40f..94e6708c3038 100644 --- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java +++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java @@ -29,13 +29,14 @@ public final class ComponentBitSize { public static final int KEY_BITS = 4; public static final int OPERATOR_BITS = 3; public static final int CONNECTOR_BITS = 2; - public static final int SEPARATOR_BITS = 2; + public static final int SEPARATOR_BITS = 3; public static final int VALUE_SIZE_BITS = 8; public static final int IS_HASHED_BITS = 1; public static final int ATOMIC_FORMULA_START = 0; public static final int COMPOUND_FORMULA_START = 1; public static final int COMPOUND_FORMULA_END = 2; + public static final int INSTALLER_ALLOWED_BY_MANIFEST_START = 3; public static final int DEFAULT_FORMAT_VERSION = 1; public static final int SIGNAL_BIT = 1; diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index 4b8efafcb6b0..11e8d91dde12 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMU import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; @@ -35,6 +36,7 @@ import static com.android.server.integrity.parser.BinaryFileOperations.getString import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; +import android.content.integrity.InstallerAllowedByManifestFormula; import android.content.integrity.IntegrityFormula; import android.content.integrity.Rule; @@ -140,6 +142,8 @@ public class RuleBinaryParser implements RuleParser { return parseCompoundFormula(bitInputStream); case COMPOUND_FORMULA_END: return null; + case INSTALLER_ALLOWED_BY_MANIFEST_START: + return new InstallerAllowedByManifestFormula(); default: throw new IllegalArgumentException( String.format("Unknown formula separator: %s", separator)); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index 00e054596cd7..8ba5870aef0f 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; @@ -36,6 +37,7 @@ import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAG import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; +import android.content.integrity.InstallerAllowedByManifestFormula; import android.content.integrity.IntegrityFormula; import android.content.integrity.IntegrityUtils; import android.content.integrity.Rule; @@ -202,6 +204,8 @@ public class RuleBinarySerializer implements RuleSerializer { serializeAtomicFormula((AtomicFormula) formula, bitOutputStream); } else if (formula instanceof CompoundFormula) { serializeCompoundFormula((CompoundFormula) formula, bitOutputStream); + } else if (formula instanceof InstallerAllowedByManifestFormula) { + bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START); } else { throw new IllegalArgumentException( String.format("Invalid formula type: %s", formula.getClass())); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java index 6f7d172aabcc..e7235591fb9b 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java @@ -84,6 +84,7 @@ class RuleIndexingDetailsIdentifier { return getIndexingDetailsForStringAtomicFormula( (AtomicFormula.StringAtomicFormula) formula); case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG: + case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG: case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG: // Package name and app certificate related formulas are string atomic formulas. return new RuleIndexingDetails(NOT_INDEXED); diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java index 55e427f70180..6ba5f079264c 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java @@ -18,6 +18,7 @@ package com.android.server.location; import android.content.Context; import android.location.GnssMeasurementsEvent; +import android.location.GnssRequest; import android.location.IGnssMeasurementsListener; import android.os.Handler; import android.os.RemoteException; @@ -33,14 +34,14 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public abstract class GnssMeasurementsProvider - extends RemoteListenerHelper<IGnssMeasurementsListener> { - private static final String TAG = "GnssMeasurementsProvider"; + extends RemoteListenerHelper<GnssRequest, IGnssMeasurementsListener> { + private static final String TAG = "GnssMeasProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final GnssMeasurementProviderNative mNative; - private boolean mIsCollectionStarted; - private boolean mEnableFullTracking; + private boolean mStartedCollection; + private boolean mStartedFullTracking; protected GnssMeasurementsProvider(Context context, Handler handler) { this(context, handler, new GnssMeasurementProviderNative()); @@ -57,8 +58,8 @@ public abstract class GnssMeasurementsProvider if (DEBUG) { Log.d(TAG, "resumeIfStarted"); } - if (mIsCollectionStarted) { - mNative.startMeasurementCollection(mEnableFullTracking); + if (mStartedCollection) { + mNative.startMeasurementCollection(mStartedFullTracking); } } @@ -67,18 +68,35 @@ public abstract class GnssMeasurementsProvider return mNative.isMeasurementSupported(); } - @Override - protected int registerWithService() { + private boolean getMergedFullTracking() { int devOptions = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); - int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(), + int enableFullTracking = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0); - boolean enableFullTracking = (devOptions == 1 /* Developer Mode enabled */) - && (fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */); + boolean enableFullTrackingBySetting = (devOptions == 1 /* Developer Mode enabled */) + && (enableFullTracking == 1 /* Raw Measurements Full Tracking enabled */); + if (enableFullTrackingBySetting) { + return true; + } + + synchronized (mListenerMap) { + for (IdentifiedListener identifiedListener : mListenerMap.values()) { + GnssRequest request = identifiedListener.getRequest(); + if (request != null && request.isFullTracking()) { + return true; + } + } + } + return false; + } + + @Override + protected int registerWithService() { + boolean enableFullTracking = getMergedFullTracking(); boolean result = mNative.startMeasurementCollection(enableFullTracking); if (result) { - mIsCollectionStarted = true; - mEnableFullTracking = enableFullTracking; + mStartedCollection = true; + mStartedFullTracking = enableFullTracking; return RemoteListenerHelper.RESULT_SUCCESS; } else { return RemoteListenerHelper.RESULT_INTERNAL_ERROR; @@ -89,7 +107,7 @@ public abstract class GnssMeasurementsProvider protected void unregisterFromService() { boolean stopped = mNative.stopMeasurementCollection(); if (stopped) { - mIsCollectionStarted = false; + mStartedCollection = false; } } diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java index 983d1daa2367..fb901e86f494 100644 --- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java @@ -33,7 +33,7 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public abstract class GnssNavigationMessageProvider - extends RemoteListenerHelper<IGnssNavigationMessageListener> { + extends RemoteListenerHelper<Void, IGnssNavigationMessageListener> { private static final String TAG = "GnssNavigationMessageProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java index eaf63c87d93f..1d16c03fd6f7 100644 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java @@ -24,7 +24,8 @@ import android.util.Log; /** * Implementation of a handler for {@link IGnssStatusListener}. */ -public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> { +public abstract class GnssStatusListenerHelper extends + RemoteListenerHelper<Void, IGnssStatusListener> { private static final String TAG = "GnssStatusListenerHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index 015227394ffe..11f068533a6d 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -17,6 +17,7 @@ package com.android.server.location; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.os.Handler; @@ -32,9 +33,10 @@ import java.util.Objects; /** * A helper class that handles operations in remote listeners. * + * @param <TRequest> the type of request. * @param <TListener> the type of GNSS data listener. */ -public abstract class RemoteListenerHelper<TListener extends IInterface> { +public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> { protected static final int RESULT_SUCCESS = 0; protected static final int RESULT_NOT_AVAILABLE = 1; @@ -47,7 +49,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { protected final Handler mHandler; private final String mTag; - private final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>(); + protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>(); protected final Context mContext; protected final AppOpsManager mAppOps; @@ -75,7 +77,8 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { /** * Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}. */ - public void addListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + public void addListener(@Nullable TRequest request, @NonNull TListener listener, + CallerIdentity callerIdentity) { Objects.requireNonNull(listener, "Attempted to register a 'null' listener."); IBinder binder = listener.asBinder(); synchronized (mListenerMap) { @@ -84,7 +87,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { return; } - IdentifiedListener identifiedListener = new IdentifiedListener(listener, + IdentifiedListener identifiedListener = new IdentifiedListener(request, listener, callerIdentity); mListenerMap.put(binder, identifiedListener); @@ -257,14 +260,22 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { return RESULT_SUCCESS; } - private class IdentifiedListener { + protected class IdentifiedListener { + @Nullable private final TRequest mRequest; private final TListener mListener; private final CallerIdentity mCallerIdentity; - private IdentifiedListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener, + CallerIdentity callerIdentity) { mListener = listener; + mRequest = request; mCallerIdentity = callerIdentity; } + + @Nullable + protected TRequest getRequest() { + return mRequest; + } } private class HandlerRunnable implements Runnable { diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index 9163490fa777..6d1d1f901eb3 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -135,6 +135,24 @@ public class SettingsHelper { } /** + * Set location enabled for a user. + */ + public void setLocationEnabled(boolean enabled, int userId) { + long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_ON + : Settings.Secure.LOCATION_MODE_OFF, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Add a listener for changes to the location enabled setting. Callbacks occur on an unspecified * thread. */ diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING index 2e21fa67a967..214d2f3f5646 100644 --- a/services/core/java/com/android/server/location/TEST_MAPPING +++ b/services/core/java/com/android/server/location/TEST_MAPPING @@ -1,10 +1,19 @@ { "presubmit": [ { + "name": "CtsLocationFineTestCases" + }, + { "name": "CtsLocationCoarseTestCases" }, { "name": "CtsLocationNoneTestCases" + }, + { + "name": "FrameworksMockingServicesTests", + "options": [{ + "include-filter": "com.android.server.location" + }] } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 2bab9fa67eb0..02c1bddd8c00 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -25,6 +25,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -96,15 +97,15 @@ public class GnssManagerService { private final IGpsGeofenceHardware mGpsGeofenceProxy; @GuardedBy("mGnssMeasurementsListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>> + private final ArrayMap<IBinder, LinkedListener<GnssRequest, IGnssMeasurementsListener>> mGnssMeasurementsListeners = new ArrayMap<>(); @GuardedBy("mGnssNavigationMessageListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>> + private final ArrayMap<IBinder, LinkedListener<Void, IGnssNavigationMessageListener>> mGnssNavigationMessageListeners = new ArrayMap<>(); @GuardedBy("mGnssStatusListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>> + private final ArrayMap<IBinder, LinkedListener<Void, IGnssStatusListener>> mGnssStatusListeners = new ArrayMap<>(); @GuardedBy("this") @@ -118,7 +119,8 @@ public class GnssManagerService { @Nullable private IBatchedLocationCallback mGnssBatchingCallback; @GuardedBy("mGnssBatchingLock") - @Nullable private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback; + @Nullable + private LinkedListener<Void, IBatchedLocationCallback> mGnssBatchingDeathCallback; @GuardedBy("mGnssBatchingLock") private boolean mGnssBatchingInProgress = false; @@ -272,6 +274,7 @@ public class GnssManagerService { mGnssBatchingCallback = callback; mGnssBatchingDeathCallback = new LinkedListener<>( + /* request= */ null, callback, "BatchedLocationCallback", callerIdentity, @@ -356,36 +359,39 @@ public class GnssManagerService { } } - private <TListener extends IInterface> void updateListenersOnForegroundChangedLocked( - Map<IBinder, ? extends LinkedListenerBase> gnssDataListeners, - RemoteListenerHelper<TListener> gnssDataProvider, + private <TRequest, TListener extends IInterface> void updateListenersOnForegroundChangedLocked( + Map<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners, + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, Function<IBinder, TListener> mapBinderToListener, int uid, boolean foreground) { - for (Map.Entry<IBinder, ? extends LinkedListenerBase> entry : + for (Map.Entry<IBinder, LinkedListener<TRequest, TListener>> entry : gnssDataListeners.entrySet()) { - LinkedListenerBase linkedListener = entry.getValue(); + LinkedListener<TRequest, TListener> linkedListener = entry.getValue(); CallerIdentity callerIdentity = linkedListener.getCallerIdentity(); + TRequest request = linkedListener.getRequest(); if (callerIdentity.mUid != uid) { continue; } TListener listener = mapBinderToListener.apply(entry.getKey()); if (foreground || isThrottlingExempt(callerIdentity)) { - gnssDataProvider.addListener(listener, callerIdentity); + gnssDataProvider.addListener(request, listener, callerIdentity); } else { gnssDataProvider.removeListener(listener); } } } - private <TListener extends IInterface> boolean addGnssDataListenerLocked( + private <TListener extends IInterface, TRequest> boolean addGnssDataListenerLocked( + @Nullable TRequest request, TListener listener, String packageName, @Nullable String featureId, @NonNull String listenerIdentifier, - RemoteListenerHelper<TListener> gnssDataProvider, - ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners, + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, + ArrayMap<IBinder, + LinkedListener<TRequest, TListener>> gnssDataListeners, Consumer<TListener> binderDeathCallback) { mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); @@ -395,7 +401,7 @@ public class GnssManagerService { CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName, featureId, listenerIdentifier); - LinkedListener<TListener> linkedListener = new LinkedListener<>(listener, + LinkedListener<TRequest, TListener> linkedListener = new LinkedListener<>(request, listener, listenerIdentifier, callerIdentity, binderDeathCallback); IBinder binder = listener.asBinder(); if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) { @@ -419,15 +425,15 @@ public class GnssManagerService { } if (mAppForegroundHelper.isAppForeground(callerIdentity.mUid) || isThrottlingExempt(callerIdentity)) { - gnssDataProvider.addListener(listener, callerIdentity); + gnssDataProvider.addListener(request, listener, callerIdentity); } return true; } - private <TListener extends IInterface> void removeGnssDataListenerLocked( + private <TRequest, TListener extends IInterface> void removeGnssDataListenerLocked( TListener listener, - RemoteListenerHelper<TListener> gnssDataProvider, - ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners) { + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, + ArrayMap<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners) { if (gnssDataProvider == null) { Log.e( TAG, @@ -437,7 +443,7 @@ public class GnssManagerService { } IBinder binder = listener.asBinder(); - LinkedListener<TListener> linkedListener = + LinkedListener<TRequest, TListener> linkedListener = gnssDataListeners.remove(binder); if (linkedListener == null) { return; @@ -467,6 +473,7 @@ public class GnssManagerService { @Nullable String featureId) { synchronized (mGnssStatusListeners) { return addGnssDataListenerLocked( + /* request= */ null, listener, packageName, featureId, @@ -489,11 +496,17 @@ public class GnssManagerService { /** * Adds a GNSS measurements listener. */ - public boolean addGnssMeasurementsListener( - IGnssMeasurementsListener listener, String packageName, @Nullable String featureId, + public boolean addGnssMeasurementsListener(@Nullable GnssRequest request, + IGnssMeasurementsListener listener, String packageName, + @Nullable String featureId, @NonNull String listenerIdentifier) { + if (request != null && request.isFullTracking()) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE, + null); + } synchronized (mGnssMeasurementsListeners) { return addGnssDataListenerLocked( + request, listener, packageName, featureId, @@ -538,6 +551,7 @@ public class GnssManagerService { @Nullable String featureId, @NonNull String listenerIdentifier) { synchronized (mGnssNavigationMessageListeners) { return addGnssDataListenerLocked( + /* request= */ null, listener, packageName, featureId, diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index b726e571cf0c..ac49fa293fe7 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -590,8 +590,8 @@ public class SyntheticPasswordManager { throw new IllegalStateException("Failed to create new SID for user", e); } if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { - Slog.e(TAG, "Fail to create new SID for user " + userId); - return; + throw new IllegalStateException("Fail to create new SID for user " + userId + + " response: " + response.getResponseCode()); } saveSyntheticPasswordHandle(response.getPayload(), userId); } diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java new file mode 100644 index 000000000000..58c2707a1f19 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -0,0 +1,360 @@ +/* + * Copyright 2020 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.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Holds the media button receiver, and also provides helper methods around it. + */ +final class MediaButtonReceiverHolder { + public static final int COMPONENT_TYPE_INVALID = 0; + public static final int COMPONENT_TYPE_BROADCAST = 1; + public static final int COMPONENT_TYPE_ACTIVITY = 2; + public static final int COMPONENT_TYPE_SERVICE = 3; + + @IntDef(value = { + COMPONENT_TYPE_INVALID, + COMPONENT_TYPE_BROADCAST, + COMPONENT_TYPE_ACTIVITY, + COMPONENT_TYPE_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentType {} + + private static final String TAG = "PendingIntentHolder"; + private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT; + private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + + private final int mUserId; + private final PendingIntent mPendingIntent; + private final ComponentName mComponentName; + private final String mPackageName; + @ComponentType + private final int mComponentType; + + /** + * Unflatten from string which is previously flattened string via flattenToString(). + * <p> + * It's used to store and restore media button receiver across the boot, by keeping the intent's + * component name to the persistent storage. + * + * @param mediaButtonReceiverInfo previously flattened string via flattenToString() + * @return new instance if the string was valid. {@code null} otherwise. + */ + public static MediaButtonReceiverHolder unflattenFromString( + Context context, String mediaButtonReceiverInfo) { + if (TextUtils.isEmpty(mediaButtonReceiverInfo)) { + return null; + } + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(tokens[0]); + int userId = Integer.parseInt(tokens[1]); + // Guess component type if the OS version is updated from the older version. + int componentType = (tokens.length == 3) + ? Integer.parseInt(tokens[2]) + : getComponentType(context, componentName); + return new MediaButtonReceiverHolder(userId, null, componentName, componentType); + } + + /** + * Creates a new instance. + * + * @param context context + * @param userId userId + * @param pendingIntent pending intent + * @return Can be {@code null} if pending intent was null. + */ + public static MediaButtonReceiverHolder create(Context context, int userId, + PendingIntent pendingIntent) { + if (pendingIntent == null) { + return null; + } + ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getComponent() : null; + if (componentName != null) { + // Explicit intent, where component name is in the PendingIntent. + return new MediaButtonReceiverHolder(userId, pendingIntent, componentName, + getComponentType(context, componentName)); + } + + // Implicit intent, where component name isn't in the PendingIntent. Try resolve. + PackageManager pm = context.getPackageManager(); + Intent intent = pendingIntent.getIntent(); + if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE); + } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent)) + != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST); + } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY); + } + + // Failed to resolve target component for the pending intent. It's unlikely to be usable. + // However, the pending intent would be still used, just to follow the legacy behavior. + Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent); + String packageName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getPackage() : null; + return new MediaButtonReceiverHolder(userId, pendingIntent, + packageName != null ? packageName : ""); + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, + ComponentName componentName, @ComponentType int componentType) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = componentName; + mPackageName = componentName.getPackageName(); + mComponentType = componentType; + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = null; + mPackageName = packageName; + mComponentType = COMPONENT_TYPE_INVALID; + } + + /** + * @return the user id + */ + public int getUserId() { + return mUserId; + } + + /** + * @return package name that the media button receiver would be sent to. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Sends the media key event to the media button receiver. + * <p> + * This prioritizes using use pending intent for sending media key event. + * + * @param context context to be used to call PendingIntent#send + * @param keyEvent keyEvent to send + * @param resultCode result code to be used to call PendingIntent#send + * Ignored if there's no valid pending intent. + * @param onFinishedListener callback to be used to get result of PendingIntent#send. + * Ignored if there's no valid pending intent. + * @param handler handler to be used to call onFinishedListener + * Ignored if there's no valid pending intent. + * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler) + */ + public boolean send(Context context, KeyEvent keyEvent, String callingPackageName, + int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + // TODO: Find a way to also send PID/UID in secure way. + mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); + + if (mPendingIntent != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " + + mPendingIntent); + } + try { + mPendingIntent.send( + context, resultCode, mediaButtonIntent, onFinishedListener, handler); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e); + return false; + } + } else if (mComponentName != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + + mComponentName + ", type=" + mComponentType); + } + mediaButtonIntent.setComponent(mComponentName); + UserHandle userHandle = UserHandle.of(mUserId); + try { + switch (mComponentType) { + case COMPONENT_TYPE_ACTIVITY: + context.startActivityAsUser(mediaButtonIntent, userHandle); + break; + case COMPONENT_TYPE_SERVICE: + context.startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + context.sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + mComponentName + ", type=" + mComponentType, e); + return false; + } + } else { + // Leave log, just in case. + Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set"); + return false; + } + return true; + } + + + @Override + public String toString() { + if (mPendingIntent != null) { + return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}"; + } + return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}"; + } + + /** + * @return flattened string. Can be empty string if the MBR is created with implicit intent. + */ + public String flattenToString() { + if (mComponentName == null) { + // We don't know which component would receive the key event. + return ""; + } + return String.join(COMPONENT_NAME_USER_ID_DELIM, + mComponentName.toString(), + String.valueOf(mUserId), + String.valueOf(mComponentType)); + } + + /** + * Gets the type of the component + * + * @param context context + * @param componentName component name + * @return A component type + */ + @ComponentType + private static int getComponentType(Context context, ComponentName componentName) { + if (componentName == null) { + return COMPONENT_TYPE_INVALID; + } + PackageManager pm = context.getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (PackageManager.NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (PackageManager.NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } + + private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + // - GET_SERVICES: Return service + return createComponentName(pm.resolveService(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES)); + } + + private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent( + PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + return (resolveInfos != null && !resolveInfos.isEmpty()) + ? createComponentName(resolveInfos.get(0)) : null; + } + + private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // Filter apps regardless of the phone's locked/unlocked state. + // - MATCH_DEFAULT_ONLY: + // Implicit intent receiver should be set as default. Only needed for activity. + // - GET_ACTIVITIES: Return activity + return createComponentName(pm.resolveActivity(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.GET_ACTIVITIES)); + } + + private static ComponentName createComponentName(ResolveInfo resolveInfo) { + if (resolveInfo == null) { + return null; + } + ComponentInfo componentInfo; + // Code borrowed from ResolveInfo#getComponentInfo(). + if (resolveInfo.activityInfo != null) { + componentInfo = resolveInfo.activityInfo; + } else if (resolveInfo.serviceInfo != null) { + componentInfo = resolveInfo.serviceInfo; + } else { + // We're not interested in content provider. + return null; + } + // Code borrowed from ComponentInfo#getComponentName(). + try { + return new ComponentName(componentInfo.packageName, componentInfo.name); + } catch (IllegalArgumentException | NullPointerException e) { + // This may be happen if resolveActivity() end up with matching multiple activities. + // see PackageManager#resolveActivity(). + return null; + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index d08fb71d3dee..dd536ecd19fa 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -411,6 +411,12 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mActiveConnection.dispose(); mActiveConnection = null; setAndNotifyProviderState(null); + synchronized (mLock) { + for (RoutingSessionInfo sessionInfo : mSessionInfos) { + mCallback.onSessionReleased(this, sessionInfo); + } + mSessionInfos.clear(); + } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 7bcbcd4a3d09..9f47b349d3fc 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -127,7 +127,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR new ArrayList<>(); private long mFlags; - private PendingIntent mMediaButtonReceiver; + private MediaButtonReceiverHolder mMediaButtonReceiverHolder; private PendingIntent mLaunchIntent; // TransportPerformer fields @@ -220,8 +220,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR * * @return The pending intent set by the app or null. */ - public PendingIntent getMediaButtonReceiver() { - return mMediaButtonReceiver; + public MediaButtonReceiverHolder getMediaButtonReceiver() { + return mMediaButtonReceiverHolder; } /** @@ -471,7 +471,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR + ", userId=" + mUserId); pw.println(indent + "package=" + mPackageName); pw.println(indent + "launchIntent=" + mLaunchIntent); - pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver); + pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder); pw.println(indent + "active=" + mIsActive); pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); @@ -833,12 +833,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { - if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) { - return; - } - mMediaButtonReceiver = pi; final long token = Binder.clearCallingIdentity(); try { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) + != 0) { + return; + } + mMediaButtonReceiverHolder = + MediaButtonReceiverHolder.create(mContext, mUserId, pi); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); @@ -1529,5 +1531,4 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR msg.sendToTarget(); } } - } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 88b884efce83..7ffac062b7f5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -18,22 +18,17 @@ package com.android.server.media; import static android.os.UserHandle.USER_ALL; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; -import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -99,7 +94,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Leave log for key event always. - private static final boolean DEBUG_KEY_EVENT = true; + static final boolean DEBUG_KEY_EVENT = true; private static final int WAKELOCK_TIMEOUT = 5000; private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; @@ -760,12 +755,6 @@ public class MediaSessionService extends SystemService implements Monitor { * <p>The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { - public static final int COMPONENT_TYPE_INVALID = 0; - public static final int COMPONENT_TYPE_BROADCAST = 1; - public static final int COMPONENT_TYPE_ACTIVITY = 2; - public static final int COMPONENT_TYPE_SERVICE = 3; - private static final String COMPONENT_NAME_USER_ID_DELIM = ","; - private final int mFullUserId; private final MediaSessionStack mPriorityStack; private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord> @@ -774,10 +763,7 @@ public class MediaSessionService extends SystemService implements Monitor { mOnMediaKeyEventSessionChangedListeners = new HashMap<>(); private final SparseIntArray mUidToSessionCount = new SparseIntArray(); - private PendingIntent mLastMediaButtonReceiver; - private ComponentName mRestoredMediaButtonReceiver; - private int mRestoredMediaButtonReceiverComponentType; - private int mRestoredMediaButtonReceiverUserId; + private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; private int mOnVolumeKeyLongPressListenerUid; @@ -794,21 +780,9 @@ public class MediaSessionService extends SystemService implements Monitor { // Restore the remembered media button receiver before the boot. String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiverInfo == null) { - return; - } - String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { - return; - } - mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); - mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); - if (tokens.length == 3) { - mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); - } else { - mRestoredMediaButtonReceiverComponentType = - getComponentType(mRestoredMediaButtonReceiver); - } + mLastMediaButtonReceiverHolder = + MediaButtonReceiverHolder.unflattenFromString( + mContext, mediaButtonReceiverInfo); } public void destroySessionsForUserLocked(int userId) { @@ -892,10 +866,7 @@ public class MediaSessionService extends SystemService implements Monitor { : mOnMediaKeyEventSessionChangedListeners.values()) { pw.println(indent + " from " + getCallingPackageName(cr.uid)); } - pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiverComponentType: " - + mRestoredMediaButtonReceiverComponentType); + pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder); mPriorityStack.dump(pw, indent); } @@ -924,25 +895,12 @@ public class MediaSessionService extends SystemService implements Monitor { return; } MediaSessionRecord sessionRecord = (MediaSessionRecord) record; - PendingIntent receiver = sessionRecord.getMediaButtonReceiver(); - mLastMediaButtonReceiver = receiver; - mRestoredMediaButtonReceiver = null; - mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID; - - String mediaButtonReceiverInfo = ""; - if (receiver != null) { - ComponentName component = receiver.getIntent().getComponent(); - if (component != null - && record.getPackageName().equals(component.getPackageName())) { - String componentName = component.flattenToString(); - int componentType = getComponentType(component); - mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, - componentName, String.valueOf(record.getUserId()), - String.valueOf(componentType)); - } - } + mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver(); + String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null) + ? "" : mLastMediaButtonReceiverHolder.flattenToString(); Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, + Settings.System.MEDIA_BUTTON_RECEIVER, + mediaButtonReceiverInfo, mFullUserId); } @@ -958,15 +916,9 @@ public class MediaSessionService extends SystemService implements Monitor { } else { // TODO(jaewan): Implement } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mLastMediaButtonReceiver - .getIntent().getComponent().getPackageName(), - null); - } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(), - null); + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { + String packageName = mLastMediaButtonReceiverHolder.getPackageName(); + callback.onMediaKeyEventSessionChanged(packageName, null); } } catch (RemoteException e) { Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e); @@ -985,35 +937,6 @@ public class MediaSessionService extends SystemService implements Monitor { ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } - private int getComponentType(@Nullable ComponentName componentName) { - if (componentName == null) { - return COMPONENT_TYPE_INVALID; - } - PackageManager pm = mContext.getPackageManager(); - try { - ActivityInfo activityInfo = pm.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_ACTIVITIES); - if (activityInfo != null) { - return COMPONENT_TYPE_ACTIVITY; - } - } catch (NameNotFoundException e) { - } - try { - ServiceInfo serviceInfo = pm.getServiceInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_SERVICES); - if (serviceInfo != null) { - return COMPONENT_TYPE_SERVICE; - } - } catch (NameNotFoundException e) { - } - // Pick legacy behavior for BroadcastReceiver or unknown. - return COMPONENT_TYPE_BROADCAST; - } - final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient { public final IOnMediaKeyEventDispatchedListener callback; public final int uid; @@ -2166,79 +2089,31 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { Log.w(TAG, "Failed to send callback", e); } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null - || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { if (needWakeLock) { mKeyEventReceiver.acquireWakeLockLocked(); } - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - // TODO: Find a way to also send PID/UID in secure way. - String callerPackageName = + String callingPackageName = (asSystemService) ? mContext.getPackageName() : packageName; - mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName); - try { - if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver; - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent - + " to the last known PendingIntent " + receiver); - } - receiver.send(mContext, - needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, mHandler); - ComponentName componentName = mCurrentFullUserRecord - .mLastMediaButtonReceiver.getIntent().getComponent(); - if (componentName != null) { - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - componentName.getPackageName(), null); - } - } - } else { - ComponentName receiver = - mCurrentFullUserRecord.mRestoredMediaButtonReceiver; - int componentType = mCurrentFullUserRecord - .mRestoredMediaButtonReceiverComponentType; - UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId); - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver + ", type=" + componentType); - } - mediaButtonIntent.setComponent(receiver); + + MediaButtonReceiverHolder mediaButtonReceiverHolder = + mCurrentFullUserRecord.mLastMediaButtonReceiverHolder; + + boolean sent = mediaButtonReceiverHolder.send( + mContext, keyEvent, callingPackageName, + needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, + mHandler); + if (sent) { + String pkgName = mediaButtonReceiverHolder.getPackageName(); + for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr + : mCurrentFullUserRecord + .mOnMediaKeyEventDispatchedListeners.values()) { try { - switch (componentType) { - case FullUserRecord.COMPONENT_TYPE_ACTIVITY: - mContext.startActivityAsUser(mediaButtonIntent, userHandle); - break; - case FullUserRecord.COMPONENT_TYPE_SERVICE: - mContext.startForegroundServiceAsUser(mediaButtonIntent, - userHandle); - break; - default: - // Legacy behavior for other cases. - mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle); - } - } catch (Exception e) { - Log.w(TAG, "Error sending media button to the restored intent " - + receiver + ", type=" + componentType, e); - } - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - receiver.getPackageName(), null); + cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null); + } catch (RemoteException e) { + Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e); } } - } catch (CanceledException e) { - Log.i(TAG, "Error sending key event to media button receiver " - + mCurrentFullUserRecord.mLastMediaButtonReceiver, e); - } catch (RemoteException e) { - Log.w(TAG, "Failed to send callback", e); } } } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index d75eb6d65b03..3c31f6a7f0d7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -913,9 +913,9 @@ public final class OverlayManagerService extends SystemService { } try { - ActivityManager.getService().broadcastIntentWithFeature(null, null, intent, - null, null, 0, null, null, null, android.app.AppOpsManager.OP_NONE, - null, false, false, userId); + ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, + null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, + userId); } catch (RemoteException e) { // Intentionally left empty. } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 8c13b5bf8519..d629b547992b 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,7 +33,6 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedComponent; import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo; import android.content.pm.parsing.ComponentParseUtils.ParsedProvider; import android.content.pm.parsing.ComponentParseUtils.ParsedService; -import android.net.Uri; import android.os.Binder; import android.os.Process; import android.os.Trace; @@ -87,10 +86,10 @@ public class AppsFilter { private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>(); /** - * A mapping from the set of App IDs that query others via intent to the list - * of packages that the intents resolve to. + * A mapping from the set of App IDs that query others via component match to the list + * of packages that the they resolve to. */ - private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>(); + private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** * A set of App IDs that are always queryable by any package, regardless of their manifest @@ -206,16 +205,19 @@ public class AppsFilter { } /** Returns true if the querying package may query for the potential target package */ - private static boolean canQueryViaIntent(AndroidPackage querying, + private static boolean canQueryViaComponents(AndroidPackage querying, AndroidPackage potentialTarget) { - if (querying.getQueriesIntents() == null) { - return false; - } - for (Intent intent : querying.getQueriesIntents()) { - if (matches(intent, potentialTarget)) { - return true; + if (querying.getQueriesIntents() != null) { + for (Intent intent : querying.getQueriesIntents()) { + if (matchesIntentFilters(intent, potentialTarget)) { + return true; + } } } + if (querying.getQueriesProviders() != null + && matchesProviders(querying.getQueriesProviders(), potentialTarget)) { + return true; + } return false; } @@ -238,24 +240,25 @@ public class AppsFilter { return false; } - private static boolean matches(Intent intent, AndroidPackage potentialTarget) { + private static boolean matchesProviders( + Set<String> queriesAuthorities, AndroidPackage potentialTarget) { for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { ParsedProvider provider = potentialTarget.getProviders().get(p); if (!provider.isExported()) { continue; } - final Uri data = intent.getData(); - if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null - || provider.getAuthority() == null) { - continue; - } - StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false); + StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", + false); while (authorities.hasMoreElements()) { - if (Objects.equals(authorities.nextElement(), data.getAuthority())) { + if (queriesAuthorities.contains(authorities.nextToken())) { return true; } } } + return false; + } + + private static boolean matchesIntentFilters(Intent intent, AndroidPackage potentialTarget) { for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) { ParsedService service = potentialTarget.getServices().get(s); if (!service.exported) { @@ -368,8 +371,8 @@ public class AppsFilter { final AndroidPackage existingPkg = existingSetting.pkg; // let's evaluate the ability of already added packages to see this new package if (!newIsForceQueryable) { - if (canQueryViaIntent(existingPkg, newPkg)) { - mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId); + if (canQueryViaComponents(existingPkg, newPkg)) { + mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); } if (canQueryViaPackage(existingPkg, newPkg) || canQueryAsInstaller(existingSetting, newPkg)) { @@ -378,8 +381,8 @@ public class AppsFilter { } // now we'll evaluate our new package's ability to see existing packages if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaIntent(newPkg, existingPkg)) { - mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId); + if (canQueryViaComponents(newPkg, existingPkg)) { + mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); } if (canQueryViaPackage(newPkg, existingPkg) || canQueryAsInstaller(newPkgSetting, existingPkg)) { @@ -427,9 +430,9 @@ public class AppsFilter { } } - mQueriesViaIntent.remove(setting.appId); - for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) { - mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId); + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); } mQueriesViaPackage.remove(setting.appId); for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { @@ -594,10 +597,10 @@ public class AppsFilter { Trace.endSection(); } try { - Trace.beginSection("mQueriesViaIntent"); - if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + Trace.beginSection("mQueriesViaComponent"); + if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + log(callingSetting, targetPkgSetting, "queries component"); } return false; } @@ -732,7 +735,7 @@ public class AppsFilter { pw.println(" queries via package name:"); dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages); pw.println(" queries via intent:"); - dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, " ", expandPackages); + dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages); pw.println(" queryable via interaction:"); for (int user : users) { pw.append(" User ").append(Integer.toString(user)).println(":"); diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index cd4485e67064..74d2efeceb63 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -44,7 +44,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.stats.devicepolicy.DevicePolicyEnums; @@ -52,8 +54,10 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; +import com.android.server.appop.AppOpsService; import com.android.server.wm.ActivityTaskManagerInternal; import java.util.ArrayList; @@ -65,7 +69,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { private Context mContext; private Injector mInjector; - private AppOpsManager mAppOpsManager; + private AppOpsService mAppOpsService; public CrossProfileAppsServiceImpl(Context context) { this(context, new InjectorImpl(context)); @@ -96,7 +100,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { public void startActivityAsUser( IApplicationThread caller, String callingPackage, - String callingFeatureId, ComponentName component, @UserIdInt int userId, boolean launchMainActivity) throws RemoteException { @@ -162,7 +165,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { launchIntent.setPackage(null); launchIntent.setComponent(component); mInjector.getActivityTaskManagerInternal().startActivityAsUser( - caller, callingPackage, callingFeatureId, launchIntent, + caller, callingPackage, launchIntent, launchMainActivity ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle() : null, @@ -173,7 +176,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { public void startActivityAsUserByIntent( IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, @UserIdInt int userId) throws RemoteException { Objects.requireNonNull(callingPackage); @@ -210,8 +212,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { verifyActivityCanHandleIntent(launchIntent, callingUid, userId); - mInjector.getActivityTaskManagerInternal().startActivityAsUser(caller, callingPackage, - callingFeatureId, launchIntent, /* options= */ null, userId); + mInjector.getActivityTaskManagerInternal().startActivityAsUser( + caller, callingPackage, launchIntent, /* options= */ null, userId); } @Override @@ -543,11 +545,12 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { permission, uid, /* owningUid= */-1, /* exported= */ true); } - private AppOpsManager getAppOpsManager() { - if (mAppOpsManager == null) { - mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private AppOpsService getAppOpsService() { + if (mAppOpsService == null) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b); } - return mAppOpsManager; + return mAppOpsService; } private static class InjectorImpl implements Injector { diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 0b0f13929b2a..8333ae5cbbad 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -222,7 +222,6 @@ public abstract class InstantAppResolver { sanitizedIntent, failureIntent, requestObj.callingPackage, - requestObj.callingFeatureId, requestObj.verificationBundle, requestObj.resolvedType, requestObj.userId, @@ -267,7 +266,6 @@ public abstract class InstantAppResolver { @NonNull Intent sanitizedIntent, @Nullable Intent failureIntent, @NonNull String callingPackage, - @Nullable String callingFeatureId, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, @@ -310,10 +308,9 @@ public abstract class InstantAppResolver { onFailureIntent = failureIntent; } final IIntentSender failureIntentTarget = ActivityManager.getService() - .getIntentSenderWithFeature( + .getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - callingFeatureId, null /*token*/, null /*resultWho*/, - 1 /*requestCode*/, + null /*token*/, null /*resultWho*/, 1 /*requestCode*/, new Intent[] { onFailureIntent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT @@ -331,10 +328,9 @@ public abstract class InstantAppResolver { successIntent.setLaunchToken(token); try { final IIntentSender successIntentTarget = ActivityManager.getService() - .getIntentSenderWithFeature( + .getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - callingFeatureId, null /*token*/, null /*resultWho*/, - 0 /*requestCode*/, + null /*token*/, null /*resultWho*/, 0 /*requestCode*/, new Intent[] { successIntent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index da07365a5f35..e9f84fd419d6 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,8 +16,8 @@ package com.android.server.pm; +import android.Manifest.permission; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -306,6 +306,10 @@ public class LauncherAppsService extends SystemService { final int callingUserId = injectCallingUserId(); if (targetUserId == callingUserId) return true; + if (mContext.checkCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + return true; + } long ident = injectClearCallingIdentity(); try { @@ -783,9 +787,8 @@ public class LauncherAppsService extends SystemService { } @Override - public boolean startShortcut(String callingPackage, String packageName, String featureId, - String shortcutId, Rect sourceBounds, Bundle startActivityOptions, - int targetUserId) { + public boolean startShortcut(String callingPackage, String packageName, String shortcutId, + Rect sourceBounds, Bundle startActivityOptions, int targetUserId) { verifyCallingPackage(callingPackage); if (!canAccessProfile(targetUserId, "Cannot start activity")) { return false; @@ -809,16 +812,15 @@ public class LauncherAppsService extends SystemService { intents[0].setSourceBounds(sourceBounds); return startShortcutIntentsAsPublisher( - intents, packageName, featureId, startActivityOptions, targetUserId); + intents, packageName, startActivityOptions, targetUserId); } private boolean startShortcutIntentsAsPublisher(@NonNull Intent[] intents, - @NonNull String publisherPackage, @Nullable String publishedFeatureId, - Bundle startActivityOptions, int userId) { + @NonNull String publisherPackage, Bundle startActivityOptions, int userId) { final int code; try { code = mActivityTaskManagerInternal.startActivitiesAsPackage(publisherPackage, - publishedFeatureId, userId, intents, startActivityOptions); + userId, intents, startActivityOptions); if (ActivityManager.isStartResultSuccessful(code)) { return true; // Success } else { @@ -873,8 +875,8 @@ public class LauncherAppsService extends SystemService { @Override public void startSessionDetailsActivityAsUser(IApplicationThread caller, - String callingPackage, String callingFeatureId, SessionInfo sessionInfo, - Rect sourceBounds, Bundle opts, UserHandle userHandle) throws RemoteException { + String callingPackage, SessionInfo sessionInfo, Rect sourceBounds, + Bundle opts, UserHandle userHandle) throws RemoteException { int userId = userHandle.getIdentifier(); if (!canAccessProfile(userId, "Cannot start details activity")) { return; @@ -890,13 +892,13 @@ public class LauncherAppsService extends SystemService { .authority(callingPackage).build()); i.setSourceBounds(sourceBounds); - mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage, - callingFeatureId, i, opts, userId); + mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage, i, opts, + userId); } @Override public void startActivityAsUser(IApplicationThread caller, String callingPackage, - String callingFeatureId, ComponentName component, Rect sourceBounds, + ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) { return; @@ -950,12 +952,12 @@ public class LauncherAppsService extends SystemService { Binder.restoreCallingIdentity(ident); } mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage, - callingFeatureId, launchIntent, opts, user.getIdentifier()); + launchIntent, opts, user.getIdentifier()); } @Override public void showAppDetailsAsUser(IApplicationThread caller, - String callingPackage, String callingFeatureId, ComponentName component, + String callingPackage, ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { if (!canAccessProfile(user.getIdentifier(), "Cannot show app details")) { return; @@ -973,7 +975,7 @@ public class LauncherAppsService extends SystemService { Binder.restoreCallingIdentity(ident); } mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage, - callingFeatureId, intent, opts, user.getIdentifier()); + intent, opts, user.getIdentifier()); } /** Checks if user is a profile of or same as listeningUser. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 944280d6db88..97defcdd3bc7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -214,7 +214,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; - private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {}; + private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {}; private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; @@ -309,33 +309,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private int mParentSessionId; - static class FileInfo { - public final int location; - public final String name; - public final Long lengthBytes; - public final byte[] metadata; - public final byte[] signature; - - public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata, - byte[] signature) { - return new FileInfo(location, name, lengthBytes, metadata, signature); - } - - public static FileInfo removed(int location, String name) { - return new FileInfo(location, name, -1L, null, null); - } - - FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) { - this.location = location; - this.name = name; - this.lengthBytes = lengthBytes; - this.metadata = metadata; - this.signature = signature; - } - } - @GuardedBy("mLock") - private ArrayList<FileInfo> mFiles = new ArrayList<>(); + private ArrayList<InstallationFile> mFiles = new ArrayList<>(); @GuardedBy("mLock") private boolean mStagedSessionApplied; @@ -508,7 +483,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager, int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, - File stageDir, String stageCid, FileInfo[] files, boolean prepared, + File stageDir, String stageCid, InstallationFile[] files, boolean prepared, boolean committed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, @@ -539,7 +514,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.mParentSessionId = parentSessionId; if (files != null) { - for (FileInfo file : files) { + for (InstallationFile file : files) { mFiles.add(file); } } @@ -738,7 +713,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String[] result = new String[mFiles.size()]; for (int i = 0, size = mFiles.size(); i < size; ++i) { - result[i] = mFiles.get(i).name; + result[i] = mFiles.get(i).getName(); } return result; } @@ -2425,7 +2400,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature)); + mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature)); } } @@ -2443,7 +2418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("removeFile"); - mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name))); + mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null)); } } @@ -2461,17 +2436,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final List<InstallationFile> addedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sAddedFilter.accept(new File(this.stageDir, file.name))) { - addedFiles.add(new InstallationFile( - file.name, file.lengthBytes, file.metadata)); + for (InstallationFile file : mFiles) { + if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) { + addedFiles.add(file); } } final List<String> removedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sRemovedFilter.accept(new File(this.stageDir, file.name))) { - String name = file.name.substring( - 0, file.name.length() - REMOVE_MARKER_EXTENSION.length()); + for (InstallationFile file : mFiles) { + if (sRemovedFilter.accept(new File(this.stageDir, file.getName()))) { + String name = file.getName().substring( + 0, file.getName().length() - REMOVE_MARKER_EXTENSION.length()); removedFiles.add(name); } } @@ -2970,13 +2944,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); out.endTag(null, TAG_CHILD_SESSION); } - for (FileInfo fileInfo : mFiles) { + for (InstallationFile file : mFiles) { out.startTag(null, TAG_SESSION_FILE); - writeIntAttribute(out, ATTR_LOCATION, fileInfo.location); - writeStringAttribute(out, ATTR_NAME, fileInfo.name); - writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes); - writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata); - writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature); + writeIntAttribute(out, ATTR_LOCATION, file.getLocation()); + writeStringAttribute(out, ATTR_NAME, file.getName()); + writeLongAttribute(out, ATTR_LENGTH_BYTES, file.getLengthBytes()); + writeByteArrayAttribute(out, ATTR_METADATA, file.getMetadata()); + writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature()); out.endTag(null, TAG_SESSION_FILE); } } @@ -3088,7 +3062,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<String> grantedRuntimePermissions = new ArrayList<>(); List<String> whitelistedRestrictedPermissions = new ArrayList<>(); List<Integer> childSessionIds = new ArrayList<>(); - List<FileInfo> files = new ArrayList<>(); + List<InstallationFile> files = new ArrayList<>(); int outerDepth = in.getDepth(); int type; while ((type = in.next()) != XmlPullParser.END_DOCUMENT @@ -3107,7 +3081,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID)); } if (TAG_SESSION_FILE.equals(in.getName())) { - files.add(new FileInfo( + files.add(new InstallationFile( readIntAttribute(in, ATTR_LOCATION, 0), readStringAttribute(in, ATTR_NAME), readLongAttribute(in, ATTR_LENGTH_BYTES, -1), @@ -3135,16 +3109,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY; } - FileInfo[] fileInfosArray = null; + InstallationFile[] fileArray = null; if (!files.isEmpty()) { - fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY); + fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY); } InstallSource installSource = InstallSource.create(installInitiatingPackageName, installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, - installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, + installSource, params, createdMillis, stageDir, stageCid, fileArray, prepared, committed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2c85d067a3ca..88c048cea267 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6298,11 +6298,10 @@ public class PackageManagerService extends IPackageManager.Stub private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, - @Nullable String callingFeatureId, boolean isRequesterInstantApp, - Bundle verificationBundle, int userId) { + boolean isRequesterInstantApp, Bundle verificationBundle, int userId) { final Message msg = mHandler.obtainMessage(INSTANT_APP_RESOLUTION_PHASE_TWO, new InstantAppRequest(responseObj, origIntent, resolvedType, - callingPackage, callingFeatureId, isRequesterInstantApp, userId, verificationBundle, + callingPackage, isRequesterInstantApp, userId, verificationBundle, false /*resolveForStart*/, responseObj.hostDigestPrefixSecure, responseObj.token)); mHandler.sendMessage(msg); @@ -6988,10 +6987,10 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); String token = UUID.randomUUID().toString(); InstantAppDigest digest = InstantAppResolver.parseDigest(intent); - final InstantAppRequest requestObject = new InstantAppRequest(null /*responseObj*/, - intent /*origIntent*/, resolvedType, null /*callingPackage*/, - null /*callingFeatureId*/, isRequesterInstantApp, userId, - null /*verificationBundle*/, resolveForStart, + final InstantAppRequest requestObject = new InstantAppRequest( + null /*responseObj*/, intent /*origIntent*/, resolvedType, + null /*callingPackage*/, isRequesterInstantApp, + userId, null /*verificationBundle*/, resolveForStart, digest.getDigestPrefixSecure(), token); auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( mInstantAppResolverConnection, requestObject); @@ -12207,7 +12206,7 @@ public class PackageManagerService extends IPackageManager.Stub + intent.toShortString(false, true, false, false) + " " + intent.getExtras(), here); } - am.broadcastIntentWithFeature(null, null, intent, null, finishedReceiver, + am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, requiredPermissions, android.app.AppOpsManager.OP_NONE, null, finishedReceiver != null, false, id); } @@ -12369,9 +12368,8 @@ public class PackageManagerService extends IPackageManager.Stub lockedBcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED}; - am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null, - requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false, - userId); + am.broadcastIntent(null, lockedBcIntent, null, null, 0, null, null, requiredPermissions, + android.app.AppOpsManager.OP_NONE, null, false, false, userId); // Deliver BOOT_COMPLETED only if user is unlocked if (mUserManager.isUserUnlockingOrUnlocked(userId)) { @@ -12379,9 +12377,8 @@ public class PackageManagerService extends IPackageManager.Stub if (includeStopped) { bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } - am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null, - requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false, - userId); + am.broadcastIntent(null, bcIntent, null, null, 0, null, null, requiredPermissions, + android.app.AppOpsManager.OP_NONE, null, false, false, userId); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -18784,7 +18781,7 @@ public class PackageManagerService extends IPackageManager.Stub intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); try { - am.broadcastIntentWithFeature(null, null, intent, null, null, + am.broadcastIntent(null, intent, null, null, 0, null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); } catch (RemoteException e) { @@ -23428,10 +23425,9 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, - @Nullable String callingFeatureId, boolean isRequesterInstantApp, - Bundle verificationBundle, int userId) { - PackageManagerService.this.requestInstantAppResolutionPhaseTwo(responseObj, origIntent, - resolvedType, callingPackage, callingFeatureId, isRequesterInstantApp, + boolean isRequesterInstantApp, Bundle verificationBundle, int userId) { + PackageManagerService.this.requestInstantAppResolutionPhaseTwo( + responseObj, origIntent, resolvedType, callingPackage, isRequesterInstantApp, verificationBundle, userId); } @@ -24341,9 +24337,8 @@ public class PackageManagerService extends IPackageManager.Stub Manifest.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY, }; try { - am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null, - requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false, - UserHandle.USER_ALL); + am.broadcastIntent(null, intent, null, null, 0, null, null, requiredPermissions, + android.app.AppOpsManager.OP_NONE, null, false, false, UserHandle.USER_ALL); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cb9404397f3d..c69a62d406aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1163,7 +1163,7 @@ class PackageManagerShellCommand extends ShellCommand { final InstallParams params = makeInstallParams(); if (params.sessionParams.dataLoaderParams == null) { params.sessionParams.setDataLoaderParams( - PackageManagerShellCommandDataLoader.getDataLoaderParams(this)); + PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this)); } return doRunInstall(params); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index 5dca9e147e31..4170be4a913d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -54,7 +54,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { private static final String STDIN_PATH = "-"; - static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) { + private static String getDataLoaderParamsArgs(ShellCommand shellCommand) { int commandId; synchronized (sShellCommands) { // Clean up old references. @@ -78,8 +78,12 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { sShellCommands.put(commandId, new WeakReference<>(shellCommand)); } - final String args = SHELL_COMMAND_ID_PREFIX + commandId; - return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args); + return SHELL_COMMAND_ID_PREFIX + commandId; + } + + static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) { + return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), + getDataLoaderParamsArgs(shellCommand)); } private static int extractShellCommandId(String args) { @@ -133,17 +137,17 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { return false; } try { - for (InstallationFile fileInfo : addedFiles) { - String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8); + for (InstallationFile file : addedFiles) { + String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8); if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) { final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup( shellCommand.getInFileDescriptor()); - mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd); + mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd); } else { ParcelFileDescriptor incomingFd = null; try { incomingFd = shellCommand.openFileForSystem(filePath, "r"); - mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(), + mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(), incomingFd); } finally { IoUtils.closeQuietly(incomingFd); @@ -159,7 +163,8 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { } @Override - public DataLoaderService.DataLoader onCreateDataLoader() { + public DataLoaderService.DataLoader onCreateDataLoader( + @NonNull DataLoaderParams dataLoaderParams) { return new DataLoader(); } } diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 77bb48eadc41..4c40448085ff 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -89,7 +89,7 @@ class UserSystemPackageInstaller { * <ul> * <li> 0 - disable whitelist (install all system packages; no logging)</li> * <li> 1 - enforce (only install system packages if they are whitelisted)</li> - * <li> 2 - log (log when a non-whitelisted package is run)</li> + * <li> 2 - log (log non-whitelisted packages)</li> * <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li> * <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li> * <li> 16 - ignore OTAs (don't install system packages during OTAs)</li> diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4b2dc85c6667..ede04f3bbb14 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5195,8 +5195,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final Intent dock = createHomeDockIntent(); if (dock != null) { int result = ActivityTaskManager.getService() - .startActivityAsUser(null, mContext.getBasePackageName(), - mContext.getFeatureId(), dock, + .startActivityAsUser(null, mContext.getBasePackageName(), dock, dock.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, ActivityManager.START_FLAG_ONLY_IF_NEEDED, @@ -5207,8 +5206,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } int result = ActivityTaskManager.getService() - .startActivityAsUser(null, mContext.getBasePackageName(), - mContext.getFeatureId(), mHomeIntent, + .startActivityAsUser(null, mContext.getBasePackageName(), mHomeIntent, mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, ActivityManager.START_FLAG_ONLY_IF_NEEDED, diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index d7543805c2c6..a83c58d9c5f4 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -27,16 +27,22 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYST import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static java.lang.Integer.min; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.Build; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.compat.IPlatformCompat; /** * The behavior of soft restricted permissions is different for each permission. This class collects @@ -46,6 +52,27 @@ import android.os.UserHandle; * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy} */ public abstract class SoftRestrictedPermissionPolicy { + /** + * Enables scoped storage, with exceptions for apps that explicitly request legacy access, or + * apps that hold the {@code android.Manifest.permission#WRITE_MEDIA_STORAGE} permission. + * See https://developer.android.com/training/data-storage#scoped-storage for more information. + */ + @ChangeId + // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.P} + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.P) + static final long ENABLE_SCOPED_STORAGE = 144914977L; + + /** + * Enforces scoped storage for all apps, preventing individual apps from opting out. This change + * has precedence over {@code ENABLE_SCOPED_STORAGE}. + */ + @ChangeId + // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.Q}. + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q) + static final long REQUIRE_SCOPED_STORAGE = 131432978L; + + private static final String LOG_TAG = SoftRestrictedPermissionPolicy.class.getSimpleName(); + private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT @@ -60,41 +87,6 @@ public abstract class SoftRestrictedPermissionPolicy { }; /** - * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over - * what to set, always compute the combined targetSDK. - * - * @param context A context - * @param appInfo The app that is changed - * @param user The user the app belongs to - * - * @return The minimum targetSDK of all apps sharing the uid of the app - */ - private static int getMinimumTargetSDK(@NonNull Context context, - @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) { - PackageManager pm = context.getPackageManager(); - - int minimumTargetSDK = appInfo.targetSdkVersion; - - String[] uidPkgs = pm.getPackagesForUid(appInfo.uid); - if (uidPkgs != null) { - for (String uidPkg : uidPkgs) { - if (!uidPkg.equals(appInfo.packageName)) { - ApplicationInfo uidPkgInfo; - try { - uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user); - } catch (PackageManager.NameNotFoundException e) { - continue; - } - - minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion); - } - } - } - - return minimumTargetSDK; - } - - /** * Get the policy for a soft restricted permission. * * @param context A context to use @@ -114,27 +106,31 @@ public abstract class SoftRestrictedPermissionPolicy { case READ_EXTERNAL_STORAGE: { final boolean isWhiteListed; boolean shouldApplyRestriction; - final int targetSDK; final boolean hasRequestedLegacyExternalStorage; final boolean hasWriteMediaStorageGrantedForUid; + final boolean isScopedStorageEnabled; if (appInfo != null) { PackageManager pm = context.getPackageManager(); int flags = pm.getPermissionFlags(permission, appInfo.packageName, user); isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - targetSDK = getMinimumTargetSDK(context, appInfo, user); - shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0 - || targetSDK > Build.VERSION_CODES.Q; hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage( appInfo.uid, context); hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid( appInfo.uid, context); + final boolean isScopedStorageRequired = + isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE); + isScopedStorageEnabled = + isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE) + || isScopedStorageRequired; + shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0 + || isScopedStorageRequired; } else { isWhiteListed = false; shouldApplyRestriction = false; - targetSDK = 0; hasRequestedLegacyExternalStorage = false; hasWriteMediaStorageGrantedForUid = false; + isScopedStorageEnabled = false; } // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode @@ -144,7 +140,7 @@ public abstract class SoftRestrictedPermissionPolicy { return new SoftRestrictedPermissionPolicy() { @Override public boolean mayGrantPermission() { - return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; + return isWhiteListed || isScopedStorageEnabled; } @Override public int getExtraAppOpCode() { @@ -152,7 +148,7 @@ public abstract class SoftRestrictedPermissionPolicy { } @Override public boolean mayAllowExtraAppOp() { - return !shouldApplyRestriction && targetSDK <= Build.VERSION_CODES.Q + return !shouldApplyRestriction && (hasRequestedLegacyExternalStorage || hasWriteMediaStorageGrantedForUid); } @@ -164,22 +160,26 @@ public abstract class SoftRestrictedPermissionPolicy { } case WRITE_EXTERNAL_STORAGE: { final boolean isWhiteListed; - final int targetSDK; + final boolean isScopedStorageEnabled; if (appInfo != null) { final int flags = context.getPackageManager().getPermissionFlags(permission, appInfo.packageName, user); isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - targetSDK = getMinimumTargetSDK(context, appInfo, user); + final boolean isScopedStorageRequired = + isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE); + isScopedStorageEnabled = + isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE) + || isScopedStorageRequired; } else { isWhiteListed = false; - targetSDK = 0; + isScopedStorageEnabled = false; } return new SoftRestrictedPermissionPolicy() { @Override public boolean mayGrantPermission() { - return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; + return isWhiteListed || isScopedStorageEnabled; } }; } @@ -188,6 +188,62 @@ public abstract class SoftRestrictedPermissionPolicy { } } + /** + * Checks whether an AppCompat change is enabled for all packages sharing a UID with the + * provided application. + * + * @param context A context to use. + * @param appInfo The application for which to check whether the compat change is enabled. + * @param user The user the app belongs to. + * @param changeId A {@link android.compat.annotation.ChangeId} corresponding to the change. + * + * @return true if this change is enabled for all apps sharing the UID of the provided app, + * false otherwise. + */ + private static boolean isChangeEnabledForUid(@NonNull Context context, + @NonNull ApplicationInfo appInfo, @NonNull UserHandle user, long changeId) { + PackageManager pm = context.getPackageManager(); + + String[] uidPackages = pm.getPackagesForUid(appInfo.uid); + if (uidPackages != null) { + for (String uidPackage : uidPackages) { + ApplicationInfo uidPackageInfo; + try { + uidPackageInfo = pm.getApplicationInfoAsUser(uidPackage, 0, user); + } catch (PackageManager.NameNotFoundException e) { + continue; + } + if (!isChangeEnabled(uidPackageInfo, changeId)) { + // At least one package sharing this UID does not have this change enabled. + return false; + } + } + // All packages sharing this UID returned true for {@link #isChangeEnabled()}. + return true; + } else { + Log.w(LOG_TAG, "Check for change " + changeId + " for uid " + appInfo.uid + + " produced no packages. Defaulting to using the information for " + + appInfo.packageName + " only."); + return isChangeEnabled(appInfo, changeId); + } + } + + private static boolean isChangeEnabled(@NonNull ApplicationInfo appInfo, long changeId) { + IBinder binder = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(binder); + + final long callingId = Binder.clearCallingIdentity(); + + try { + return platformCompat.isChangeEnabled(changeId, appInfo); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Check for change " + changeId + " failed. Defaulting to enabled.", e); + return true; + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) { PackageManager packageManager = context.getPackageManager(); String[] packageNames = packageManager.getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java new file mode 100644 index 000000000000..cda00b404c0b --- /dev/null +++ b/services/core/java/com/android/server/power/PreRebootLogger.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 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.power; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Environment; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Provides utils to dump/wipe pre-reboot information. + */ +final class PreRebootLogger { + private static final String TAG = "PreRebootLogger"; + private static final String PREREBOOT_DIR = "prereboot"; + + private static final String[] BUFFERS_TO_DUMP = {"system"}; + private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"}; + + private static final Object sLock = new Object(); + + /** + * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if + * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise. + */ + static void log(Context context) { + log(context, getDumpDir()); + } + + @VisibleForTesting + static void log(Context context, @NonNull File dumpDir) { + if (Settings.Global.getInt( + context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) { + Slog.d(TAG, "Dumping pre-reboot information..."); + dump(dumpDir); + } else { + Slog.d(TAG, "Wiping pre-reboot information..."); + wipe(dumpDir); + } + } + + private static void dump(@NonNull File dumpDir) { + synchronized (sLock) { + for (String buffer : BUFFERS_TO_DUMP) { + dumpLogsLocked(dumpDir, buffer); + } + for (String service : SERVICES_TO_DUMP) { + dumpServiceLocked(dumpDir, service); + } + } + } + + private static void wipe(@NonNull File dumpDir) { + synchronized (sLock) { + for (File file : dumpDir.listFiles()) { + file.delete(); + } + } + } + + private static File getDumpDir() { + final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR); + if (!dumpDir.exists() || !dumpDir.isDirectory()) { + throw new UnsupportedOperationException("Pre-reboot dump directory not found"); + } + return dumpDir; + } + + @GuardedBy("sLock") + private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) { + try { + final File dumpFile = new File(dumpDir, buffer); + if (dumpFile.createNewFile()) { + dumpFile.setWritable(true /* writable */, true /* ownerOnly */); + } else { + // Wipes dumped information in existing file before recording new information. + new FileWriter(dumpFile, false).flush(); + } + + final String[] cmdline = + {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()}; + Runtime.getRuntime().exec(cmdline).waitFor(); + } catch (IOException | InterruptedException e) { + Slog.d(TAG, "Dump system log buffer before reboot fail", e); + } + } + + @GuardedBy("sLock") + private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) { + final IBinder binder = ServiceManager.checkService(serviceName); + if (binder == null) { + return; + } + + try { + final File dumpFile = new File(dumpDir, serviceName); + final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile, + ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE + | ParcelFileDescriptor.MODE_WRITE_ONLY); + binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); + } catch (FileNotFoundException | RemoteException e) { + Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e); + } + } +} diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index cc1cddd3a111..bc722f181650 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -44,6 +44,7 @@ import android.os.Vibrator; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.util.TimingsTraceLog; import android.view.WindowManager; @@ -446,6 +447,15 @@ public final class ShutdownThread extends Thread { SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); } + shutdownTimingLog.traceBegin("DumpPreRebootInfo"); + try { + Slog.i(TAG, "Logging pre-reboot information..."); + PreRebootLogger.log(mContext); + } catch (Exception e) { + Slog.e(TAG, "Failed to log pre-reboot information", e); + } + shutdownTimingLog.traceEnd(); // DumpPreRebootInfo + metricStarted(METRIC_SEND_BROADCAST); shutdownTimingLog.traceBegin("SendShutdownBroadcast"); Log.i(TAG, "Sending shutdown broadcast..."); diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index e77839c4e5b5..524ae54972e5 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -224,6 +224,13 @@ public class AppDataRollbackHelper { } /** + * Deletes all device-encrypted apex data snapshots for the given rollback id. + */ + public void destroyApexDeSnapshots(int rollbackId) { + mApexManager.destroyDeSnapshots(rollbackId); + } + + /** * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode * of the CE user data snapshot. diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index b5da1c2ec58a..7b96777b5987 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -44,7 +44,6 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.pm.ApexManager; import java.io.File; import java.io.IOException; @@ -182,15 +181,6 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** - * A temp flag to facilitate merging of the 2 rollback collections managed by - * RollbackManagerServiceImpl. True if this rollback is in the process of enabling and was - * originally managed by RollbackManagerServiceImpl#mNewRollbacks. - * TODO: remove this flag when merge is completed. - */ - @GuardedBy("mLock") - private boolean mIsNewRollback = false; - - /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. @@ -671,7 +661,7 @@ class Rollback { } } if (containsApex) { - ApexManager.getInstance().destroyDeSnapshots(info.getRollbackId()); + dataHelper.destroyApexDeSnapshots(info.getRollbackId()); } RollbackStore.deleteRollback(this); @@ -838,18 +828,6 @@ class Rollback { } } - void setIsNewRollback(boolean newRollback) { - synchronized (mLock) { - mIsNewRollback = newRollback; - } - } - - boolean isNewRollback() { - synchronized (mLock) { - return mIsNewRollback; - } - } - static String rollbackStateToString(@RollbackState int state) { switch (state) { case Rollback.ROLLBACK_STATE_ENABLING: return "enabling"; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 1421258c12f6..91e7cc981b89 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -164,8 +164,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // Load rollback data from device storage. synchronized (mLock) { mRollbacks = mRollbackStore.loadRollbacks(); - for (Rollback rollback : mRollbacks) { - mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true); + if (!context.getPackageManager().isDeviceUpgrading()) { + for (Rollback rollback : mRollbacks) { + mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true); + } + } else { + // Delete rollbacks when build fingerprint has changed. + for (Rollback rollback : mRollbacks) { + rollback.delete(mAppDataRollbackHelper); + } + mRollbacks.clear(); } } @@ -788,14 +796,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Rollback newRollback; synchronized (mLock) { - // See if we already have a NewRollback that contains this package - // session. If not, create a NewRollback for the parent session + // See if we already have a Rollback that contains this package + // session. If not, create a new Rollback for the parent session // that we will use for all the packages in the session. - newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId()); + newRollback = getRollbackForSessionLocked(packageSession.getSessionId()); if (newRollback == null) { newRollback = createNewRollbackLocked(parentSession); mRollbacks.add(newRollback); - newRollback.setIsNewRollback(true); } } newRollback.addToken(token); @@ -1148,24 +1155,23 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (success) { - Rollback newRollback; + Rollback rollback; synchronized (mLock) { - newRollback = getNewRollbackForPackageSessionLocked(sessionId); - if (newRollback != null && newRollback.notifySessionWithSuccess()) { - mRollbacks.remove(newRollback); - newRollback.setIsNewRollback(false); - } else { - // Not all child sessions finished with success. - // Don't enable the rollback yet. - newRollback = null; + rollback = getRollbackForSessionLocked(sessionId); + if (rollback == null || rollback.isStaged() || !rollback.isEnabling() + || !rollback.notifySessionWithSuccess()) { + return; } + // All child sessions finished with success. We can enable this rollback now. + // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from + // mRollbacks here and add it back in #completeEnableRollback later. + mRollbacks.remove(rollback); } - - if (newRollback != null) { - Rollback rollback = completeEnableRollback(newRollback); - if (rollback != null && !rollback.isStaged()) { - makeRollbackAvailable(rollback); - } + // TODO: Now #completeEnableRollback returns the same rollback object as the + // parameter on success. It would be more readable to return a boolean to indicate + // success or failure. + if (completeEnableRollback(rollback) != null) { + makeRollbackAvailable(rollback); } } else { synchronized (mLock) { @@ -1354,22 +1360,4 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } return null; } - - /** - * Returns the NewRollback associated with the given package session. - * Returns null if no NewRollback is found for the given package - * session. - */ - @WorkerThread - @GuardedBy("mLock") - Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) { - // We expect mRollbacks to be a very small list; linear search - // should be plenty fast. - for (Rollback rollback: mRollbacks) { - if (rollback.isNewRollback() && rollback.containsSessionId(packageSessionId)) { - return rollback; - } - } - return null; - } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 34d2c16ed0ac..58455ca2753b 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -36,7 +36,7 @@ import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.TextClassifierService; import android.service.textclassifier.TextClassifierService.ConnectionState; import android.text.TextUtils; -import android.util.LruCache; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; @@ -65,6 +65,7 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; @@ -146,11 +147,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final Object mLock; @GuardedBy("mLock") final SparseArray<UserState> mUserStates = new SparseArray<>(); - // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here - // to avoid leak. - @GuardedBy("mLock") - private final LruCache<TextClassificationSessionId, TextClassificationContext> - mSessionContextCache = new LruCache<>(40); + private final SessionCache mSessionCache; private final TextClassificationConstants mSettings; @Nullable private final String mDefaultTextClassifierPackage; @@ -165,6 +162,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi PackageManager packageManager = mContext.getPackageManager(); mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName(); mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName(); + mSessionCache = new SessionCache(mLock); } private void startListenSettings() { @@ -314,7 +312,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi classificationContext.getUseDefaultTextClassifier(), service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); - mSessionContextCache.put(sessionId, classificationContext); + mSessionCache.put(sessionId, classificationContext); }, "onCreateTextClassificationSession", NO_OP_CALLBACK); @@ -326,14 +324,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(sessionId); synchronized (mLock) { - TextClassificationContext textClassificationContext = - mSessionContextCache.get(sessionId); + final StrippedTextClassificationContext textClassificationContext = + mSessionCache.get(sessionId); final int userId = textClassificationContext != null - ? textClassificationContext.getUserId() + ? textClassificationContext.userId : UserHandle.getCallingUserId(); final boolean useDefaultTextClassifier = textClassificationContext != null - ? textClassificationContext.getUseDefaultTextClassifier() + ? textClassificationContext.useDefaultTextClassifier : true; handleRequest( userId, @@ -342,7 +340,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi useDefaultTextClassifier, service -> { service.onDestroyTextClassificationSession(sessionId); - mSessionContextCache.remove(sessionId); + mSessionCache.remove(sessionId); }, "onDestroyTextClassificationSession", NO_OP_CALLBACK); @@ -409,7 +407,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.decreaseIndent(); } } - pw.println("Number of active sessions: " + mSessionContextCache.size()); + pw.println("Number of active sessions: " + mSessionCache.size()); } } @@ -568,6 +566,81 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + /** + * Stores the stripped down version of {@link TextClassificationContext}s, i.e. {@link + * StrippedTextClassificationContext}, keyed by {@link TextClassificationSessionId}. Sessions + * are cleaned up automatically when the client process is dead. + */ + static final class SessionCache { + @NonNull + private final Object mLock; + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache = + new ArrayMap<>(); + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients = + new ArrayMap<>(); + + SessionCache(@NonNull Object lock) { + mLock = Objects.requireNonNull(lock); + } + + void put(@NonNull TextClassificationSessionId sessionId, + @NonNull TextClassificationContext textClassificationContext) { + synchronized (mLock) { + mCache.put(sessionId, + new StrippedTextClassificationContext(textClassificationContext)); + try { + DeathRecipient deathRecipient = () -> remove(sessionId); + sessionId.getToken().linkToDeath(deathRecipient, /* flags= */ 0); + mDeathRecipients.put(sessionId, deathRecipient); + } catch (RemoteException e) { + Slog.w(LOG_TAG, "SessionCache: Failed to link to death", e); + } + } + } + + @Nullable + StrippedTextClassificationContext get(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + return mCache.get(sessionId); + } + } + + void remove(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + DeathRecipient deathRecipient = mDeathRecipients.get(sessionId); + if (deathRecipient != null) { + sessionId.getToken().unlinkToDeath(deathRecipient, /* flags= */ 0); + } + mDeathRecipients.remove(sessionId); + mCache.remove(sessionId); + } + } + + int size() { + synchronized (mLock) { + return mCache.size(); + } + } + } + + /** A stripped down version of {@link TextClassificationContext}. */ + static class StrippedTextClassificationContext { + @UserIdInt + public final int userId; + public final boolean useDefaultTextClassifier; + + StrippedTextClassificationContext(TextClassificationContext textClassificationContext) { + userId = textClassificationContext.getUserId(); + useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier(); + } + } + private final class UserState { @UserIdInt final int mUserId; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 8c54fa95e04b..88de34027bb9 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -87,11 +87,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; /** - * The number of previous telephony suggestions to keep for each ID (for use during debugging). + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. */ - private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30; + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; - // A log for changes made to the system clock and why. + /** + * A log that records the decisions / decision metadata that affected the device's system clock + * time. This is logged in bug reports to assist with debugging issues with detection. + */ @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */); @@ -140,7 +144,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) { return; } - mLastNetworkSuggestion.set(timeSuggestion); + + // The caller submits suggestions with the best available information when there are network + // changes. The best available information may have been cached and if they were all stored + // this would lead to duplicates showing up in the suggestion history. The suggestions may + // be made for different reasons but there is not a significant benefit to storing the same + // suggestion information again. doAutoTimeDetection() should still be called: this ensures + // the suggestion and device state are always re-evaluated, which might produce a different + // detected time if, for example, the age of all suggestions are considered. + NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get(); + if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) { + mLastNetworkSuggestion.set(timeSuggestion); + } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 652dbe153680..cc33fb0f5008 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -161,16 +161,17 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_SCORE_MEDIUM; /** - * The number of previous telephony suggestions to keep for each ID (for use during debugging). + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. */ - private static final int KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE = 30; + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; @NonNull private final Callback mCallback; /** - * A log that records the decisions / decision metadata that affected the device's time zone - * (for use during debugging). + * A log that records the decisions / decision metadata that affected the device's time zone. + * This is logged in bug reports to assist with debugging issues with detection. */ @NonNull private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); @@ -182,8 +183,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ @GuardedBy("this") private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion> - mSuggestionBySlotIndex = - new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE); + mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); /** * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ad57e1f675d5..aa6d8545f705 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -362,7 +362,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private static final String TAG_PERSISTABLEBUNDLE = "persistable_bundle"; private static final String ATTR_LAUNCHEDFROMUID = "launched_from_uid"; private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package"; - private static final String ATTR_LAUNCHEDFROMFEATURE = "launched_from_feature"; private static final String ATTR_RESOLVEDTYPE = "resolved_type"; private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; @@ -423,7 +422,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int launchedFromPid; // always the pid who started the activity. final int launchedFromUid; // always the uid who started the activity. final String launchedFromPackage; // always the package who started the activity. - final @Nullable String launchedFromFeatureId; // always the feature in launchedFromPackage final Intent intent; // the original intent that generated us final String shortComponentName; // the short component name of the intent final String resolvedType; // as per original caller; @@ -775,7 +773,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" processName="); pw.println(processName); pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid); pw.print(" launchedFromPackage="); pw.print(launchedFromPackage); - pw.print(" launchedFromFeature="); pw.print(launchedFromFeatureId); pw.print(" userId="); pw.println(mUserId); pw.print(prefix); pw.print("app="); pw.println(app); pw.print(prefix); pw.println(intent.toInsecureString()); @@ -1486,10 +1483,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller, - int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, - @Nullable String _launchedFromFeature, Intent _intent, String _resolvedType, - ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo, - String _resultWho, int _reqCode, boolean _componentSpecified, + int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, Intent _intent, + String _resolvedType, ActivityInfo aInfo, Configuration _configuration, + ActivityRecord _resultTo, String _resultWho, int _reqCode, boolean _componentSpecified, boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor, ActivityOptions options, ActivityRecord sourceRecord) { super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true, @@ -1568,7 +1564,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A launchedFromPid = _launchedFromPid; launchedFromUid = _launchedFromUid; launchedFromPackage = _launchedFromPackage; - launchedFromFeatureId = _launchedFromFeature; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; componentSpecified = _componentSpecified; @@ -2289,7 +2284,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * @return Whether AppOps allows this package to enter picture-in-picture. */ private boolean checkEnterPictureInPictureAppOpsState() { - return mAtmService.getAppOpsManager().checkOpNoThrow( + return mAtmService.getAppOpsService().checkOperation( OP_PICTURE_IN_PICTURE, info.applicationInfo.uid, packageName) == MODE_ALLOWED; } @@ -7287,9 +7282,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (launchedFromPackage != null) { out.attribute(null, ATTR_LAUNCHEDFROMPACKAGE, launchedFromPackage); } - if (launchedFromFeatureId != null) { - out.attribute(null, ATTR_LAUNCHEDFROMFEATURE, launchedFromFeatureId); - } if (resolvedType != null) { out.attribute(null, ATTR_RESOLVEDTYPE, resolvedType); } @@ -7317,7 +7309,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A PersistableBundle persistentState = null; int launchedFromUid = 0; String launchedFromPackage = null; - String launchedFromFeature = null; String resolvedType = null; boolean componentSpecified = false; int userId = 0; @@ -7336,8 +7327,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A launchedFromUid = Integer.parseInt(attrValue); } else if (ATTR_LAUNCHEDFROMPACKAGE.equals(attrName)) { launchedFromPackage = attrValue; - } else if (ATTR_LAUNCHEDFROMFEATURE.equals(attrName)) { - launchedFromFeature = attrValue; } else if (ATTR_RESOLVEDTYPE.equals(attrName)) { resolvedType = attrValue; } else if (ATTR_COMPONENTSPECIFIED.equals(attrName)) { @@ -7385,11 +7374,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A " resolvedType=" + resolvedType); } final ActivityRecord r = new ActivityRecord(service, null /* caller */, - 0 /* launchedFromPid */, launchedFromUid, launchedFromPackage, launchedFromFeature, - intent, resolvedType, aInfo, service.getConfiguration(), null /* resultTo */, - null /* resultWho */, 0 /* reqCode */, componentSpecified, - false /* rootVoiceInteraction */, stackSupervisor, null /* options */, - null /* sourceRecord */); + 0 /* launchedFromPid */, launchedFromUid, launchedFromPackage, intent, resolvedType, + aInfo, service.getConfiguration(), null /* resultTo */, null /* resultWho */, + 0 /* reqCode */, componentSpecified, false /* rootVoiceInteraction */, + stackSupervisor, null /* options */, null /* sourceRecord */); r.mPersistentState = persistentState; r.taskDescription = taskDescription; @@ -7686,7 +7674,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, getLetterboxInsets()); return new RemoteAnimationTarget(task.mTaskId, record.getMode(), - record.mAdapter.mCapturedLeash, !task.fillsParent(), + record.mAdapter.mCapturedLeash, !fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mStackBounds, task.getWindowConfiguration(), @@ -7704,4 +7692,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); } + + void setPictureInPictureParams(PictureInPictureParams p) { + pictureInPictureArgs.copyOnlySet(p); + getTask().getRootTask().setPictureInPictureParams(p); + } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 9030fce7cfcc..d380f8cd7337 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -135,7 +135,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static java.lang.Integer.MAX_VALUE; import android.annotation.IntDef; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -623,7 +622,7 @@ class ActivityStack extends Task implements BoundsAnimationTarget { true /*neverRelinquishIdentity*/, _taskDescription != null ? _taskDescription : new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/, - info.applicationInfo.uid, info.packageName, null, info.resizeMode, + info.applicationInfo.uid, info.packageName, info.resizeMode, info.supportsPictureInPicture(), false /*_realActivitySuspended*/, false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info, _voiceSession, _voiceInteractor, stack); @@ -636,16 +635,15 @@ class ActivityStack extends Task implements BoundsAnimationTarget { String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity, ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, - String callingPackage, @Nullable String callingFeatureId, int resizeMode, - boolean supportsPictureInPicture, boolean _realActivitySuspended, - boolean userSetupComplete, int minWidth, int minHeight, + String callingPackage, int resizeMode, boolean supportsPictureInPicture, + boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info, IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, ActivityStack stack) { super(atmService, id, _intent, _affinityIntent, _affinity, _rootAffinity, _realActivity, _origActivity, _rootWasReset, _autoRemoveRecents, _askedCompatMode, _userId, _effectiveUid, _lastDescription, lastTimeMoved, neverRelinquishIdentity, _lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, - callingUid, callingPackage, callingFeatureId, resizeMode, supportsPictureInPicture, + callingUid, callingPackage, resizeMode, supportsPictureInPicture, _realActivitySuspended, userSetupComplete, minWidth, minHeight, info, _voiceSession, _voiceInteractor, stack); @@ -2926,7 +2924,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { .setCallingPid(-1) .setCallingUid(parent.launchedFromUid) .setCallingPackage(parent.launchedFromPackage) - .setCallingFeatureId(parent.launchedFromFeatureId) .setRealCallingPid(-1) .setRealCallingUid(parent.launchedFromUid) .setComponentSpecified(true) diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index a582f2159dfc..a513ef8f3190 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -88,7 +88,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.Manifest; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -261,9 +260,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { /** Short cut */ private WindowManagerService mWindowManager; - private AppOpsManager mAppOpsManager; - - /** Common synchronization logic used to save things to disks. */ + /** Common synchronization logic used to save things to disks. */ PersisterQueue mPersisterQueue; LaunchParamsPersister mLaunchParamsPersister; private LaunchParamsController mLaunchParamsController; @@ -1050,8 +1047,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, - @Nullable String callingFeatureId, boolean ignoreTargetSecurity, - boolean launchingInTask, WindowProcessController callerApp, ActivityRecord resultRecord, + boolean ignoreTargetSecurity, boolean launchingInTask, + WindowProcessController callerApp, ActivityRecord resultRecord, ActivityStack resultStack) { final boolean isCallerRecents = mService.getRecentTasks() != null && mService.getRecentTasks().isCallerRecents(callingUid); @@ -1063,10 +1060,10 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // existing task, then also allow the activity to be fully relaunched. return true; } - final int componentRestriction = getComponentRestrictionForCallingPackage(aInfo, - callingPackage, callingFeatureId, callingPid, callingUid, ignoreTargetSecurity); + final int componentRestriction = getComponentRestrictionForCallingPackage( + aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity); final int actionRestriction = getActionRestrictionForCallingPackage( - intent.getAction(), callingPackage, callingFeatureId, callingPid, callingUid); + intent.getAction(), callingPackage, callingPid, callingUid); if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) { if (resultRecord != null) { @@ -1197,16 +1194,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } - private AppOpsManager getAppOpsManager() { - if (mAppOpsManager == null) { - mAppOpsManager = mService.mContext.getSystemService(AppOpsManager.class); - } - return mAppOpsManager; - } - private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo, - String callingPackage, @Nullable String callingFeatureId, int callingPid, - int callingUid, boolean ignoreTargetSecurity) { + String callingPackage, int callingPid, int callingUid, boolean ignoreTargetSecurity) { if (!ignoreTargetSecurity && mService.checkComponentPermission(activityInfo.permission, callingPid, callingUid, activityInfo.applicationInfo.uid, activityInfo.exported) == PERMISSION_DENIED) { @@ -1222,8 +1211,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return ACTIVITY_RESTRICTION_NONE; } - if (getAppOpsManager().noteOpNoThrow(opCode, callingUid, - callingPackage, callingFeatureId, "") != AppOpsManager.MODE_ALLOWED) { + // TODO moltmann b/136595429: Set featureId from caller + if (mService.getAppOpsService().noteOperation(opCode, callingUid, + callingPackage, /* featureId */ null, false, "") != AppOpsManager.MODE_ALLOWED) { if (!ignoreTargetSecurity) { return ACTIVITY_RESTRICTION_APPOP; } @@ -1232,8 +1222,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return ACTIVITY_RESTRICTION_NONE; } - private int getActionRestrictionForCallingPackage(String action, String callingPackage, - @Nullable String callingFeatureId, int callingPid, int callingUid) { + private int getActionRestrictionForCallingPackage(String action, + String callingPackage, int callingPid, int callingUid) { if (action == null) { return ACTIVITY_RESTRICTION_NONE; } @@ -1266,8 +1256,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return ACTIVITY_RESTRICTION_NONE; } - if (getAppOpsManager().noteOpNoThrow(opCode, callingUid, - callingPackage, callingFeatureId, "") != AppOpsManager.MODE_ALLOWED) { + // TODO moltmann b/136595429: Set featureId from caller + if (mService.getAppOpsService().noteOperation(opCode, callingUid, + callingPackage, /* featureId */ null, false, "") != AppOpsManager.MODE_ALLOWED) { return ACTIVITY_RESTRICTION_APPOP; } @@ -2710,7 +2701,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { SafeActivityOptions options) { Task task = null; final String callingPackage; - final String callingFeatureId; final Intent intent; final int userId; int activityType = ACTIVITY_TYPE_UNDEFINED; @@ -2790,12 +2780,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return ActivityManager.START_TASK_TO_FRONT; } callingPackage = task.mCallingPackage; - callingFeatureId = task.mCallingFeatureId; intent = task.intent; intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); userId = task.mUserId; - return mService.getActivityStartController().startActivityInPackage(task.mCallingUid, - callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null, + return mService.getActivityStartController().startActivityInPackage( + task.mCallingUid, callingPid, callingUid, callingPackage, intent, null, null, null, 0, 0, options, userId, task, "startActivityFromRecents", false /* validateIncomingUser */, null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */); diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 881dc81807f5..f35ba9e69ed7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -26,7 +26,6 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.IApplicationThread; import android.content.ComponentName; @@ -279,11 +278,10 @@ public class ActivityStartController { } final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeatureId, Intent intent, - String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart) { + String callingPackage, Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, SafeActivityOptions options, + int userId, Task inTask, String reason, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { userId = checkTargetUser(userId, validateIncomingUser, realCallingPid, realCallingUid, reason); @@ -294,7 +292,6 @@ public class ActivityStartController { .setRealCallingPid(realCallingPid) .setRealCallingUid(realCallingUid) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setResultTo(resultTo) .setResultWho(resultWho) @@ -313,20 +310,19 @@ public class ActivityStartController { * * @param uid Make a call as if this UID did. * @param callingPackage Make a call as if this package did. - * @param callingFeatureId Make a call as if this feature in the package did. * @param intents Intents to start. * @param userId Start the intents on this user. * @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID. * @param originatingPendingIntent PendingIntentRecord that originated this activity start or * null if not originated by PendingIntent */ - final int startActivitiesInPackage(int uid, String callingPackage, - @Nullable String callingFeatureId, Intent[] intents, String[] resolvedTypes, - IBinder resultTo, SafeActivityOptions options, int userId, boolean validateIncomingUser, - PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { + final int startActivitiesInPackage(int uid, String callingPackage, Intent[] intents, + String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, + boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, + boolean allowBackgroundActivityStart) { return startActivitiesInPackage(uid, 0 /* realCallingPid */, -1 /* realCallingUid */, - callingPackage, callingFeatureId, intents, resolvedTypes, resultTo, options, userId, - validateIncomingUser, originatingPendingIntent, allowBackgroundActivityStart); + callingPackage, intents, resolvedTypes, resultTo, options, userId, validateIncomingUser, + originatingPendingIntent, allowBackgroundActivityStart); } /** @@ -343,9 +339,9 @@ public class ActivityStartController { * null if not originated by PendingIntent */ final int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeatureId, Intent[] intents, - String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, + String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, + SafeActivityOptions options, int userId, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { final String reason = "startActivityInPackage"; @@ -354,14 +350,14 @@ public class ActivityStartController { Binder.getCallingUid(), reason); // TODO: Switch to user app stacks here. - return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage, - callingFeatureId, intents, resolvedTypes, resultTo, options, userId, reason, - originatingPendingIntent, allowBackgroundActivityStart); + return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage, intents, + resolvedTypes, resultTo, options, userId, reason, originatingPendingIntent, + allowBackgroundActivityStart); } int startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid, - int incomingRealCallingUid, String callingPackage, @Nullable String callingFeatureId, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, + int incomingRealCallingUid, String callingPackage, Intent[] intents, + String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, String reason, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { if (intents == null) { @@ -439,7 +435,6 @@ public class ActivityStartController { .setCallingPid(callingPid) .setCallingUid(callingUid) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setRealCallingPid(realCallingPid) .setRealCallingUid(realCallingUid) .setActivityOptions(checkedOptions) diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 100977187239..76aa1d115ef6 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -34,7 +34,6 @@ import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; @@ -86,7 +85,6 @@ class ActivityStartInterceptor { private int mUserId; private int mStartFlags; private String mCallingPackage; - private @Nullable String mCallingFeatureId; /* * Per-intent states that were load from ActivityStarter and are subject to modifications @@ -122,20 +120,19 @@ class ActivityStartInterceptor { * method should not be changed during intercept. */ void setStates(int userId, int realCallingPid, int realCallingUid, int startFlags, - String callingPackage, @Nullable String callingFeatureId) { + String callingPackage) { mRealCallingPid = realCallingPid; mRealCallingUid = realCallingUid; mUserId = userId; mStartFlags = startFlags; mCallingPackage = callingPackage; - mCallingFeatureId = callingFeatureId; } private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) { Bundle activityOptions = deferCrossProfileAppsAnimationIfNecessary(); final IIntentSender target = mService.getIntentSenderLocked( - INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId, - null /*token*/, null /*resultCode*/, 0 /*requestCode*/, + INTENT_SENDER_ACTIVITY, mCallingPackage, callingUid, mUserId, null /*token*/, + null /*resultCode*/, 0 /*requestCode*/, new Intent[] { mIntent }, new String[] { mResolvedType }, flags, activityOptions); return new IntentSender(target); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 8ed798c766d2..c7270f257923 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -318,7 +318,6 @@ class ActivityStarter { int callingPid = DEFAULT_CALLING_PID; int callingUid = DEFAULT_CALLING_UID; String callingPackage; - @Nullable String callingFeatureId; int realCallingPid = DEFAULT_REAL_CALLING_PID; int realCallingUid = DEFAULT_REAL_CALLING_UID; int startFlags; @@ -368,7 +367,6 @@ class ActivityStarter { callingPid = DEFAULT_CALLING_PID; callingUid = DEFAULT_CALLING_UID; callingPackage = null; - callingFeatureId = null; realCallingPid = DEFAULT_REAL_CALLING_PID; realCallingUid = DEFAULT_REAL_CALLING_UID; startFlags = 0; @@ -407,7 +405,6 @@ class ActivityStarter { callingPid = request.callingPid; callingUid = request.callingUid; callingPackage = request.callingPackage; - callingFeatureId = request.callingFeatureId; realCallingPid = request.realCallingPid; realCallingUid = request.realCallingUid; startFlags = request.startFlags; @@ -696,10 +693,9 @@ class ActivityStarter { } final IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, "android" /* packageName */, - null /* featureId */, appCallingUid, mRequest.userId, null /* token */, - null /* resultWho*/, 0 /* requestCode*/, new Intent[]{mRequest.intent}, - new String[]{mRequest.resolvedType}, + ActivityManager.INTENT_SENDER_ACTIVITY, "android" /* packageName */, appCallingUid, + mRequest.userId, null /* token */, null /* resultWho*/, 0 /* requestCode*/, + new Intent[] { mRequest.intent }, new String[] { mRequest.resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null /* bOptions */); @@ -811,7 +807,6 @@ class ActivityStarter { int callingPid = request.callingPid; int callingUid = request.callingUid; String callingPackage = request.callingPackage; - String callingFeatureId = request.callingFeatureId; final int realCallingPid = request.realCallingPid; final int realCallingUid = request.realCallingUid; final int startFlags = request.startFlags; @@ -887,7 +882,6 @@ class ActivityStarter { // we want the final activity to consider it to have been launched by the // previous app activity. callingPackage = sourceRecord.launchedFromPackage; - callingFeatureId = sourceRecord.launchedFromFeatureId; } } @@ -955,8 +949,8 @@ class ActivityStarter { } boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, - requestCode, callingPid, callingUid, callingPackage, callingFeatureId, - request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord, resultStack); + requestCode, callingPid, callingUid, callingPackage, request.ignoreTargetSecurity, + inTask != null, callerApp, resultRecord, resultStack); abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, @@ -996,8 +990,7 @@ class ActivityStarter { } } - mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage, - callingFeatureId); + mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, checkedOptions)) { // activity start was intercepted, e.g. because the target user is currently in quiet @@ -1030,7 +1023,7 @@ class ActivityStarter { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { final IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingFeatureId, + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingUid, userId, null, null, 0, new Intent[]{intent}, new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); @@ -1088,7 +1081,7 @@ class ActivityStarter { // app [on install success]. if (rInfo != null && rInfo.auxiliaryInfo != null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent, - callingPackage, callingFeatureId, verificationBundle, resolvedType, userId); + callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; @@ -1097,10 +1090,9 @@ class ActivityStarter { } final ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, - callingPackage, callingFeatureId, intent, resolvedType, aInfo, - mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, - request.componentSpecified, voiceSession != null, mSupervisor, checkedOptions, - sourceRecord); + callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), + resultRecord, resultWho, requestCode, request.componentSpecified, + voiceSession != null, mSupervisor, checkedOptions, sourceRecord); mLastStartActivityRecord = r; if (r.appTimeTracker == null && sourceRecord != null) { @@ -1310,22 +1302,21 @@ class ActivityStarter { * Creates a launch intent for the given auxiliary resolution data. */ private @NonNull Intent createLaunchIntent(@Nullable AuxiliaryResolveInfo auxiliaryResponse, - Intent originalIntent, String callingPackage, @Nullable String callingFeatureId, - Bundle verificationBundle, String resolvedType, int userId) { + Intent originalIntent, String callingPackage, Bundle verificationBundle, + String resolvedType, int userId) { if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) { // request phase two resolution PackageManagerInternal packageManager = mService.getPackageManagerInternalLocked(); boolean isRequesterInstantApp = packageManager.isInstantApp(callingPackage, userId); packageManager.requestInstantAppResolutionPhaseTwo( auxiliaryResponse, originalIntent, resolvedType, callingPackage, - callingFeatureId, isRequesterInstantApp, verificationBundle, userId); + isRequesterInstantApp, verificationBundle, userId); } return InstantAppResolver.buildEphemeralInstallerIntent( originalIntent, InstantAppResolver.sanitizeIntent(originalIntent), auxiliaryResponse == null ? null : auxiliaryResponse.failureIntent, callingPackage, - callingFeatureId, verificationBundle, resolvedType, userId, @@ -2584,11 +2575,6 @@ class ActivityStarter { return this; } - ActivityStarter setCallingFeatureId(String callingFeatureId) { - mRequest.callingFeatureId = callingFeatureId; - return this; - } - /** * Sets the pid of the caller who requested to launch the activity. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 7302e529c1eb..25f6d6fd5ca6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -182,15 +182,14 @@ public abstract class ActivityTaskManagerInternal { public abstract void notifySingleTaskDisplayDrawn(int displayId); /** - * Start activity {@code intents} as if {@code packageName/featureId} on user {@code userId} did - * it. + * Start activity {@code intents} as if {@code packageName} on user {@code userId} did it. * * - DO NOT call it with the calling UID cleared. * - All the necessary caller permission checks must be done at callsites. * * @return error codes used by {@link IActivityManager#startActivity} and its siblings. */ - public abstract int startActivitiesAsPackage(String packageName, String featureId, + public abstract int startActivitiesAsPackage(String packageName, int userId, Intent[] intents, Bundle bOptions); /** @@ -200,7 +199,6 @@ public abstract class ActivityTaskManagerInternal { * @param realCallingPid PID of the real caller. * @param realCallingUid UID of the real caller. * @param callingPackage Make a call as if this package did. - * @param callingFeatureId Make a call as if this feature in the package did. * @param intents Intents to start. * @param userId Start the intents on this user. * @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID. @@ -210,17 +208,16 @@ public abstract class ActivityTaskManagerInternal { * from originatingPendingIntent */ public abstract int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeatureId, Intent[] intents, - String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, + String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, + SafeActivityOptions options, int userId, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart); public abstract int startActivityInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeaturId, Intent intent, - String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart); + String callingPackage, Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, SafeActivityOptions options, + int userId, Task inTask, String reason, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart); /** * Start activity {@code intent} without calling user-id check. @@ -231,7 +228,7 @@ public abstract class ActivityTaskManagerInternal { * @return error codes used by {@link IActivityManager#startActivity} and its siblings. */ public abstract int startActivityAsUser(IApplicationThread caller, String callingPackage, - @Nullable String callingFeatureId, Intent intent, @Nullable Bundle options, int userId); + Intent intent, @Nullable Bundle options, int userId); /** * Called when Keyguard flags might have changed. @@ -391,7 +388,7 @@ public abstract class ActivityTaskManagerInternal { public abstract ActivityTokens getTopActivityForTask(int taskId); public abstract IIntentSender getIntentSender(int type, String packageName, - @Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho, + int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ea5a71a7200e..4b96ea0a85dd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -234,6 +234,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; +import com.android.internal.app.IAppOpsService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ProcessMap; import com.android.internal.logging.MetricsLogger; @@ -263,6 +264,7 @@ import com.android.server.am.BaseErrorDialog; import com.android.server.am.PendingIntentController; import com.android.server.am.PendingIntentRecord; import com.android.server.am.UserState; +import com.android.server.appop.AppOpsService; import com.android.server.firewall.IntentFirewall; import com.android.server.inputmethod.InputMethodSystemProperty; import com.android.server.pm.UserManagerService; @@ -376,7 +378,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { RootWindowContainer mRootWindowContainer; WindowManagerService mWindowManager; private UserManagerService mUserManager; - private AppOpsManager mAppOpsManager; + private AppOpsService mAppOpsService; /** All active uids in the system. */ private final MirrorActiveUids mActiveUids = new MirrorActiveUids(); private final SparseArray<String> mPendingTempWhitelist = new SparseArray<>(); @@ -889,11 +891,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mUserManager; } - AppOpsManager getAppOpsManager() { - if (mAppOpsManager == null) { - mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + AppOpsService getAppOpsService() { + if (mAppOpsService == null) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b); } - return mAppOpsManager; + return mAppOpsService; } boolean hasUserRestriction(String restriction, int userId) { @@ -901,8 +904,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage) { - final int mode = getAppOpsManager().noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - callingUid, callingPackage, /* featureId */ null, ""); + final int mode = getAppOpsService().noteOperation(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + callingUid, callingPackage, /* featureId */ null, false, ""); if (mode == AppOpsManager.MODE_DEFAULT) { return checkPermission(Manifest.permission.SYSTEM_ALERT_WINDOW, callingPid, callingUid) == PERMISSION_GRANTED; @@ -1022,43 +1025,41 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public final int startActivity(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions) { - return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { + return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, + resultWho, requestCode, startFlags, profilerInfo, bOptions, UserHandle.getCallingUserId()); } @Override public final int startActivities(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle bOptions, int userId) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions, + int userId) { assertPackageMatchesCallingUid(callingPackage); final String reason = "startActivities"; enforceNotIsolatedCaller(reason); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason); // TODO: Switch to user app stacks here. return getActivityStartController().startActivities(caller, -1, 0, -1, callingPackage, - callingFeatureId, intents, resolvedTypes, resultTo, - SafeActivityOptions.fromBundle(bOptions), userId, reason, - null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */); + intents, resolvedTypes, resultTo, SafeActivityOptions.fromBundle(bOptions), userId, + reason, null /* originatingPendingIntent */, + false /* allowBackgroundActivityStart */); } @Override public int startActivityAsUser(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions, int userId) { - return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, userId, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { + return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, + resultWho, requestCode, startFlags, profilerInfo, bOptions, userId, true /*validateIncomingUser*/); } private int startActivityAsUser(IApplicationThread caller, String callingPackage, - @Nullable String callingFeatureId, Intent intent, String resolvedType, - IBinder resultTo, String resultWho, int requestCode, int startFlags, - ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, + boolean validateIncomingUser) { assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityAsUser"); @@ -1069,7 +1070,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getActivityStartController().obtainStarter(intent, "startActivityAsUser") .setCaller(caller) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setResultTo(resultTo) .setResultWho(resultWho) @@ -1215,7 +1215,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .setCallingPid(-1) .setCallingUid(r.launchedFromUid) .setCallingPackage(r.launchedFromPackage) - .setCallingFeatureId(r.launchedFromFeatureId) .setRealCallingPid(-1) .setRealCallingUid(r.launchedFromUid) .setActivityOptions(options) @@ -1232,9 +1231,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions, int userId) { + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { assertPackageMatchesCallingUid(callingPackage); final WaitResult res = new WaitResult(); enforceNotIsolatedCaller("startActivityAndWait"); @@ -1244,7 +1242,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getActivityStartController().obtainStarter(intent, "startActivityAndWait") .setCaller(caller) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setResultTo(resultTo) .setResultWho(resultWho) @@ -1260,9 +1257,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public final int startActivityWithConfig(IApplicationThread caller, String callingPackage, - String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, Configuration config, - Bundle bOptions, int userId) { + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, Configuration config, Bundle bOptions, int userId) { assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityWithConfig"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, @@ -1271,7 +1267,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getActivityStartController().obtainStarter(intent, "startActivityWithConfig") .setCaller(caller) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setResultTo(resultTo) .setResultWho(resultWho) @@ -1322,7 +1317,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityRecord sourceRecord; final int targetUid; final String targetPackage; - final String targetFeatureId; final boolean isResolver; synchronized (mGlobalLock) { if (resultTo == null) { @@ -1390,7 +1384,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } targetUid = sourceRecord.launchedFromUid; targetPackage = sourceRecord.launchedFromPackage; - targetFeatureId = sourceRecord.launchedFromFeatureId; isResolver = sourceRecord.isResolverOrChildActivity(); } @@ -1403,7 +1396,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getActivityStartController().obtainStarter(intent, "startActivityAsCaller") .setCallingUid(targetUid) .setCallingPackage(targetPackage) - .setCallingFeatureId(targetFeatureId) .setResolvedType(resolvedType) .setResultTo(resultTo) .setResultWho(resultWho) @@ -1439,8 +1431,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public int startVoiceActivity(String callingPackage, String callingFeatureId, int callingPid, - int callingUid, Intent intent, String resolvedType, IVoiceInteractionSession session, + public int startVoiceActivity(String callingPackage, int callingPid, int callingUid, + Intent intent, String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { assertPackageMatchesCallingUid(callingPackage); @@ -1453,7 +1445,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getActivityStartController().obtainStarter(intent, "startVoiceActivity") .setCallingUid(callingUid) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setVoiceSession(session) .setVoiceInteractor(interactor) @@ -1466,9 +1457,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public int startAssistantActivity(String callingPackage, @NonNull String callingFeatureId, - int callingPid, int callingUid, Intent intent, String resolvedType, Bundle bOptions, - int userId) { + public int startAssistantActivity(String callingPackage, int callingPid, int callingUid, + Intent intent, String resolvedType, Bundle bOptions, int userId) { assertPackageMatchesCallingUid(callingPackage); mAmInternal.enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()"); userId = handleIncomingUser(callingPid, callingUid, userId, "startAssistantActivity"); @@ -1476,7 +1466,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getActivityStartController().obtainStarter(intent, "startAssistantActivity") .setCallingUid(callingUid) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setActivityOptions(bOptions) .setUserId(userId) @@ -1500,14 +1489,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { try { synchronized (mGlobalLock) { final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); - final String recentsFeatureId = mRecentTasks.getRecentsComponentFeatureId(); final int recentsUid = mRecentTasks.getRecentsComponentUid(); final WindowProcessController caller = getProcessController(callingPid, callingUid); // Start a new recents animation final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor, getActivityStartController(), mWindowManager, intent, recentsComponent, - recentsFeatureId, recentsUid, caller); + recentsUid, caller); if (recentsAnimationRunner == null) { anim.preloadRecentsActivity(); } else { @@ -2807,6 +2795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfos() { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); @@ -2833,6 +2822,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfosOnDisplay(int displayId) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); @@ -4165,7 +4155,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } // Only update the saved args from the args that are set - r.pictureInPictureArgs.copyOnlySet(params); + r.setPictureInPictureParams(params); final float aspectRatio = r.pictureInPictureArgs.getAspectRatio(); final List<RemoteAction> actions = r.pictureInPictureArgs.getActions(); // Adjust the source bounds by the insets for the transition down @@ -4213,7 +4203,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { "setPictureInPictureParams", token, params); // Only update the saved args from the args that are set - r.pictureInPictureArgs.copyOnlySet(params); + r.setPictureInPictureParams(params); if (r.inPinnedWindowingMode()) { // If the activity is already in picture-in-picture, update the pinned stack now // if it is not already expanding to fullscreen. Otherwise, the arguments will @@ -5785,9 +5775,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } - IIntentSender getIntentSenderLocked(int type, String packageName, String featureId, - int callingUid, int userId, IBinder token, String resultWho, int requestCode, - Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) { + IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, int userId, + IBinder token, String resultWho, int requestCode, Intent[] intents, + String[] resolvedTypes, int flags, Bundle bOptions) { ActivityRecord activity = null; if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { @@ -5803,8 +5793,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final PendingIntentRecord rec = mPendingIntentController.getIntentSender(type, packageName, - featureId, callingUid, userId, token, resultWho, requestCode, intents, - resolvedTypes, flags, bOptions); + callingUid, userId, token, resultWho, requestCode, intents, resolvedTypes, flags, + bOptions); final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0; if (noCreate) { return rec; @@ -6192,8 +6182,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public int startActivitiesAsPackage(String packageName, @Nullable String featureId, - int userId, Intent[] intents, Bundle bOptions) { + public int startActivitiesAsPackage(String packageName, int userId, Intent[] intents, + Bundle bOptions) { Objects.requireNonNull(intents, "intents"); final String[] resolvedTypes = new String[intents.length]; @@ -6218,7 +6208,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } return getActivityStartController().startActivitiesInPackage( - packageUid, packageName, featureId, + packageUid, packageName, intents, resolvedTypes, null /* resultTo */, SafeActivityOptions.fromBundle(bOptions), userId, false /* validateIncomingUser */, null /* originatingPendingIntent */, @@ -6227,41 +6217,41 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeatureId, Intent[] intents, - String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, + String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, + SafeActivityOptions options, int userId, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { assertPackageMatchesCallingUid(callingPackage); synchronized (mGlobalLock) { return getActivityStartController().startActivitiesInPackage(uid, realCallingPid, - realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes, - resultTo, options, userId, validateIncomingUser, originatingPendingIntent, + realCallingUid, callingPackage, intents, resolvedTypes, resultTo, options, + userId, validateIncomingUser, originatingPendingIntent, allowBackgroundActivityStart); } } @Override public int startActivityInPackage(int uid, int realCallingPid, int realCallingUid, - String callingPackage, @Nullable String callingFeatureId, Intent intent, - String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason, - boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, + String callingPackage, Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, SafeActivityOptions options, + int userId, Task inTask, String reason, boolean validateIncomingUser, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { assertPackageMatchesCallingUid(callingPackage); synchronized (mGlobalLock) { return getActivityStartController().startActivityInPackage(uid, realCallingPid, - realCallingUid, callingPackage, callingFeatureId, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, options, userId, inTask, - reason, validateIncomingUser, originatingPendingIntent, + realCallingUid, callingPackage, intent, resolvedType, resultTo, resultWho, + requestCode, startFlags, options, userId, inTask, reason, + validateIncomingUser, originatingPendingIntent, allowBackgroundActivityStart); } } @Override - public int startActivityAsUser(IApplicationThread caller, String callerPackage, - @Nullable String callerFeatureId, Intent intent, Bundle options, int userId) { + public int startActivityAsUser(IApplicationThread caller, String callerPacakge, + Intent intent, Bundle options, int userId) { return ActivityTaskManagerService.this.startActivityAsUser( - caller, callerPackage, callerFeatureId, intent, + caller, callerPacakge, intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, options, userId, false /*validateIncomingUser*/); @@ -6697,12 +6687,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public IIntentSender getIntentSender(int type, String packageName, - @Nullable String featureId, int callingUid, int userId, IBinder token, - String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, - int flags, Bundle bOptions) { + int callingUid, int userId, IBinder token, String resultWho, + int requestCode, Intent[] intents, String[] resolvedTypes, int flags, + Bundle bOptions) { synchronized (mGlobalLock) { - return getIntentSenderLocked(type, packageName, featureId, callingUid, userId, - token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions); + return getIntentSenderLocked(type, packageName, callingUid, userId, token, + resultWho, requestCode, intents, resolvedTypes, flags, bOptions); } } diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 8fa811915fc2..16a75645f9ae 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -125,7 +125,7 @@ class AppTaskImpl extends IAppTask.Stub { } @Override - public int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, + public int startActivity(IBinder whoThread, String callingPackage, Intent intent, String resolvedType, Bundle bOptions) { checkCaller(); mService.assertPackageMatchesCallingUid(callingPackage); @@ -148,7 +148,6 @@ class AppTaskImpl extends IAppTask.Stub { return mService.getActivityStartController().obtainStarter(intent, "AppTaskImpl") .setCaller(appThread) .setCallingPackage(callingPackage) - .setCallingFeatureId(callingFeatureId) .setResolvedType(resolvedType) .setActivityOptions(bOptions) .setUserId(callingUser) diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 06e7b48a1f04..9e93e1455f2c 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -18,6 +18,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import android.content.res.Resources; +import android.text.TextUtils; + import com.android.server.wm.DisplayContent.TaskContainers; /** @@ -42,7 +45,18 @@ public abstract class DisplayAreaPolicy { */ protected final TaskContainers mTaskContainers; - DisplayAreaPolicy(WindowManagerService wmService, + /** + * Construct a new {@link DisplayAreaPolicy} + * + * @param wmService the window manager service instance + * @param content the display content for which the policy applies + * @param root the root display area under which the policy operates + * @param imeContainer the ime container that the policy must attach + * @param taskContainers the task container that the policy must attach + * + * @see #attachDisplayAreas() + */ + protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { mWmService = wmService; @@ -119,5 +133,55 @@ public abstract class DisplayAreaPolicy { throw new IllegalArgumentException("don't know how to sort " + token); } } + + /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */ + static class Provider implements DisplayAreaPolicy.Provider { + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers) { + return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer, + taskContainers); + } + } + } + + /** + * Provider for {@link DisplayAreaPolicy} instances. + * + * By implementing this interface and overriding the + * {@code config_deviceSpecificDisplayAreaPolicyProvider}, a device-specific implementations + * of {@link DisplayAreaPolicy} can be supplied. + */ + public interface Provider { + /** + * Instantiate a new DisplayAreaPolicy. + * + * @see DisplayAreaPolicy#DisplayAreaPolicy + */ + DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers); + + /** + * Instantiate the device-specific {@link Provider}. + */ + static Provider fromResources(Resources res) { + String name = res.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider); + if (TextUtils.isEmpty(name)) { + return new DisplayAreaPolicy.Default.Provider(); + } + try { + return (Provider) Class.forName(name).newInstance(); + } catch (ReflectiveOperationException | ClassCastException e) { + throw new IllegalStateException("Couldn't instantiate class " + name + + " for config_deviceSpecificDisplayAreaPolicyProvider:" + + " make sure it has a public zero-argument constructor" + + " and implements DisplayAreaPolicy.Provider", e); + } + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 810aa3438ea0..3b658c04b8c2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -304,8 +304,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService); - private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default( - mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); + private final DisplayAreaPolicy mDisplayAreaPolicy; private WindowState mTmpWindow; private WindowState mTmpWindow2; @@ -1027,6 +1026,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.addChild(mWindowContainers, null); super.addChild(mOverlayContainers, null); + mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate( + mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); mWindowContainers.addChildren(); // Sets the display content for the children. @@ -5659,7 +5660,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return activityType == ACTIVITY_TYPE_STANDARD && (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + || windowingMode == WINDOWING_MODE_MULTI_WINDOW); } /** diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index d0179adadbd7..51b9916159fe 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -44,6 +44,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsetsAnimationCallback; import android.view.WindowInsetsAnimationControlListener; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; /** @@ -107,11 +108,11 @@ class InsetsPolicy { changed = true; } if (changed) { - startAnimation(mShowingTransientTypes, true, () -> { + mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray()); + updateBarControlTarget(mFocusedWin); + startAnimation(true /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { - mPolicy.getStatusBarManagerInternal().showTransient( - mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); mStateController.notifyInsetsChanged(); } }); @@ -122,7 +123,7 @@ class InsetsPolicy { if (mShowingTransientTypes.size() == 0) { return; } - startAnimation(mShowingTransientTypes, false, () -> { + startAnimation(false /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { mShowingTransientTypes.clear(); mStateController.notifyInsetsChanged(); @@ -268,18 +269,20 @@ class InsetsPolicy { return isDockedStackVisible || isFreeformStackVisible || isResizing; } - private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) { + @VisibleForTesting + void startAnimation(boolean show, Runnable callback) { int typesReady = 0; final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); - updateBarControlTarget(mFocusedWin); - for (int i = internalTypes.size() - 1; i >= 0; i--) { + final IntArray showingTransientTypes = mShowingTransientTypes; + for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { InsetsSourceProvider provider = - mStateController.getSourceProvider(internalTypes.get(i)); - if (provider == null) continue; - InsetsSourceControl control = provider.getControl(provider.getControlTarget()); - if (control == null || control.getLeash() == null) continue; - typesReady |= InsetsState.toPublicType(internalTypes.get(i)); - controls.put(control.getType(), control); + mStateController.getSourceProvider(showingTransientTypes.get(i)); + InsetsSourceControl control = provider.getControl(mTransientControlTarget); + if (control == null || control.getLeash() == null) { + continue; + } + typesReady |= InsetsState.toPublicType(showingTransientTypes.get(i)); + controls.put(control.getType(), new InsetsSourceControl(control)); } controlAnimationUnchecked(typesReady, controls, show, callback); } @@ -335,7 +338,6 @@ class InsetsPolicy { private InsetsPolicyAnimationControlListener mListener; InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { - super(); mListener = listener; } @@ -353,9 +355,11 @@ class InsetsPolicy { InsetsController.INTERPOLATOR, true, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + SurfaceAnimationThread.getHandler().post( + () -> mListener.onReady(mAnimationControl, typesReady)); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void scheduleApplyChangeInsets() { InsetsState state = getState(); @@ -384,7 +388,7 @@ class InsetsPolicy { return overrideState; } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void applySurfaceParams( final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { @@ -396,14 +400,12 @@ class InsetsPolicy { t.apply(); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimationCallback.InsetsAnimation animation, WindowInsetsAnimationCallback.AnimationBounds bounds, int layoutDuringAnimation) { - SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types)); } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 798665972a33..0d3f6b98f483 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -59,6 +59,7 @@ class InsetsSourceProvider { private final InsetsSourceControl mFakeControl; private @Nullable InsetsSourceControl mControl; private @Nullable InsetsControlTarget mControlTarget; + private @Nullable InsetsControlTarget mPendingControlTarget; private @Nullable InsetsControlTarget mFakeControlTarget; private @Nullable ControlAdapter mAdapter; @@ -140,8 +141,9 @@ class InsetsSourceProvider { mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); - if (mControlTarget != null) { - updateControlForTarget(mControlTarget, true /* force */); + if (mPendingControlTarget != null) { + updateControlForTarget(mPendingControlTarget, true /* force */); + mPendingControlTarget = null; } } } @@ -245,7 +247,7 @@ class InsetsSourceProvider { setWindow(null, null, null); } if (mWin == null) { - mControlTarget = target; + mPendingControlTarget = target; return; } if (target == mControlTarget && !force) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index caaa4305406c..2d7d3f18c101 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -89,6 +90,12 @@ class InsetsStateController { if (type == ITYPE_NAVIGATION_BAR) { state.removeSource(ITYPE_IME); state.removeSource(ITYPE_STATUS_BAR); + state.removeSource(ITYPE_CAPTION_BAR); + } + + // Status bar doesn't get influenced by caption bar + if (type == ITYPE_STATUS_BAR) { + state.removeSource(ITYPE_CAPTION_BAR); } // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). @@ -212,18 +219,18 @@ class InsetsStateController { /** * Called when the focused window that is able to control the system bars changes. * - * @param topControlling The target that is now able to control the top bar appearance - * and visibility. + * @param statusControlling The target that is now able to control the status bar appearance + * and visibility. * @param navControlling The target that is now able to control the nav bar appearance * and visibility. */ - void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling, - @Nullable InsetsControlTarget fakeTopControlling, + void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling, + @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling) { - onControlChanged(ITYPE_STATUS_BAR, topControlling); + onControlChanged(ITYPE_STATUS_BAR, statusControlling); onControlChanged(ITYPE_NAVIGATION_BAR, navControlling); - onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeTopControlling); + onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling); onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling); notifyPendingInsetsControlChanged(); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 3771b3ee9dc9..a9dc36d91a3f 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -42,7 +42,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AppGlobals; @@ -156,7 +155,6 @@ class RecentTasks { */ private int mRecentsUid = -1; private ComponentName mRecentsComponent = null; - private @Nullable String mFeatureId; /** * Mapping of user id -> whether recent tasks have been loaded for that user. @@ -420,13 +418,6 @@ class RecentTasks { } /** - * @return the featureId for the recents component. - */ - @Nullable String getRecentsComponentFeatureId() { - return mFeatureId; - } - - /** * @return the uid for the recents component. */ int getRecentsComponentUid() { diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index b0492be5f542..9770947ff2c3 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -63,7 +63,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, private final DisplayContent mDefaultDisplay; private final Intent mTargetIntent; private final ComponentName mRecentsComponent; - private final @Nullable String mRecentsFeatureId; private final int mRecentsUid; private final @Nullable WindowProcessController mCaller; private final int mUserId; @@ -81,8 +80,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, RecentsAnimation(ActivityTaskManagerService atm, ActivityStackSupervisor stackSupervisor, ActivityStartController activityStartController, WindowManagerService wm, - Intent targetIntent, ComponentName recentsComponent, @Nullable String recentsFeatureId, - int recentsUid, @Nullable WindowProcessController caller) { + Intent targetIntent, ComponentName recentsComponent, int recentsUid, + @Nullable WindowProcessController caller) { mService = atm; mStackSupervisor = stackSupervisor; mDefaultDisplay = mService.mRootWindowContainer.getDefaultDisplay(); @@ -90,7 +89,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager = wm; mTargetIntent = targetIntent; mRecentsComponent = recentsComponent; - mRecentsFeatureId = recentsFeatureId; mRecentsUid = recentsUid; mCaller = caller; mUserId = atm.getCurrentUserId(); @@ -458,7 +456,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, .obtainStarter(mTargetIntent, reason) .setCallingUid(mRecentsUid) .setCallingPackage(mRecentsComponent.getPackageName()) - .setCallingFeatureId(mRecentsFeatureId) .setActivityOptions(new SafeActivityOptions(options)) .setUserId(mUserId) .execute(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2fdcbc9083f6..2196d899406d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3323,6 +3323,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @return {@code true} if the top activity looks like it belongs to {@param userId}. */ private void taskTopActivityIsUser(Task task, @UserIdInt int userId) { + // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking + // leaf tasks here. + if (!task.isLeafTask()) { + // No op if not a leaf task since we don't want to report root tasks to + // TaskStackListeners. + return; + } + // To handle the case that work app is in the task but just is not the top one. final ActivityRecord activityRecord = task.getTopNonFinishingActivity(); final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0e500f9a25c0..326335e6df46 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -99,6 +99,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; +import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -183,7 +184,6 @@ class Task extends WindowContainer<WindowContainer> { private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color"; private static final String ATTR_CALLING_UID = "calling_uid"; private static final String ATTR_CALLING_PACKAGE = "calling_package"; - private static final String ATTR_CALLING_FEATURE_ID = "calling_feature_id"; private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture"; private static final String ATTR_RESIZE_MODE = "resize_mode"; private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds"; @@ -297,7 +297,6 @@ class Task extends WindowContainer<WindowContainer> { // For relaunching the task from recents as though it was launched by the original launcher. int mCallingUid; String mCallingPackage; - String mCallingFeatureId; private final Rect mTmpStableBounds = new Rect(); private final Rect mTmpNonDecorBounds = new Rect(); @@ -463,6 +462,11 @@ class Task extends WindowContainer<WindowContainer> { */ ITaskOrganizer mTaskOrganizer; + /** + * Last Picture-in-Picture params applicable to the task. Updated when the app + * enters Picture-in-Picture or when setPictureInPictureParams is called. + */ + PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, @@ -479,8 +483,8 @@ class Task extends WindowContainer<WindowContainer> { true /*neverRelinquishIdentity*/, _taskDescription != null ? _taskDescription : new TaskDescription(), _taskId, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/, - info.applicationInfo.uid, info.packageName, null /* default featureId */, - info.resizeMode, info.supportsPictureInPicture(), false /*_realActivitySuspended*/, + info.applicationInfo.uid, info.packageName, info.resizeMode, + info.supportsPictureInPicture(), false /*_realActivitySuspended*/, false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info, _voiceSession, _voiceInteractor, stack); } @@ -502,17 +506,18 @@ class Task extends WindowContainer<WindowContainer> { } /** Don't use constructor directly. This is only used by XML parser. */ - Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, - String _affinity, String _rootAffinity, ComponentName _realActivity, - ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents, - boolean _askedCompatMode, int _userId, int _effectiveUid, String _lastDescription, + Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, + Intent _affinityIntent, String _affinity, String _rootAffinity, + ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, + boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, + int _effectiveUid, String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, - @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture, - boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, - ActivityInfo info, IVoiceInteractionSession _voiceSession, - IVoiceInteractor _voiceInteractor, ActivityStack stack) { + int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended, + boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info, + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, + ActivityStack stack) { super(atmService.mWindowManager); EventLogTags.writeWmTaskCreated(_taskId, stack != null ? getRootTaskId() : INVALID_TASK_ID); @@ -551,7 +556,6 @@ class Task extends WindowContainer<WindowContainer> { mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; - mCallingFeatureId = callingFeatureId; mResizeMode = resizeMode; if (info != null) { setIntent(_intent, info); @@ -898,7 +902,6 @@ class Task extends WindowContainer<WindowContainer> { void setIntent(ActivityRecord r) { mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; - mCallingFeatureId = r.launchedFromFeatureId; setIntent(r.intent, r.info); setLockTaskAuth(r); @@ -1354,7 +1357,6 @@ class Task extends WindowContainer<WindowContainer> { isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; - mCallingFeatureId = r.launchedFromFeatureId; // Clamp to [1, max]. maxRecents = Math.min(Math.max(r.info.maxRecents, 1), ActivityTaskManager.getMaxAppRecentsLimitStatic()); @@ -1431,18 +1433,27 @@ class Task extends WindowContainer<WindowContainer> { } /** - * @return whether or not there are ONLY task overlay activities in the stack. + * @return whether or not there are ONLY task overlay activities in the task. * If {@param includeFinishing} is set, then don't ignore finishing activities in the * check. If there are no task overlay activities, this call returns false. */ boolean onlyHasTaskOverlayActivities(boolean includeFinishing) { - if (getChildCount() == 0) { - return false; - } - if (includeFinishing) { - return getActivity((r) -> r.isTaskOverlay()) != null; + int count = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + final ActivityRecord r = getChildAt(i).asActivityRecord(); + if (r == null) { + // Has a child that is other than Activity. + return false; + } + if (!includeFinishing && r.finishing) { + continue; + } + if (!r.isTaskOverlay()) { + return false; + } + count++; } - return getActivity((r) -> !r.finishing && r.isTaskOverlay()) != null; + return count > 0; } private boolean autoRemoveFromRecents() { @@ -2369,6 +2380,15 @@ class Task extends WindowContainer<WindowContainer> { return getRootTask() == this; } + boolean isLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).asTask() != null) { + return false; + } + } + return true; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, @@ -3203,6 +3223,12 @@ class Task extends WindowContainer<WindowContainer> { // order changes. final Task top = getTopMostTask(); info.resizeMode = top != null ? top.mResizeMode : mResizeMode; + + if (mPictureInPictureParams.empty()) { + info.pictureInPictureParams = null; + } else { + info.pictureInPictureParams = mPictureInPictureParams; + } } /** @@ -3235,7 +3261,6 @@ class Task extends WindowContainer<WindowContainer> { pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid); pw.print(" mUserSetupComplete="); pw.print(mUserSetupComplete); pw.print(" mCallingPackage="); pw.println(mCallingPackage); - pw.print(" mCallingFeatureId="); pw.println(mCallingFeatureId); if (affinity != null || rootAffinity != null) { pw.print(prefix); pw.print("affinity="); pw.print(affinity); if (affinity == null || !affinity.equals(rootAffinity)) { @@ -3445,8 +3470,6 @@ class Task extends WindowContainer<WindowContainer> { out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId)); out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid)); out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage); - out.attribute(null, ATTR_CALLING_FEATURE_ID, - mCallingFeatureId == null ? "" : mCallingFeatureId); out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode)); out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE, String.valueOf(mSupportsPictureInPicture)); @@ -3560,17 +3583,16 @@ class Task extends WindowContainer<WindowContainer> { long lastTimeMoved, boolean neverRelinquishIdentity, TaskDescription lastTaskDescription, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, - @Nullable String callingFeatureId, int resizeMode, - boolean supportsPictureInPicture, boolean realActivitySuspended, + int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) { return new ActivityStack(service, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootWasReset, autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, lastTimeMoved, neverRelinquishIdentity, lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid, callingPackage, - callingFeatureId, resizeMode, supportsPictureInPicture, realActivitySuspended, - userSetupComplete, minWidth, minHeight, null /*ActivityInfo*/, - null /*_voiceSession*/, null /*_voiceInteractor*/, stack); + resizeMode, supportsPictureInPicture, realActivitySuspended, userSetupComplete, + minWidth, minHeight, null /*ActivityInfo*/, null /*_voiceSession*/, + null /*_voiceInteractor*/, stack); } Task restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) @@ -3603,7 +3625,6 @@ class Task extends WindowContainer<WindowContainer> { int nextTaskId = INVALID_TASK_ID; int callingUid = -1; String callingPackage = ""; - String callingFeatureId = null; int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; boolean supportsPictureInPicture = false; Rect lastNonFullscreenBounds = null; @@ -3684,9 +3705,6 @@ class Task extends WindowContainer<WindowContainer> { case ATTR_CALLING_PACKAGE: callingPackage = attrValue; break; - case ATTR_CALLING_FEATURE_ID: - callingFeatureId = attrValue; - break; case ATTR_RESIZE_MODE: resizeMode = Integer.parseInt(attrValue); break; @@ -3788,8 +3806,8 @@ class Task extends WindowContainer<WindowContainer> { autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, lastTimeOnTop, neverRelinquishIdentity, taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid, - callingPackage, callingFeatureId, resizeMode, supportsPictureInPicture, - realActivitySuspended, userSetupComplete, minWidth, minHeight, null /*stack*/); + callingPackage, resizeMode, supportsPictureInPicture, realActivitySuspended, + userSetupComplete, minWidth, minHeight, null /*stack*/); task.mLastNonFullscreenBounds = lastNonFullscreenBounds; task.setBounds(lastNonFullscreenBounds); @@ -3941,4 +3959,10 @@ class Task extends WindowContainer<WindowContainer> { void onWindowFocusChanged(boolean hasFocus) { updateShadowsRadius(hasFocus, getPendingTransaction()); } + + void setPictureInPictureParams(PictureInPictureParams p) { + mPictureInPictureParams.copyOnlySet(p); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, true /* force */); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 0a0530c92a16..4b13a0c1f75d 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -43,6 +43,7 @@ import android.view.IWindowContainer; import android.view.SurfaceControl; import android.view.WindowContainerTransaction; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -379,7 +380,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } @Override - public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) { + public List<RunningTaskInfo> getChildTasks(IWindowContainer parent, + @Nullable int[] activityTypes) { enforceStackPermission("getChildTasks()"); final long ident = Binder.clearCallingIdentity(); try { @@ -405,6 +407,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub for (int i = dc.getStackCount() - 1; i >= 0; --i) { final ActivityStack as = dc.getStackAt(i); if (as.getTile() == container) { + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, as.getActivityType())) { + continue; + } final RunningTaskInfo info = new RunningTaskInfo(); as.fillTaskInfo(info); out.add(info); @@ -417,6 +423,40 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } + @Override + public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) { + enforceStackPermission("getRootTasks()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent dc = + mService.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Display " + displayId + " doesn't exist"); + } + ArrayList<RunningTaskInfo> out = new ArrayList<>(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final ActivityStack task = dc.getStackAt(i); + if (task.getTile() != null) { + // a tile is supposed to look like a parent, so don't include their + // "children" here. They can be accessed via getChildTasks() + continue; + } + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, task.getActivityType())) { + continue; + } + final RunningTaskInfo info = new RunningTaskInfo(); + task.fillTaskInfo(info); + out.add(info); + } + return out; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int sanitizeAndApplyChange(WindowContainer container, WindowContainerTransaction.Change change) { if (!(container instanceof Task)) { diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java index 369db05452f7..add11d69cbaa 100644 --- a/services/core/java/com/android/server/wm/TaskTile.java +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -60,11 +60,10 @@ public class TaskTile extends ActivityStack { System.currentTimeMillis(), true /*neverRelinquishIdentity*/, new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/, 0 /*callingUid*/, "" /*callingPackage*/, - null /*callingFeatureId*/, RESIZE_MODE_RESIZEABLE, - false /*supportsPictureInPicture*/, false /*_realActivitySuspended*/, - false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, - createEmptyActivityInfo(), null /*voiceSession*/, null /*voiceInteractor*/, - null /*stack*/); + RESIZE_MODE_RESIZEABLE, false /*supportsPictureInPicture*/, + false /*_realActivitySuspended*/, false /*userSetupComplete*/, INVALID_MIN_SIZE, + INVALID_MIN_SIZE, createEmptyActivityInfo(), null /*voiceSession*/, + null /*voiceInteractor*/, null /*stack*/); getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a370093a3907..d98c18c077b8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -187,6 +187,7 @@ import android.os.SystemService; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.DeviceConfig; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -309,6 +310,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM; + private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; + static final int LAYOUT_REPEAT_THRESHOLD = 4; static final boolean PROFILE_ORIENTATION = false; @@ -413,6 +416,8 @@ public class WindowManagerService extends IWindowManager.Stub final WindowTracing mWindowTracing; + final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider; + final private KeyguardDisableHandler mKeyguardDisableHandler; // TODO: eventually unify all keyguard state in a common place instead of having it spread over // AM's KeyguardController and the policy's KeyguardServiceDelegate. @@ -623,6 +628,9 @@ public class WindowManagerService extends IWindowManager.Stub // The root of the device window hierarchy. RootWindowContainer mRoot; + // Whether the system should use BLAST for ViewRootImpl + final boolean mUseBLAST; + int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -1137,6 +1145,10 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator = new WindowAnimator(this); mRoot = new RootWindowContainer(this); + mUseBLAST = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, + WM_USE_BLAST_ADAPTER_FLAG, false); + mWindowPlacerLocked = new WindowSurfacePlacer(this); mTaskSnapshotController = new TaskSnapshotController(this); @@ -1255,6 +1267,10 @@ public class WindowManagerService extends IWindowManager.Stub LocalServices.addService(WindowManagerInternal.class, new LocalService()); mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock); + + mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources( + mContext.getResources()); + setGlobalShadowSettings(); } @@ -5051,6 +5067,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean useBLAST() { + return mUseBLAST; + } + + @Override public void getInitialDisplaySize(int displayId, Point size) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 553ec4201cc2..cb687c9144c6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -458,8 +458,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // A collection of user restrictions that are deprecated and should simply be ignored. private static final Set<String> DEPRECATED_USER_RESTRICTIONS; private static final String AB_DEVICE_KEY = "ro.build.ab_update"; - // Permissions related to location which must not be granted automatically - private static final Set<String> LOCATION_PERMISSIONS; static { SECURE_SETTINGS_WHITELIST = new ArraySet<>(); @@ -504,11 +502,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet( UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); - - LOCATION_PERMISSIONS = Sets.newHashSet( - permission.ACCESS_FINE_LOCATION, - permission.ACCESS_BACKGROUND_LOCATION, - permission.ACCESS_COARSE_LOCATION); } /** @@ -560,6 +553,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L; + /** + * Admin apps targeting Android R+ may not use + * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated + * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use + * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long USE_SET_LOCATION_ENABLED = 117835097L; + final Context mContext; final Injector mInjector; final IPackageManager mIPackageManager; @@ -8520,6 +8523,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void clearOverrideApnUnchecked() { + if (!mHasTelephonyFeature) { + return; + } // Disable Override APNs and remove them from database. setOverrideApnsEnabledUnchecked(false); final List<ApnSetting> apns = getOverrideApnsUnchecked(); @@ -11558,13 +11564,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); + enforceDeviceOwner(Objects.requireNonNull(who)); - UserHandle userHandle = mInjector.binderGetCallingUserHandle(); - mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, - userHandle)); + UserHandle user = mInjector.binderGetCallingUserHandle(); + + mInjector.binderWithCleanCallingIdentity(() -> { + boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser( + user); + mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user); + + // make a best effort to only show the notification if the admin is actually changing + // something. this is subject to race conditions with settings changes, but those are + // unlikely to realistically interfere + if (wasLocationEnabled != locationEnabled) { + showLocationSettingsChangedNotification(user); + } + }); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) @@ -11575,6 +11590,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private void showLocationSettingsChangedNotification(UserHandle user) { + PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0, + new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT, + null, user); + Notification notification = new Notification.Builder(mContext, + SystemNotificationChannels.DEVICE_ADMIN) + .setSmallIcon(R.drawable.ic_info_outline) + .setContentTitle(mContext.getString(R.string.location_changed_notification_title)) + .setContentText(mContext.getString(R.string.location_changed_notification_text)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setShowWhen(true) + .setContentIntent(locationSettingsIntent) + .setAutoCancel(true) + .build(); + mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED, + notification); + } + @Override public void requestSetLocationProviderAllowed(ComponentName who, String provider, boolean providerAllowed) { @@ -11645,6 +11679,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException(String.format( "Permission denial: Profile owners cannot update %1$s", setting)); } + if (setting.equals(Settings.Secure.LOCATION_MODE) + && isSetSecureSettingLocationModeCheckEnabled(who.getPackageName(), + callingUserId)) { + throw new UnsupportedOperationException(Settings.Secure.LOCATION_MODE + " is " + + "deprecated. Please use setLocationEnabled() instead."); + } if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) { if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) { throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS @@ -11689,6 +11729,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(callingUserId); } mInjector.settingsSecurePutStringForUser(setting, value, callingUserId); + if (setting.equals(Settings.Secure.LOCATION_MODE)) { + showLocationSettingsChangedNotification(UserHandle.of(callingUserId)); + } }); } DevicePolicyEventLogger @@ -11698,6 +11741,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private boolean isSetSecureSettingLocationModeCheckEnabled(String packageName, int userId) { + try { + return mIPlatformCompat.isChangeEnabledByPackageName(USE_SET_LOCATION_ENABLED, + packageName, userId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e); + return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q; + } + } + @Override public void setMasterVolumeMuted(ComponentName who, boolean on) { Objects.requireNonNull(who, "ComponentName is null"); @@ -12541,14 +12594,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { true); } - // Prevent granting location-related permissions without user consent. - if (LOCATION_PERMISSIONS.contains(permission) - && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED - && !isUnattendedManagedKioskUnchecked()) { - callback.sendResult(null); - return; - } - if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 3b513774b615..8012e74a6b3c 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1180,7 +1180,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ } // Create new lib file without signature info - incfs::NewFileParams libFileParams; + incfs::NewFileParams libFileParams{}; libFileParams.size = uncompressedLen; libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; // Metadata of the new lib file is its relative path diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 2444ddecdb80..2e7ced37e91d 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -122,7 +122,6 @@ public: } RawMetadata getMetadata(StorageId storage, FileId node) const; - std::string getSignatureData(StorageId storage, FileId node) const; std::vector<std::string> listFiles(StorageId storage) const; bool startLoading(StorageId storage) const; diff --git a/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java new file mode 100644 index 000000000000..203e9804bfa3 --- /dev/null +++ b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2020 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.people.data; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Base class for reading and writing protobufs on disk from a root directory. Callers should + * ensure that the root directory is unlocked before doing I/O operations using this class. + * + * @param <T> is the data class representation of a protobuf. + */ +abstract class AbstractProtoDiskReadWriter<T> { + + private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName(); + private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS; + + private final File mRootDir; + private final ScheduledExecutorService mScheduledExecutorService; + private final long mWriteDelayMs; + + @GuardedBy("this") + private ScheduledFuture<?> mScheduledFuture; + + @GuardedBy("this") + private Map<String, T> mScheduledFileDataMap = new ArrayMap<>(); + + /** + * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a + * protobuf on disk. + */ + abstract ProtoStreamWriter<T> protoStreamWriter(); + + /** + * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf + * data on disk. + */ + abstract ProtoStreamReader<T> protoStreamReader(); + + AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs, + @NonNull ScheduledExecutorService scheduledExecutorService) { + mRootDir = rootDir; + mWriteDelayMs = writeDelayMs; + mScheduledExecutorService = scheduledExecutorService; + } + + @WorkerThread + void delete(@NonNull String fileName) { + final File file = getFile(fileName); + if (!file.exists()) { + return; + } + if (!file.delete()) { + Slog.e(TAG, "Failed to delete file: " + file.getPath()); + } + } + + @WorkerThread + void writeTo(@NonNull String fileName, @NonNull T data) { + final File file = getFile(fileName); + final AtomicFile atomicFile = new AtomicFile(file); + + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = atomicFile.startWrite(); + } catch (IOException e) { + Slog.e(TAG, "Failed to write to protobuf file.", e); + return; + } + + try { + final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); + protoStreamWriter().write(protoOutputStream, data); + protoOutputStream.flush(); + atomicFile.finishWrite(fileOutputStream); + fileOutputStream = null; + } finally { + // When fileInputStream is null (successful write), this will no-op. + atomicFile.failWrite(fileOutputStream); + } + } + + @WorkerThread + @Nullable + T read(@NonNull String fileName) { + File[] files = mRootDir.listFiles( + pathname -> pathname.isFile() && pathname.getName().equals(fileName)); + if (files == null || files.length == 0) { + return null; + } else if (files.length > 1) { + // This can't possibly happen, but sanity check. + Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files)); + } + return parseFile(files[0]); + } + + /** + * Reads all files in directory and returns a map with file names as keys and parsed file + * contents as values. + */ + @WorkerThread + @Nullable + Map<String, T> readAll() { + File[] files = mRootDir.listFiles(File::isFile); + if (files == null) { + return null; + } + + Map<String, T> results = new ArrayMap<>(); + for (File file : files) { + T result = parseFile(file); + if (result != null) { + results.put(file.getName(), result); + } + } + return results; + } + + /** + * Schedules the specified data to be flushed to a file in the future. Subsequent + * calls for the same file before the flush occurs will replace the previous data but will not + * reset when the flush will occur. All unique files will be flushed at the same time. + */ + @MainThread + synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) { + mScheduledFileDataMap.put(fileName, data); + + if (mScheduledExecutorService.isShutdown()) { + Slog.e(TAG, "Worker is shutdown, failed to schedule data saving."); + return; + } + + // Skip scheduling another flush when one is pending. + if (mScheduledFuture != null) { + return; + } + + mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData, + mWriteDelayMs, TimeUnit.MILLISECONDS); + } + + /** + * Saves specified data immediately on a background thread, and blocks until its completed. This + * is useful for when device is powering off. + */ + @MainThread + synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) { + if (mScheduledExecutorService.isShutdown()) { + return; + } + // Cancel existing future. + if (mScheduledFuture != null) { + + // We shouldn't need to interrupt as this method and threaded task + // #flushScheduledData are both synchronized. + mScheduledFuture.cancel(true); + } + + mScheduledFileDataMap.put(fileName, data); + // Submit flush and blocks until it completes. Blocking will prevent the device from + // shutting down before flushing completes. + Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData); + try { + future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Failed to save data immediately.", e); + } + } + + @WorkerThread + private synchronized void flushScheduledData() { + if (mScheduledFileDataMap.isEmpty()) { + mScheduledFuture = null; + return; + } + for (String fileName : mScheduledFileDataMap.keySet()) { + T data = mScheduledFileDataMap.remove(fileName); + writeTo(fileName, data); + } + mScheduledFuture = null; + } + + @WorkerThread + @Nullable + private T parseFile(@NonNull File file) { + final AtomicFile atomicFile = new AtomicFile(file); + try (FileInputStream fileInputStream = atomicFile.openRead()) { + final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); + return protoStreamReader().read(protoInputStream); + } catch (IOException e) { + Slog.e(TAG, "Failed to parse protobuf file.", e); + } + return null; + } + + @NonNull + private File getFile(String fileName) { + return new File(mRootDir, fileName); + } + + /** + * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamWriter<T> { + + /** + * Writes {@code T} to {@link ProtoOutputStream}. + */ + void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data); + } + + /** + * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamReader<T> { + /** + * Reads {@link ProtoInputStream} and translates it to {@code T}. + */ + @Nullable + T read(@NonNull ProtoInputStream protoInputStream); + } +} diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index b60ed3e8783f..ce353667b62d 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -20,12 +20,18 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.LocusId; +import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; +import com.android.server.people.ConversationInfoProto; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -35,6 +41,8 @@ import java.util.Objects; */ public class ConversationInfo { + private static final String TAG = ConversationInfo.class.getSimpleName(); + private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; @@ -241,6 +249,72 @@ public class ConversationInfo { return (mConversationFlags & flags) == flags; } + /** Writes field members to {@link ProtoOutputStream}. */ + void writeToProto(@NonNull ProtoOutputStream protoOutputStream) { + protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId); + if (mLocusId != null) { + long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO); + protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId()); + protoOutputStream.end(locusIdToken); + } + if (mContactUri != null) { + protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString()); + } + if (mNotificationChannelId != null) { + protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, + mNotificationChannelId); + } + protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); + protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); + } + + /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ + @NonNull + static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) + throws IOException { + ConversationInfo.Builder builder = new ConversationInfo.Builder(); + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (protoInputStream.getFieldNumber()) { + case (int) ConversationInfoProto.SHORTCUT_ID: + builder.setShortcutId( + protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID)); + break; + case (int) ConversationInfoProto.LOCUS_ID_PROTO: + long locusIdToken = protoInputStream.start( + ConversationInfoProto.LOCUS_ID_PROTO); + while (protoInputStream.nextField() + != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) { + builder.setLocusId(new LocusId( + protoInputStream.readString(LocusIdProto.LOCUS_ID))); + } + } + protoInputStream.end(locusIdToken); + break; + case (int) ConversationInfoProto.CONTACT_URI: + builder.setContactUri(Uri.parse(protoInputStream.readString( + ConversationInfoProto.CONTACT_URI))); + break; + case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID: + builder.setNotificationChannelId(protoInputStream.readString( + ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); + break; + case (int) ConversationInfoProto.SHORTCUT_FLAGS: + builder.setShortcutFlags(protoInputStream.readInt( + ConversationInfoProto.SHORTCUT_FLAGS)); + break; + case (int) ConversationInfoProto.CONVERSATION_FLAGS: + builder.setConversationFlags(protoInputStream.readInt( + ConversationInfoProto.CONVERSATION_FLAGS)); + break; + default: + Slog.w(TAG, "Could not read undefined field: " + + protoInputStream.getFieldNumber()); + } + } + return builder.build(); + } + /** * Builder class for {@link ConversationInfo} objects. */ diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index 364992181f75..ea36d38e5d4a 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -16,58 +16,124 @@ package com.android.server.people.data; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.content.LocusId; import android.net.Uri; +import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import com.android.internal.annotations.GuardedBy; +import com.android.server.people.ConversationInfosProto; + +import com.google.android.collect.Lists; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; -/** The store that stores and accesses the conversations data for a package. */ +/** + * The store that stores and accesses the conversations data for a package. + */ class ConversationStore { + private static final String TAG = ConversationStore.class.getSimpleName(); + + private static final String CONVERSATIONS_FILE_NAME = "conversations"; + + private static final long DISK_WRITE_DELAY = 2L * DateUtils.MINUTE_IN_MILLIS; + // Shortcut ID -> Conversation Info + @GuardedBy("this") private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>(); // Locus ID -> Shortcut ID + @GuardedBy("this") private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>(); // Contact URI -> Shortcut ID + @GuardedBy("this") private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>(); // Phone Number -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); // Notification Channel ID -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); - void addOrUpdate(@NonNull ConversationInfo conversationInfo) { - mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + private final ScheduledExecutorService mScheduledExecutorService; + private final File mPackageDir; + private final ContactsQueryHelper mHelper; - LocusId locusId = conversationInfo.getLocusId(); - if (locusId != null) { - mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); - } + private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter; - Uri contactUri = conversationInfo.getContactUri(); - if (contactUri != null) { - mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); - } + ConversationStore(@NonNull File packageDir, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull ContactsQueryHelper helper) { + mScheduledExecutorService = scheduledExecutorService; + mPackageDir = packageDir; + mHelper = helper; + } - String phoneNumber = conversationInfo.getContactPhoneNumber(); - if (phoneNumber != null) { - mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); - } + /** + * Loads conversations from disk to memory in a background thread. This should be called + * after the device powers on and the user has been unlocked. + */ + @MainThread + void loadConversationsFromDisk() { + mScheduledExecutorService.submit(() -> { + synchronized (this) { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter == null) { + return; + } + List<ConversationInfo> conversationsOnDisk = + conversationInfosProtoDiskReadWriter.read(CONVERSATIONS_FILE_NAME); + if (conversationsOnDisk == null) { + return; + } + for (ConversationInfo conversationInfo : conversationsOnDisk) { + conversationInfo = restoreConversationPhoneNumber(conversationInfo); + updateConversationsInMemory(conversationInfo); + } + } + }); + } - String notifChannelId = conversationInfo.getNotificationChannelId(); - if (notifChannelId != null) { - mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + /** + * Immediately flushes current conversations to disk. This should be called when device is + * powering off. + */ + @MainThread + synchronized void saveConversationsToDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.saveConversationsImmediately( + new ArrayList<>(mConversationInfoMap.values())); } } - void deleteConversation(@NonNull String shortcutId) { + @MainThread + synchronized void addOrUpdate(@NonNull ConversationInfo conversationInfo) { + updateConversationsInMemory(conversationInfo); + scheduleUpdateConversationsOnDisk(); + } + + @MainThread + synchronized void deleteConversation(@NonNull String shortcutId) { ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); if (conversationInfo == null) { return; @@ -92,31 +158,32 @@ class ConversationStore { if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } + scheduleUpdateConversationsOnDisk(); } - void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { + synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { for (ConversationInfo ci : mConversationInfoMap.values()) { consumer.accept(ci); } } @Nullable - ConversationInfo getConversation(@Nullable String shortcutId) { + synchronized ConversationInfo getConversation(@Nullable String shortcutId) { return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null; } @Nullable - ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { + synchronized ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { return getConversation(mLocusIdToShortcutIdMap.get(locusId)); } @Nullable - ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { + synchronized ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { return getConversation(mContactUriToShortcutIdMap.get(contactUri)); } @Nullable - ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { + synchronized ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } @@ -124,4 +191,140 @@ class ConversationStore { ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); } + + @MainThread + private synchronized void updateConversationsInMemory( + @NonNull ConversationInfo conversationInfo) { + mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + + LocusId locusId = conversationInfo.getLocusId(); + if (locusId != null) { + mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); + } + + Uri contactUri = conversationInfo.getContactUri(); + if (contactUri != null) { + mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); + } + + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (phoneNumber != null) { + mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); + } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + } + } + + /** Schedules a dump of all conversations onto disk, overwriting existing values. */ + @MainThread + private synchronized void scheduleUpdateConversationsOnDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.scheduleConversationsSave( + new ArrayList<>(mConversationInfoMap.values())); + } + } + + @Nullable + private ConversationInfosProtoDiskReadWriter getConversationInfosProtoDiskReadWriter() { + if (!mPackageDir.exists()) { + Slog.e(TAG, "Package data directory does not exist: " + mPackageDir.getAbsolutePath()); + return null; + } + if (mConversationInfosProtoDiskReadWriter == null) { + mConversationInfosProtoDiskReadWriter = new ConversationInfosProtoDiskReadWriter( + mPackageDir, CONVERSATIONS_FILE_NAME, DISK_WRITE_DELAY, + mScheduledExecutorService); + } + return mConversationInfosProtoDiskReadWriter; + } + + /** + * Conversation's phone number is not saved on disk, so it has to be fetched. + */ + @WorkerThread + private ConversationInfo restoreConversationPhoneNumber( + @NonNull ConversationInfo conversationInfo) { + if (conversationInfo.getContactUri() != null) { + if (mHelper.query(conversationInfo.getContactUri().toString())) { + String phoneNumber = mHelper.getPhoneNumber(); + if (!TextUtils.isEmpty(phoneNumber)) { + conversationInfo = new ConversationInfo.Builder( + conversationInfo).setContactPhoneNumber( + phoneNumber).build(); + } + } + } + return conversationInfo; + } + + /** Reads and writes {@link ConversationInfo} on disk. */ + static class ConversationInfosProtoDiskReadWriter extends + AbstractProtoDiskReadWriter<List<ConversationInfo>> { + + private final String mConversationInfoFileName; + + ConversationInfosProtoDiskReadWriter(@NonNull File baseDir, + @NonNull String conversationInfoFileName, + long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) { + super(baseDir, writeDelayMs, scheduledExecutorService); + mConversationInfoFileName = conversationInfoFileName; + } + + @Override + ProtoStreamWriter<List<ConversationInfo>> protoStreamWriter() { + return (protoOutputStream, data) -> { + for (ConversationInfo conversationInfo : data) { + long token = protoOutputStream.start(ConversationInfosProto.CONVERSATION_INFOS); + conversationInfo.writeToProto(protoOutputStream); + protoOutputStream.end(token); + } + }; + } + + @Override + ProtoStreamReader<List<ConversationInfo>> protoStreamReader() { + return protoInputStream -> { + List<ConversationInfo> results = Lists.newArrayList(); + try { + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() + != (int) ConversationInfosProto.CONVERSATION_INFOS) { + continue; + } + long token = protoInputStream.start( + ConversationInfosProto.CONVERSATION_INFOS); + ConversationInfo conversationInfo = ConversationInfo.readFromProto( + protoInputStream); + protoInputStream.end(token); + results.add(conversationInfo); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read protobuf input stream.", e); + } + return results; + }; + } + + /** + * Schedules a flush of the specified conversations to disk. + */ + @MainThread + void scheduleConversationsSave(@NonNull List<ConversationInfo> conversationInfos) { + scheduleSave(mConversationInfoFileName, conversationInfos); + } + + /** + * Saves the specified conversations immediately. This should be used when device is + * powering off. + */ + @MainThread + void saveConversationsImmediately(@NonNull List<ConversationInfo> conversationInfos) { + saveImmediately(mConversationInfoFileName, conversationInfos); + } + } } diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 7a3ed5348d30..c8673f833e71 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -86,6 +86,7 @@ public class DataManager { private final Context mContext; private final Injector mInjector; private final ScheduledExecutorService mUsageStatsQueryExecutor; + private final ScheduledExecutorService mDiskReadWriterExecutor; private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); @@ -113,6 +114,7 @@ public class DataManager { BackgroundThread.getHandler()); mMmsSmsContentObserver = new MmsSmsContentObserver( BackgroundThread.getHandler()); + mDiskReadWriterExecutor = mInjector.createScheduledExecutor(); } /** Initialization. Called when the system services are up running. */ @@ -122,13 +124,18 @@ public class DataManager { mUserManager = mContext.getSystemService(UserManager.class); mShortcutServiceInternal.addListener(new ShortcutServiceListener()); + + IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); + mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); } /** This method is called when a user is unlocked. */ public void onUserUnlocked(int userId) { UserData userData = mUserDataArray.get(userId); if (userData == null) { - userData = new UserData(userId); + userData = new UserData(userId, mDiskReadWriterExecutor, + mInjector.createContactsQueryHelper(mContext)); mUserDataArray.put(userId, userData); } userData.setUserUnlocked(); @@ -662,6 +669,14 @@ public class DataManager { } } + private class ShutdownBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + forAllPackages(PackageData::saveToDisk); + } + } + @VisibleForTesting static class Injector { diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index 75b870c74591..f67699c28531 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -22,6 +22,8 @@ import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; +import java.io.File; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,17 +45,36 @@ public class PackageData { private final Predicate<String> mIsDefaultSmsAppPredicate; + private final File mPackageDataDir; + PackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull File perUserPeopleDataDir, + @NonNull ContactsQueryHelper helper) { mPackageName = packageName; mUserId = userId; - mConversationStore = new ConversationStore(); + + mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); + mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService, + helper); mEventStore = new EventStore(); mIsDefaultDialerPredicate = isDefaultDialerPredicate; mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; } + /** Called when user is unlocked. */ + void loadFromDisk() { + mPackageDataDir.mkdirs(); + mConversationStore.loadConversationsFromDisk(); + } + + /** Called when device is shutting down. */ + void saveToDisk() { + mConversationStore.saveConversationsToDisk(); + } + @NonNull public String getPackageName() { return mPackageName; diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index 4e8fd16d05fd..aaa5db878e08 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -19,10 +19,13 @@ package com.android.server.people.data; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.os.Environment; import android.text.TextUtils; import android.util.ArrayMap; +import java.io.File; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; /** The data associated with a user profile. */ @@ -30,6 +33,12 @@ class UserData { private final @UserIdInt int mUserId; + private final File mPerUserPeopleDataDir; + + private final ScheduledExecutorService mScheduledExecutorService; + + private final ContactsQueryHelper mHelper; + private boolean mIsUnlocked; private Map<String, PackageData> mPackageDataMap = new ArrayMap<>(); @@ -40,8 +49,12 @@ class UserData { @Nullable private String mDefaultSmsApp; - UserData(@UserIdInt int userId) { + UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { mUserId = userId; + mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people"); + mScheduledExecutorService = scheduledExecutorService; + mHelper = helper; } @UserIdInt int getUserId() { @@ -56,6 +69,13 @@ class UserData { void setUserUnlocked() { mIsUnlocked = true; + + // Ensures per user root directory for people data is present, and attempt to load + // data from disk. + mPerUserPeopleDataDir.mkdirs(); + for (PackageData packageData : mPackageDataMap.values()) { + packageData.loadFromDisk(); + } } void setUserStopped() { @@ -103,7 +123,8 @@ class UserData { } private PackageData createPackageData(String packageName) { - return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp); + return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp, + mScheduledExecutorService, mPerUserPeopleDataDir, mHelper); } private boolean isDefaultDialer(String packageName) { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java index 223a98b6c8f6..8daef5fad032 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java @@ -67,8 +67,7 @@ public class ShadowPerformUnifiedRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { mBackupManagerService = backupManagerService; mPackage = targetPackage; mIsFullSystemRestore = isFullSystemRestore; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index e3453a06990f..3975f0baa22a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -50,7 +50,6 @@ import org.mockito.quality.Strictness; @RunWith(AndroidJUnit4.class) public class PendingIntentControllerTest { private static final String TEST_PACKAGE_NAME = "test-package-1"; - private static final String TEST_FEATURE_ID = "test-feature-1"; private static final int TEST_CALLING_UID = android.os.Process.myUid(); private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")}; @@ -88,8 +87,8 @@ public class PendingIntentControllerTest { private PendingIntentRecord createPendingIntentRecord(int flags) { return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST, - TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0, - TEST_INTENTS, null, flags, null); + TEST_PACKAGE_NAME, TEST_CALLING_UID, 0, null, null, 0, TEST_INTENTS, null, flags, + null); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index fbb55fdeeb8f..5a96347c4ae1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -25,7 +25,9 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -133,6 +135,8 @@ public class AccessibilitySecurityPolicyTest { mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager); mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager); mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager); + + when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 9db5a080c093..10a86f9ea527 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -88,6 +88,10 @@ public class AccessibilityWindowManagerTest { private static final int DEFAULT_FOCUSED_INDEX = 1; private static final int SCREEN_WIDTH = 1080; private static final int SCREEN_HEIGHT = 1920; + private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + private static final int HOST_WINDOW_ID = 10; + private static final int EMBEDDED_WINDOW_ID = 11; + private static final int OTHER_WINDOW_ID = 12; private AccessibilityWindowManager mA11yWindowManager; // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true, @@ -117,6 +121,10 @@ public class AccessibilityWindowManagerTest { @Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy; @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock private IBinder mMockHostToken; + @Mock private IBinder mMockEmbeddedToken; + @Mock private IBinder mMockInvalidToken; + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -140,6 +148,8 @@ public class AccessibilityWindowManagerTest { // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged. // Resets it for mockito verify of further test case. Mockito.reset(mMockA11yEventSender); + + registerLeashedTokenAndWindowId(); } @After @@ -407,6 +417,51 @@ public class AccessibilityWindowManagerTest { } @Test + public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId() + throws RemoteException { + final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false, + Mockito.mock(IBinder.class), USER_SYSTEM_ID); + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() { + final int windowId = -1; + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(hostWindowId, resolvedWindowId); + } + + @Test + public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(embeddedWindowId, resolvedWindowId); + } + + @Test public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() { // Updates top 2 z-order WindowInfo are whole visible. WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); @@ -726,6 +781,64 @@ public class AccessibilityWindowManagerTest { token.asBinder(), -1); } + @Test + public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertEquals(hostToken, mMockHostToken); + } + + @Test + public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() { + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockEmbeddedToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken); + assertNull(hostToken); + } + + @Test + public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken); + assertEquals(windowId, HOST_WINDOW_ID); + } + + @Test + public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken); + assertEquals(windowId, INVALID_ID); + } + + @Test + public void getTokenLocked_windowIsRegistered_shouldReturnToken() { + final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID); + assertEquals(token, mMockHostToken); + } + + @Test + public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() { + final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID); + assertNull(token); + } + + private void registerLeashedTokenAndWindowId() { + mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID); + mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID); + } + private void startTrackingPerDisplay(int displayId) throws RemoteException { ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>(); // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy @@ -784,6 +897,7 @@ public class AccessibilityWindowManagerTest { IAccessibilityInteractionConnection.class); final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + final IBinder mockLeashToken = Mockito.mock(IBinder.class); when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) @@ -792,11 +906,31 @@ public class AccessibilityWindowManagerTest { .thenReturn(displayId); int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( - mockWindowToken, mockA11yConnection, PACKAGE_NAME, userId); + mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId); mA11yWindowTokens.put(windowId, mockWindowToken); return mockWindowToken; } + private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal, + IBinder leashToken, int userId) throws RemoteException { + final IWindow mockWindowToken = Mockito.mock(IWindow.class); + final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock( + IAccessibilityInteractionConnection.class); + final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); + final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); + when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); + when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) + .thenReturn(bGlobal); + when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowToken.asBinder())) + .thenReturn(displayId); + + int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( + mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId); + mA11yWindowTokens.put(windowId, mockWindowToken); + return windowId; + } + private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) { final WindowInfo windowInfo = WindowInfo.obtain(); windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION; diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java index a871ec64af3b..089a79b018a8 100644 --- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -178,7 +178,6 @@ public class BroadcastRecordTest { new Intent(), null /* callerApp */, null /* callerPackage */, - null /* callerFeatureId */, 0 /* callingPid */, 0 /* callingUid */, false /* callerInstantApp */, diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java index d6efe35723db..5800acabe8a5 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java @@ -16,8 +16,6 @@ package com.android.server.backup; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import androidx.test.InstrumentationRegistry; @@ -33,18 +31,15 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; @Presubmit @RunWith(AndroidJUnit4.class) public class UserBackupPreferencesTest { private static final String EXCLUDED_PACKAGE_1 = "package1"; - private static final String EXCLUDED_PACKAGE_2 = "package2"; private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2"); - private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1"); + private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key3"); @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); @@ -60,27 +55,13 @@ public class UserBackupPreferencesTest { } @Test - public void testGetExcludedKeysForPackages_returnsExcludedKeys() { + public void testGetExcludedKeysForPackage_returnsExcludedKeys() { mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); + mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_2); - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - } - - @Test - public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() { - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); - - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys(); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2)); + Set<String> excludedKeys = + mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackage(EXCLUDED_PACKAGE_1); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_1)); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_2)); } } diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 3d220432cc8e..017c93975286 100644 --- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -20,7 +20,9 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import androidx.test.InstrumentationRegistry; @@ -30,6 +32,8 @@ import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.platform.test.annotations.Presubmit; +import com.android.server.backup.UserBackupManagerService; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +44,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -59,6 +64,7 @@ public class PerformUnifiedRestoreTaskTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; + @Mock private UserBackupManagerService mBackupManagerService; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -99,6 +105,8 @@ public class PerformUnifiedRestoreTaskTest { return null; } }); + + mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService); } private void populateTestData() { @@ -114,8 +122,9 @@ public class PerformUnifiedRestoreTaskTest { @Test public void testFilterExcludedKeys() throws Exception { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + mExcludedkeys); + mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput); // Verify only the correct were written into BackupDataOutput object. @@ -125,32 +134,49 @@ public class PerformUnifiedRestoreTaskTest { } @Test + public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() { + Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + firstExcludedKeys); + assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + + + Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1, + EXCLUDED_KEY_2)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + secondExcludedKeys); + assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + } + + @Test public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); - assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 156cd6e5826d..e5adb80e6ef9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -283,7 +283,7 @@ public class BiometricServiceTest { null /* authenticators */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_NONE), + eq(BiometricAuthenticator.TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0 /* vendorCode */)); } @@ -1117,14 +1117,14 @@ public class BiometricServiceTest { // STRONG-only auth is not available int authenticators = Authenticators.BIOMETRIC_STRONG; - assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, invokeCanAuthenticate(mBiometricService, authenticators)); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, authenticators); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_NONE), - eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT), + eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED), eq(0) /* vendorCode */); // Request for weak auth works @@ -1154,7 +1154,7 @@ public class BiometricServiceTest { false /* requireConfirmation */, authenticators); waitForIdle(); - assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle)); + assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mBundle)); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( eq(mBiometricService.mCurrentAuthSession.mBundle), any(IBiometricServiceReceiverInternal.class), @@ -1162,6 +1162,28 @@ public class BiometricServiceTest { anyBoolean() /* requireConfirmation */, anyInt() /* userId */, eq(TEST_PACKAGE_NAME)); + + // Un-downgrading the authenticator allows successful strong auth + for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) { + if (wrapper.id == testId) { + wrapper.updateStrength(Authenticators.BIOMETRIC_STRONG); + } + } + + resetReceiver(); + authenticators = Authenticators.BIOMETRIC_STRONG; + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, authenticators)); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, authenticators); + waitForIdle(); + verify(mBiometricService.mStatusBarService).showAuthenticationDialog( + eq(mBiometricService.mCurrentAuthSession.mBundle), + any(IBiometricServiceReceiverInternal.class), + eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + eq(TEST_PACKAGE_NAME)); } @Test(expected = IllegalStateException.class) @@ -1245,6 +1267,19 @@ public class BiometricServiceTest { } @Test + public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + when(mDevicePolicyManager + .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG); + waitForIdle(); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + } + + @Test public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index 312ff2ca84a1..df242f3efc16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -91,31 +91,31 @@ public class UtilsTest { @Test public void testIsDeviceCredentialAllowed_withIntegerFlags() { int authenticators = 0; - assertFalse(Utils.isDeviceCredentialAllowed(authenticators)); + assertFalse(Utils.isCredentialRequested(authenticators)); authenticators |= Authenticators.DEVICE_CREDENTIAL; - assertTrue(Utils.isDeviceCredentialAllowed(authenticators)); + assertTrue(Utils.isCredentialRequested(authenticators)); authenticators |= Authenticators.BIOMETRIC_WEAK; - assertTrue(Utils.isDeviceCredentialAllowed(authenticators)); + assertTrue(Utils.isCredentialRequested(authenticators)); } @Test public void testIsDeviceCredentialAllowed_withBundle() { Bundle bundle = new Bundle(); - assertFalse(Utils.isDeviceCredentialAllowed(bundle)); + assertFalse(Utils.isCredentialRequested(bundle)); int authenticators = 0; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertFalse(Utils.isDeviceCredentialAllowed(bundle)); + assertFalse(Utils.isCredentialRequested(bundle)); authenticators |= Authenticators.DEVICE_CREDENTIAL; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isDeviceCredentialAllowed(bundle)); + assertTrue(Utils.isCredentialRequested(bundle)); authenticators |= Authenticators.BIOMETRIC_WEAK; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isDeviceCredentialAllowed(bundle)); + assertTrue(Utils.isCredentialRequested(bundle)); } @Test @@ -140,14 +140,14 @@ public class UtilsTest { for (int i = 0; i <= 7; i++) { int authenticators = 1 << i; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isBiometricAllowed(bundle)); + assertTrue(Utils.isBiometricRequested(bundle)); } // The rest of the bits are not allowed to integrate with the public APIs for (int i = 8; i < 32; i++) { int authenticators = 1 << i; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertFalse(Utils.isBiometricAllowed(bundle)); + assertFalse(Utils.isBiometricRequested(bundle)); } } diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index ce5d6d9be770..4a686ee6a081 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -19,6 +19,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -28,10 +29,12 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; @@ -48,6 +51,8 @@ public class PlatformCompatTest { @Mock private PackageManager mPackageManager; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock CompatChange.ChangeListener mListener1, mListener2; PlatformCompat mPlatformCompat; CompatConfig mCompatConfig; @@ -60,11 +65,15 @@ public class PlatformCompatTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow( new PackageManager.NameNotFoundException()); + when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt())) + .thenReturn(-1); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); // Assume userdebug/eng non-final build when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); } @Test diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index d40130a62fd9..8dae48cafd7b 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -211,7 +211,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( - new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY)); + new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), + Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); runJobInHandler(); @@ -230,7 +231,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( - new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY)); + new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), + Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); runJobInHandler(); @@ -390,7 +392,7 @@ public class AppIntegrityManagerServiceImplTest { public void getCurrentRules() throws Exception { whitelistUsAsRuleProvider(); makeUsSystemApp(); - Rule rule = new Rule(IntegrityFormula.PACKAGE_NAME.equalTo("package"), Rule.DENY); + Rule rule = new Rule(IntegrityFormula.Application.packageNameEquals("package"), Rule.DENY); when(mIntegrityFileManager.readRules(any())).thenReturn(Arrays.asList(rule)); assertThat(mService.getCurrentRules().getList()).containsExactly(rule); diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 38cf562f8c5b..3dc26afdb9af 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -58,7 +58,7 @@ public class RuleBinaryParserTest { getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); private static final String ATOMIC_FORMULA_START_BITS = getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - private static final int INVALID_FORMULA_SEPARATOR_VALUE = 3; + private static final int INVALID_FORMULA_SEPARATOR_VALUE = (1 << SEPARATOR_BITS) - 1; private static final String INVALID_FORMULA_SEPARATOR_BITS = getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 913aff7daaa9..ea9e6ff86728 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -112,6 +112,7 @@ public class RuleIndexingDetailsIdentifierTest { ATOMIC_FORMULA_WITH_VERSION_CODE, ATOMIC_FORMULA_WITH_ISPREINSTALLED)), Rule.DENY); + public static final int INVALID_FORMULA_TAG = -1; @Test public void getIndexType_nullRule() { @@ -290,7 +291,7 @@ public class RuleIndexingDetailsIdentifierTest { return new AtomicFormula(0) { @Override public int getTag() { - return 4; + return INVALID_FORMULA_TAG; } @Override diff --git a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java index f16cf35bfb34..4ba584e67929 100644 --- a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java @@ -39,6 +39,7 @@ import android.location.GnssClock; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; +import android.location.GnssRequest; import android.location.GnssSingleSatCorrection; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; @@ -563,7 +564,7 @@ public class GnssManagerServiceTest { assertThrows(SecurityException.class, () -> mGnssManagerService.addGnssMeasurementsListener( - mockGnssMeasurementsListener, + new GnssRequest.Builder().build(), mockGnssMeasurementsListener, "com.android.server", "abcd123", "TestGnssMeasurementsListener")); mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent); @@ -580,8 +581,11 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - assertThat(mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener")).isEqualTo(true); + assertThat(mGnssManagerService.addGnssMeasurementsListener( + new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener")).isEqualTo(true); mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent); verify(mockGnssMeasurementsListener, times(1)).onGnssMeasurementsReceived( @@ -626,8 +630,10 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener"); + mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener"); disableLocationPermissions(); @@ -648,8 +654,10 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener"); + mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener"); disableLocationPermissions(); diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java index 4ae374abb7c2..9213e1fe5a25 100644 --- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java @@ -51,6 +51,7 @@ public final class PeopleServiceTest { private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; private static final int APP_PREDICTION_TARGET_COUNT = 4; private static final String TEST_PACKAGE_NAME = "com.example"; + private static final int USER_ID = 0; private PeopleServiceInternal mServiceInternal; private PeopleService.LocalService mLocalService; @@ -73,7 +74,7 @@ public final class PeopleServiceTest { mServiceInternal = LocalServices.getService(PeopleServiceInternal.class); mLocalService = (PeopleService.LocalService) mServiceInternal; - mSessionId = new AppPredictionSessionId("abc"); + mSessionId = new AppPredictionSessionId("abc", USER_ID); mPredictionContext = new AppPredictionContext.Builder(mContext) .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT) diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java index 331ad5972fc1..03b5e38cadb7 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java @@ -21,16 +21,24 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.annotation.Nullable; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import android.os.FileUtils; +import android.text.format.DateUtils; import android.util.ArraySet; +import androidx.test.InstrumentationRegistry; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.Set; @RunWith(JUnit4.class) @@ -42,11 +50,34 @@ public final class ConversationStoreTest { private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; + private static final String SHORTCUT_ID_2 = "ghi"; + private static final String NOTIFICATION_CHANNEL_ID_2 = "test : ghi"; + private static final LocusId LOCUS_ID_2 = new LocusId("jkl"); + private static final Uri CONTACT_URI_2 = Uri.parse("tel:+3234567890"); + private static final String PHONE_NUMBER_2 = "+3234567890"; + + private static final String SHORTCUT_ID_3 = "mno"; + private static final String NOTIFICATION_CHANNEL_ID_3 = "test : mno"; + private static final LocusId LOCUS_ID_3 = new LocusId("pqr"); + private static final Uri CONTACT_URI_3 = Uri.parse("tel:+9234567890"); + private static final String PHONE_NUMBER_3 = "+9234567890"; + + private MockScheduledExecutorService mMockScheduledExecutorService; + private TestContactQueryHelper mTestContactQueryHelper; private ConversationStore mConversationStore; + private File mFile; @Before public void setUp() { - mConversationStore = new ConversationStore(); + Context ctx = InstrumentationRegistry.getContext(); + mFile = new File(ctx.getCacheDir(), "testdir"); + mTestContactQueryHelper = new TestContactQueryHelper(ctx); + resetConversationStore(); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); } @Test @@ -153,6 +184,130 @@ public final class ConversationStoreTest { mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID)); } + @Test + public void testDataPersistenceAndRestoration() { + // Add conversation infos, causing it to be loaded to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.addOrUpdate(in3); + + long futuresExecuted = mMockScheduledExecutorService.fastForwardTime( + 3L * DateUtils.MINUTE_IN_MILLIS); + assertEquals(1, futuresExecuted); + + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // During restoration, we want to confirm that this conversation was removed. + mConversationStore.deleteConversation(SHORTCUT_ID_3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + mConversationStore.deleteConversation(SHORTCUT_ID); + mConversationStore.deleteConversation(SHORTCUT_ID_2); + mConversationStore.deleteConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertNull(out3); + } + + @Test + public void testDelayedDiskWrites() { + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + + mConversationStore.addOrUpdate(in1); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // Should not see second conversation on disk because of disk write delay has not been + // reached. + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertNull(out2); + + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + mConversationStore.addOrUpdate(in3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.reset(); + mTestContactQueryHelper.setQueryResult(true, true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3); + + resetConversationStore(); + out1 = mConversationStore.getConversation(SHORTCUT_ID); + out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertEquals(in3, out3); + } + + @Test + public void testMimicDevicePowerOff() { + + // Even without fast forwarding time with our mock ScheduledExecutorService, we should + // see the conversations immediately saved to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.saveConversationsToDisk(); + + // Ensure that futures were cancelled and the immediate flush occurred. + assertEquals(0, mMockScheduledExecutorService.getFutures().size()); + + // Expect to see 2 executes: loadConversationFromDisk and saveConversationsToDisk. + // loadConversationFromDisk gets called each time we call #resetConversationStore(). + assertEquals(2, mMockScheduledExecutorService.getExecutes().size()); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertEquals(in2, out2); + } + + private void resetConversationStore() { + mFile.mkdir(); + mMockScheduledExecutorService = new MockScheduledExecutorService(); + mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService, + mTestContactQueryHelper); + mConversationStore.loadConversationsFromDisk(); + } + private static ConversationInfo buildConversationInfo(String shortcutId) { return buildConversationInfo(shortcutId, null, null, null, null); } @@ -171,4 +326,54 @@ public final class ConversationStoreTest { .setBubbled(true) .build(); } + + private static class TestContactQueryHelper extends ContactsQueryHelper { + + private int mQueryCalls; + private boolean[] mQueryResult; + + private int mPhoneNumberCalls; + private String[] mPhoneNumberResult; + + TestContactQueryHelper(Context context) { + super(context); + + mQueryCalls = 0; + mPhoneNumberCalls = 0; + } + + private void setQueryResult(boolean... values) { + mQueryResult = values; + } + + private void setPhoneNumberResult(String... values) { + mPhoneNumberResult = values; + } + + private void reset() { + mQueryCalls = 0; + mQueryResult = null; + mPhoneNumberCalls = 0; + mPhoneNumberResult = null; + } + + @Override + boolean query(String contactUri) { + if (mQueryResult != null && mQueryCalls < mQueryResult.length) { + return mQueryResult[mQueryCalls++]; + } + mQueryCalls++; + return false; + } + + @Override + @Nullable + String getPhoneNumber() { + if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) { + return mPhoneNumberResult[mPhoneNumberCalls++]; + } + mPhoneNumberCalls++; + return null; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java new file mode 100644 index 000000000000..8b8ba1247a4b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 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.people.data; + +import com.android.internal.util.Preconditions; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Mock implementation of ScheduledExecutorService for testing. All commands will run + * synchronously. Commands passed to {@link #submit(Runnable)} and {@link #execute(Runnable)} will + * run immediately. Commands scheduled via {@link #schedule(Runnable, long, TimeUnit)} will run + * after calling {@link #fastForwardTime(long)}. + */ +class MockScheduledExecutorService implements ScheduledExecutorService { + + private final List<Runnable> mExecutes = new ArrayList<>(); + private final List<MockScheduledFuture<?>> mFutures = new ArrayList<>(); + private long mTimeElapsedMillis = 0; + + /** + * Advances fake time, runs all the commands for which the delay has expired. + */ + long fastForwardTime(long millis) { + mTimeElapsedMillis += millis; + ImmutableList<MockScheduledFuture<?>> futuresCopy = ImmutableList.copyOf(mFutures); + mFutures.clear(); + long totalExecuted = 0; + for (MockScheduledFuture<?> future : futuresCopy) { + if (future.getDelay() < mTimeElapsedMillis) { + future.getCommand().run(); + mExecutes.add(future.getCommand()); + totalExecuted += 1; + } else { + mFutures.add(future); + } + } + return totalExecuted; + } + + List<Runnable> getExecutes() { + return mExecutes; + } + + List<MockScheduledFuture<?>> getFutures() { + return mFutures; + } + + void resetTimeElapsedMillis() { + mTimeElapsedMillis = 0; + } + + /** + * Fakes a schedule execution of {@link Runnable}. The command will be executed by an explicit + * call to {@link #fastForwardTime(long)}. + */ + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + Preconditions.checkState(unit == TimeUnit.MILLISECONDS); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, delay, unit); + mFutures.add(future); + return future; + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, + TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable command) { + mExecutes.add(command); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, 0, + TimeUnit.MILLISECONDS); + future.getCommand().run(); + return future; + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws ExecutionException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + mExecutes.add(command); + command.run(); + } + + class MockScheduledFuture<V> implements ScheduledFuture<V> { + + private final Runnable mCommand; + private final long mDelay; + private boolean mCancelled = false; + + MockScheduledFuture(Runnable command, long delay, TimeUnit timeUnit) { + mCommand = command; + mDelay = delay; + } + + public long getDelay() { + return mDelay; + } + + public Runnable getCommand() { + return mCommand; + } + + @Override + public long getDelay(TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(Delayed o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + mCancelled = true; + return mFutures.remove(this); + } + + @Override + public boolean isCancelled() { + return mCancelled; + } + + @Override + public boolean isDone() { + return !mFutures.contains(this); + } + + @Override + public V get() throws ExecutionException, InterruptedException { + return null; + } + + @Override + public V get(long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + return null; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java index ec4789ad0cdf..1ddc21e4ea4d 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java @@ -20,15 +20,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import androidx.test.InstrumentationRegistry; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.List; @RunWith(JUnit4.class) @@ -52,8 +56,12 @@ public final class PackageDataTest { @Before public void setUp() { + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + testDir.mkdir(); mPackageData = new PackageData( - PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp); + PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp, + new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx)); ConversationInfo conversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setLocusId(LOCUS_ID) diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java index e4248a04878d..b273578ada3a 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -29,8 +29,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; +import android.content.Context; import android.content.LocusId; +import androidx.test.InstrumentationRegistry; + import com.android.server.LocalServices; import org.junit.After; @@ -41,10 +44,12 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Predicate; @RunWith(JUnit4.class) @@ -69,7 +74,13 @@ public final class UsageStatsQueryHelperTest { addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); - mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false); + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService(); + ContactsQueryHelper helper = new ContactsQueryHelper(ctx); + + mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false, + scheduledExecutorService, testDir, helper); mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) @@ -175,7 +186,7 @@ public final class UsageStatsQueryHelperTest { assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2)); } - private void addUsageEvents(UsageEvents.Event ... events) { + private void addUsageEvents(UsageEvents.Event... events) { UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), anyBoolean(), anyBoolean())).thenReturn(usageEvents); @@ -228,6 +239,12 @@ public final class UsageStatsQueryHelperTest { private ConversationInfo mConversationInfo; + TestConversationStore(File packageDir, + ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { + super(packageDir, scheduledExecutorService, helper); + } + @Override @Nullable ConversationInfo getConversation(@Nullable String shortcutId) { @@ -237,13 +254,18 @@ public final class UsageStatsQueryHelperTest { private static class TestPackageData extends PackageData { - private final TestConversationStore mConversationStore = new TestConversationStore(); + private final TestConversationStore mConversationStore; private final TestEventStore mEventStore = new TestEventStore(); TestPackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { - super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate); + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir, + @NonNull ContactsQueryHelper helper) { + super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate, + scheduledExecutorService, rootDir, helper); + mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService, + helper); } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 3e3f40d31d0e..e28d13a1a20c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -35,7 +35,6 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo; import android.content.pm.parsing.PackageImpl; import android.content.pm.parsing.ParsingPackage; -import android.net.Uri; import android.os.Build; import android.os.Process; import android.util.ArrayMap; @@ -87,6 +86,17 @@ public class AppsFilterTest { return pkg; } + private static ParsingPackage pkgQueriesProvider(String packageName, + String... queriesAuthorities) { + ParsingPackage pkg = pkg(packageName); + if (queriesAuthorities != null) { + for (String authority : queriesAuthorities) { + pkg.addQueriesProvider(authority); + } + } + return pkg; + } + private static ParsingPackage pkg(String packageName, String... queriesPackages) { ParsingPackage pkg = pkg(packageName); if (queriesPackages != null) { @@ -172,8 +182,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -188,8 +197,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.other.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), DUMMY_CALLING_UID); assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -205,8 +213,7 @@ public class AppsFilterTest { pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -266,7 +273,7 @@ public class AppsFilterTest { appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); + pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_UID); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index df2b3efeb94c..77f842aa503f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -24,7 +24,6 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; @@ -1729,7 +1728,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { final ArgumentCaptor<Intent[]> intentsCaptor = ArgumentCaptor.forClass(Intent[].class); verify(mMockActivityTaskManagerInternal).startActivitiesAsPackage( eq(packageName), - isNull(), eq(userId), intentsCaptor.capture(), anyOrNull(Bundle.class)); @@ -1788,7 +1786,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { // This shouldn't have been called. verify(mMockActivityTaskManagerInternal, times(0)).startActivitiesAsPackage( anyString(), - isNull(), anyInt(), any(Intent[].class), anyOrNull(Bundle.class)); diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java index fea1b825228c..91cc9f35da1e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -55,7 +55,6 @@ import java.util.List; @RunWith(MockitoJUnitRunner.class) public class CrossProfileAppsServiceImplTest { private static final String PACKAGE_ONE = "com.one"; - private static final String FEATURE_ID = "feature.one"; private static final int PACKAGE_ONE_UID = 1111; private static final ComponentName ACTIVITY_COMPONENT = new ComponentName("com.one", "test"); @@ -221,7 +220,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), true)); @@ -230,7 +228,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -244,7 +241,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), false)); @@ -253,7 +249,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -269,7 +264,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), true)); @@ -278,7 +272,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -294,7 +287,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), false)); @@ -303,7 +295,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -317,7 +308,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_TWO, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), true)); @@ -326,7 +316,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -340,7 +329,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_TWO, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), false)); @@ -349,7 +337,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -365,7 +352,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), true)); @@ -374,7 +360,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -395,7 +380,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), false)); @@ -404,7 +388,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -418,7 +401,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, new ComponentName(PACKAGE_TWO, "test"), UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), true)); @@ -427,7 +409,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -441,7 +422,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, new ComponentName(PACKAGE_TWO, "test"), UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), false)); @@ -450,7 +430,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -464,7 +443,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(SECONDARY_USER).getIdentifier(), true)); @@ -473,7 +451,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -487,7 +464,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(SECONDARY_USER).getIdentifier(), false)); @@ -496,7 +472,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), anyString(), - nullable(String.class), any(Intent.class), nullable(Bundle.class), anyInt()); @@ -509,7 +484,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( mIApplicationThread, PACKAGE_ONE, - FEATURE_ID, ACTIVITY_COMPONENT, UserHandle.of(PRIMARY_USER).getIdentifier(), true); @@ -518,7 +492,6 @@ public class CrossProfileAppsServiceImplTest { .startActivityAsUser( nullable(IApplicationThread.class), eq(PACKAGE_ONE), - eq(FEATURE_ID), any(Intent.class), nullable(Bundle.class), eq(PRIMARY_USER)); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 2936bdd8501b..f03670843f3a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -3144,7 +3144,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Not launchable. doReturn(ActivityManager.START_CLASS_NOT_FOUND) .when(mMockActivityTaskManagerInternal).startActivitiesAsPackage( - anyStringOrNull(), anyStringOrNull(), anyInt(), + anyStringOrNull(), anyInt(), anyOrNull(Intent[].class), anyOrNull(Bundle.class)); assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0, ActivityNotFoundException.class); @@ -3153,7 +3153,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { doReturn(ActivityManager.START_CLASS_NOT_FOUND) .when(mMockActivityTaskManagerInternal) .startActivitiesAsPackage( - anyStringOrNull(), anyStringOrNull(), anyInt(), + anyStringOrNull(), anyInt(), anyOrNull(Intent[].class), anyOrNull(Bundle.class)); assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0, ActivityNotFoundException.class); diff --git a/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java new file mode 100644 index 000000000000..a13823441665 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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.power; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.test.mock.MockContentResolver; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.FakeSettingsProvider; + +import com.google.common.io.Files; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; + +/** + * Tests for {@link PreRebootLogger} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PreRebootLoggerTest { + @Mock Context mContext; + private MockContentResolver mContentResolver; + private File mDumpDir; + + @BeforeClass + public static void setupOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @AfterClass + public static void tearDownOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + + mDumpDir = Files.createTempDir(); + mDumpDir.mkdir(); + mDumpDir.deleteOnExit(); + } + + @Test + public void log_adbEnabled_dumpsInformationProperly() { + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1); + + PreRebootLogger.log(mContext, mDumpDir); + + assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback"); + } + + @Test + public void log_adbDisabled_wipesDumpedInformation() { + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1); + PreRebootLogger.log(mContext, mDumpDir); + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0); + + PreRebootLogger.log(mContext, mDumpDir); + + assertThat(mDumpDir.listFiles()).isEmpty(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index 804c1b9e09fd..1a6c6b4011cd 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -224,21 +224,43 @@ public class RollbackUnitTest { } @Test - public void snapshotThenDelete() { + public void snapshotThenDeleteNoApex() { + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); + PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); + PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, false); + rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); + + int[] userIds = {111, 222}; + rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); + + verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); + + rollback.delete(mMockDataHelper); + + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111)); + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222)); + verify(mMockDataHelper, never()).destroyApexDeSnapshots(anyInt()); + + assertThat(rollback.isDeleted()).isTrue(); + } + + @Test + public void snapshotThenDeleteWithApex() { Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); - int[] userIds = {12, 18}; + int[] userIds = {111, 222}; rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); rollback.delete(mMockDataHelper); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12)); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18)); + verify(mMockDataHelper, never()) + .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt()); + verify(mMockDataHelper).destroyApexDeSnapshots(123); assertThat(rollback.isDeleted()).isTrue(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ebe4ab99663a..a0ea7290ec01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -140,6 +141,19 @@ public class ActivityRecordTests extends ActivityTestsBase { } @Test + public void testRemoveChildWithOverlayActivity() { + final ActivityRecord overlayActivity = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity.setTaskOverlay(true); + final ActivityRecord overlayActivity2 = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity2.setTaskOverlay(true); + + mTask.removeChild(overlayActivity2, "test"); + verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any()); + } + + @Test public void testNoCleanupMovingActivityInSameStack() { final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); mActivity.reparent(newTask, 0, null /*reason*/); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index af04f7641250..135d00586329 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -120,7 +120,7 @@ public class ActivityStartInterceptorTest { mInterceptor = new ActivityStartInterceptor( mService, mSupervisor, mRootWindowContainer, mContext); mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID, - TEST_START_FLAGS, TEST_CALLING_PACKAGE, null); + TEST_START_FLAGS, TEST_CALLING_PACKAGE); // Mock ActivityManagerInternal LocalServices.removeServiceForTest(ActivityManagerInternal.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index fa182d68d917..bab877e6c26f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -275,7 +275,7 @@ public class ActivityStarterTests extends ActivityTestsBase { if (containsConditions(preconditions, PRECONDITION_CANNOT_START_ANY_ACTIVITY)) { doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission( - any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), any(), + any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(), anyBoolean(), any(), any(), any()); } @@ -349,7 +349,7 @@ public class ActivityStarterTests extends ActivityTestsBase { boolean mockGetLaunchStack) { // always allow test to start activity. doReturn(true).when(mSupervisor).checkStartAnyActivityPermission( - any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), any(), + any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(), anyBoolean(), any(), any(), any()); if (mockGetLaunchStack) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index c93dc9757853..eb84d0af35c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -255,10 +255,10 @@ class ActivityTestsBase extends SystemServiceTestsBase { final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */, - null, null, intent, null, aInfo /*aInfo*/, new Configuration(), - null /* resultTo */, null /* resultWho */, 0 /* reqCode */, - false /*componentSpecified*/, false /* rootVoiceInteraction */, - mService.mStackSupervisor, options, null /* sourceRecord */); + null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */, + null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/, + false /* rootVoiceInteraction */, mService.mStackSupervisor, options, + null /* sourceRecord */); spyOn(activity); if (mTask != null) { // fullscreen value is normally read from resources in ctor, so for testing we need diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java new file mode 100644 index 000000000000..c1a1d5ecd3c8 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +@Presubmit +public class DisplayAreaProviderTest { + + @Test + public void testFromResources_emptyProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_nullProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_customProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + TestProvider.class.getName())), Matchers.instanceOf(TestProvider.class)); + } + + @Test + public void testFromResources_badProvider_notImplementingProviderInterface() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + Object.class.getName())); + }); + } + + @Test + public void testFromResources_badProvider_doesntExist() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + "com.android.wmtests.nonexistent.Provider")); + }); + } + + private static Resources resourcesWithProvider(String provider) { + Resources mock = mock(Resources.class); + when(mock.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider)) + .thenReturn(provider); + return mock; + } + + static class TestProvider implements DisplayAreaPolicy.Provider { + + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, + DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, + DisplayContent.TaskContainers taskContainers) { + throw new RuntimeException("test stub"); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 9dbaa4c9ca82..e6291a4c34ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -95,8 +95,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Before public void setUp() throws Exception { - updateDisplayFrames(); - mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from // changing those frames. @@ -106,6 +104,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; attrs.format = PixelFormat.TRANSLUCENT; + + updateDisplayFrames(); } @After @@ -126,6 +126,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { private void updateDisplayFrames() { mFrames = createDisplayFrames(); + mDisplayContent.mDisplayFrames = mFrames; } private DisplayFrames createDisplayFrames() { @@ -180,63 +181,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_appDrawsBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_forceAppDrawBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_onlyDrawBottomBar() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test public void layoutWindowLw_fitAllSides() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); @@ -273,7 +217,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitMax() { + public void layoutWindowLw_fitInsetsIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -295,7 +239,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitNonMax() { + public void layoutWindowLw_fitInsetsNotIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -398,6 +342,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -416,6 +361,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; addWindow(mWindow); @@ -435,6 +381,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -456,6 +403,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -477,6 +425,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -501,6 +450,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -521,6 +471,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -541,6 +492,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -581,6 +533,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -603,6 +556,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE; addWindow(mWindow); @@ -625,6 +579,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING; addWindow(mWindow); @@ -849,7 +804,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { final String prefix = ""; final InsetsState simulatedInsetsState = new InsetsState(); final DisplayFrames simulatedDisplayFrames = createDisplayFrames(); - mDisplayContent.mDisplayFrames = mFrames; mDisplayPolicy.beginLayoutLw(mFrames, uiMode); mDisplayContent.getInsetsStateController().onPostLayout(); mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode); @@ -867,6 +821,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { // Exclude comparing IME insets because currently the simulated layout only focuses on the // insets from status bar and navigation bar. realInsetsState.removeSource(InsetsState.ITYPE_IME); + realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR); realInsetsState.dump(prefix, new PrintWriter(realInsetsDump)); final StringWriter simulatedInsetsDump = new StringWriter(); simulatedInsetsState.dump(prefix, new PrintWriter(simulatedInsetsDump)); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index e71225579989..eae007d3a767 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -29,12 +29,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; import android.platform.test.annotations.Presubmit; import android.util.IntArray; @@ -122,13 +123,13 @@ public class InsetsPolicyTest extends WindowTestsBase { // TODO: adjust this test if we pretend to the app that it's still able to control it. @Test public void testControlsForDispatch_forceStatusBarVisible() { - addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |= + addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); - // The app must not control the top bar. + // The app must not control the status bar. assertNotNull(controls); assertEquals(1, controls.length); } @@ -137,6 +138,7 @@ public class InsetsPolicyTest extends WindowTestsBase { public void testControlsForDispatch_statusBarForceShowNavigation() { addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; + addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); @@ -169,7 +171,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); @@ -184,7 +187,7 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testShowTransientBars_topCanBeTransient_appGetsTopFakeControl() { + public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() { // Adding app window before setting source visibility is to prevent the visibility from // being cleared by InsetsSourceProvider.updateVisibility. final WindowState app = addWindow(TYPE_APPLICATION, "app"); @@ -194,14 +197,15 @@ public class InsetsPolicyTest extends WindowTestsBase { addWindow(TYPE_NAVIGATION_BAR, "navBar") .getControllableInsetProvider().getSource().setVisible(true); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); - // The app must get the fake control of the top bar, and must get the real control of the + // The app must get the fake control of the status bar, and must get the real control of the // navigation bar. assertEquals(2, controls.length); for (int i = controls.length - 1; i >= 0; i--) { @@ -222,7 +226,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 39cdd2cb907e..5cf1fbbacaf4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -20,7 +20,9 @@ import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -33,14 +35,14 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - @SmallTest @FlakyTest(detail = "Promote to pre-submit once confirmed stable.") @Presubmit @@ -88,6 +90,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); @@ -98,6 +104,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testImeForDispatch() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + InsetsSourceProvider statusBarProvider = getController().getSourceProvider(ITYPE_STATUS_BAR); statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) -> diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index f517881d835b..e50750841e20 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -439,11 +439,11 @@ public class SystemServicesTestRule implements TestRule { doNothing().when(this).updateCpuStats(); // AppOpsService - final AppOpsManager aos = mock(AppOpsManager.class); - doReturn(aos).when(this).getAppOpsManager(); + final AppOpsService aos = mock(AppOpsService.class); + doReturn(aos).when(this).getAppOpsService(); // Make sure permission checks aren't overridden. - doReturn(AppOpsManager.MODE_DEFAULT).when(aos).noteOpNoThrow(anyInt(), anyInt(), - anyString(), nullable(String.class), nullable(String.class)); + doReturn(AppOpsManager.MODE_DEFAULT).when(aos).noteOperation(anyInt(), anyInt(), + anyString(), nullable(String.class), anyBoolean(), nullable(String.class)); // UserManagerService final UserManagerService ums = mock(UserManagerService.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index f7aa3cc9e52c..0312df6d34fc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -34,14 +34,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; +import android.app.PictureInPictureParams; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -49,6 +56,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; +import android.content.pm.ActivityInfo; +import android.util.Rational; import android.view.Display; import android.view.ITaskOrganizer; import android.view.IWindowContainer; @@ -384,11 +393,19 @@ public class TaskOrganizerTests extends WindowTestsBase { RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */).size(); + final ActivityStack stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); final ActivityStack stack2 = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + // Check getRootTasks works + List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */); + assertEquals(initialRootTaskCount + 2, roots.size()); + lastReportedTiles.clear(); WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); @@ -415,11 +432,18 @@ public class TaskOrganizerTests extends WindowTestsBase { // Check the getChildren call List<RunningTaskInfo> children = - mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token); + mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token, + null /* activityTypes */); assertEquals(2, children.size()); - children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token); + children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token, + null /* activityTypes */); assertEquals(0, children.size()); + // Check that getRootTasks doesn't include children of tiles + roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId, + null /* activityTypes */); + assertEquals(initialRootTaskCount, roots.size()); + lastReportedTiles.clear(); wct = new WindowContainerTransaction(); wct.reorder(stack2.mRemoteToken, true /* onTop */); @@ -483,4 +507,76 @@ public class TaskOrganizerTests extends WindowTestsBase { verify(transactionListener) .transactionReady(anyInt(), any()); } + + class StubOrganizer extends ITaskOrganizer.Stub { + RunningTaskInfo mInfo; + + @Override + public void taskAppeared(RunningTaskInfo info) { + mInfo = info; + } + @Override + public void taskVanished(IWindowContainer wc) { + } + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + @Override + public void onTaskInfoChanged(RunningTaskInfo info) { + } + }; + + private ActivityRecord makePipableActivity() { + final ActivityRecord record = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; + spyOn(record); + doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean()); + return record; + } + + @Test + public void testEnterPipParams() { + final StubOrganizer o = new StubOrganizer(); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); + final ActivityRecord record = makePipableActivity(); + + final PictureInPictureParams p = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build(); + assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); + waitUntilHandlersIdle(); + assertNotNull(o.mInfo); + assertNotNull(o.mInfo.pictureInPictureParams); + } + + @Test + public void testChangePipParams() { + class ChangeSavingOrganizer extends StubOrganizer { + RunningTaskInfo mChangedInfo; + @Override + public void onTaskInfoChanged(RunningTaskInfo info) { + mChangedInfo = info; + } + } + ChangeSavingOrganizer o = new ChangeSavingOrganizer(); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); + + final ActivityRecord record = makePipableActivity(); + final PictureInPictureParams p = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build(); + assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); + waitUntilHandlersIdle(); + assertNotNull(o.mInfo); + assertNotNull(o.mInfo.pictureInPictureParams); + + final PictureInPictureParams p2 = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build(); + mWm.mAtmService.setPictureInPictureParams(record.token, p2); + waitUntilHandlersIdle(); + assertNotNull(o.mChangedInfo); + assertNotNull(o.mChangedInfo.pictureInPictureParams); + final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational(); + assertEquals(3, ratio.getNumerator()); + assertEquals(4, ratio.getDenominator()); + } } 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 feadd0a496bb..ebf14d2c6a19 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -988,7 +988,7 @@ public class TaskRecordTests extends ActivityTestsBase { private Task createTask(int taskId) { return new ActivityStack(mService, taskId, new Intent(), null, null, null, ActivityBuilder.getDefaultComponent(), null, false, false, false, 0, 10050, null, - 0, false, null, 0, 0, 0, 0, 0, null, null, 0, false, false, false, 0, + 0, false, null, 0, 0, 0, 0, 0, null, 0, false, false, false, 0, 0, null /*ActivityInfo*/, null /*_voiceSession*/, null /*_voiceInteractor*/, null /*stack*/); } @@ -1022,7 +1022,7 @@ public class TaskRecordTests extends ActivityTestsBase { boolean neverRelinquishIdentity, ActivityManager.TaskDescription lastTaskDescription, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, - int callingUid, String callingPackage, String callingFeatureId, int resizeMode, + int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8378d8ed9f68..d5eec332cda0 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -747,8 +747,7 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public int startVoiceActivity(IBinder token, Intent intent, String resolvedType, - String callingFeatureId) { + public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) { synchronized (this) { if (mImpl == null) { Slog.w(TAG, "startVoiceActivity without running voice interaction service"); @@ -758,8 +757,8 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - return mImpl.startVoiceActivityLocked(callingFeatureId, callingPid, callingUid, - token, intent, resolvedType); + return mImpl.startVoiceActivityLocked(callingPid, callingUid, token, + intent, resolvedType); } finally { Binder.restoreCallingIdentity(caller); } @@ -767,8 +766,7 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public int startAssistantActivity(IBinder token, Intent intent, String resolvedType, - String callingFeatureId) { + public int startAssistantActivity(IBinder token, Intent intent, String resolvedType) { synchronized (this) { if (mImpl == null) { Slog.w(TAG, "startAssistantActivity without running voice interaction service"); @@ -778,8 +776,8 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - return mImpl.startAssistantActivityLocked(callingFeatureId, callingPid, - callingUid, token, intent, resolvedType); + return mImpl.startAssistantActivityLocked(callingPid, callingUid, token, + intent, resolvedType); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index a62b03ca82e4..a1210cfce26d 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -216,8 +216,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return true; } - public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid, - int callingUid, IBinder token, Intent intent, String resolvedType) { + public int startVoiceActivityLocked(int callingPid, int callingUid, IBinder token, + Intent intent, String resolvedType) { try { if (mActiveSession == null || token != mActiveSession.mToken) { Slog.w(TAG, "startVoiceActivity does not match active session"); @@ -230,16 +230,16 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne intent = new Intent(intent); intent.addCategory(Intent.CATEGORY_VOICE); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - return mAtm.startVoiceActivity(mComponent.getPackageName(), callingFeatureId, - callingPid, callingUid, intent, resolvedType, mActiveSession.mSession, - mActiveSession.mInteractor, 0, null, null, mUser); + return mAtm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid, + intent, resolvedType, mActiveSession.mSession, mActiveSession.mInteractor, + 0, null, null, mUser); } catch (RemoteException e) { throw new IllegalStateException("Unexpected remote error", e); } } - public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid, - int callingUid, IBinder token, Intent intent, String resolvedType) { + public int startAssistantActivityLocked(int callingPid, int callingUid, IBinder token, + Intent intent, String resolvedType) { try { if (mActiveSession == null || token != mActiveSession.mToken) { Slog.w(TAG, "startAssistantActivity does not match active session"); @@ -253,8 +253,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT); - return mAtm.startAssistantActivity(mComponent.getPackageName(), callingFeatureId, - callingPid, callingUid, intent, resolvedType, options.toBundle(), mUser); + return mAtm.startAssistantActivity(mComponent.getPackageName(), callingPid, callingUid, + intent, resolvedType, options.toBundle(), mUser); } catch (RemoteException e) { throw new IllegalStateException("Unexpected remote error", e); } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index f54f8d1f5832..ec99f36f6e70 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -573,6 +573,7 @@ public final class Call { /** * Indicates that the call is an adhoc conference call. This property can be set for both * incoming and outgoing calls. + * @hide */ public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000; diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 6b0845f5d12b..56acdff530eb 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -174,6 +174,7 @@ public abstract class Conference extends Conferenceable { /** * Returns whether this conference is requesting that the system play a ringback tone * on its behalf. + * @hide */ public final boolean isRingbackRequested() { return mRingbackRequested; @@ -324,6 +325,7 @@ public abstract class Conference extends Conferenceable { * the default dialer's {@link InCallService}. * * @param videoState The video state in which to answer the connection. + * @hide */ public void onAnswer(int videoState) {} @@ -343,6 +345,7 @@ public abstract class Conference extends Conferenceable { * a request to reject. * For managed {@link ConnectionService}s, this will be called when the user rejects a call via * the default dialer's {@link InCallService}. + * @hide */ public void onReject() {} @@ -362,6 +365,7 @@ public abstract class Conference extends Conferenceable { /** * Sets state to be ringing. + * @hide */ public final void setRinging() { setState(Connection.STATE_RINGING); @@ -487,6 +491,7 @@ public abstract class Conference extends Conferenceable { * that do not play a ringback tone themselves in the conference's audio stream. * * @param ringback Whether the ringback tone is to be played. + * @hide */ public final void setRingbackRequested(boolean ringback) { if (mRingbackRequested != ringback) { @@ -736,6 +741,7 @@ public abstract class Conference extends Conferenceable { * * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}). * @return A {@code Conference} which indicates failure. + * @hide */ public @NonNull static Conference createFailedConference( @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) { diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 72c66d20548a..8049459cf3f4 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -501,7 +501,7 @@ public abstract class Connection extends Conferenceable { * Set by the framework to indicate that it is an adhoc conference call. * <p> * This is used for Outgoing and incoming conference calls. - * + * @hide */ public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index bf4dee2c1f04..a28cc4f69155 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1875,8 +1875,8 @@ public class TelecomManager { * {@link #registerPhoneAccount}. * @param extras A bundle that will be passed through to * {@link ConnectionService#onCreateIncomingConference}. + * @hide */ - public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount, @NonNull Bundle extras) { try { @@ -2115,6 +2115,7 @@ public class TelecomManager { * * @param participants List of participants to start conference with * @param extras Bundle of extras to use with the call + * @hide */ @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull List<Uri> participants, diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 553bcff931d2..e97cfaf0afa6 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -123,6 +123,14 @@ public final class CarrierAppUtils { return userContext.getContentResolver(); } + private static boolean isUpdatedSystemApp(ApplicationInfo ai) { + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + + return false; + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. @@ -137,7 +145,7 @@ public final class CarrierAppUtils { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); - List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper( userId, systemCarrierAppsDisabledUntilUsed, context); if (candidates == null || candidates.isEmpty()) { return; @@ -176,7 +184,7 @@ public final class CarrierAppUtils { if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED @@ -230,7 +238,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -361,29 +369,6 @@ public final class CarrierAppUtils { return apps; } - private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( - int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, Context context) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List<ApplicationInfo> apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( int userId, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { @@ -395,11 +380,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp( + getApplicationInfoIfSystemApp( userId, associatedAppPackages.get(j), context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null) { + if (ai != null && !isUpdatedSystemApp(ai)) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -413,26 +398,6 @@ public final class CarrierAppUtils { } @Nullable - private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - int userId, String packageName, Context context) { - try { - ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY); - if (ai != null) { - return ai; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Could not reach PackageManager", e); - } - return null; - } - - @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( int userId, String packageName, Context context) { try { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 945c8888f402..ebb53c50ca98 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1084,6 +1084,7 @@ public class CarrierConfigManager { /** * Determines whether adhoc conference calls are supported by a carrier. When {@code true}, * adhoc conference calling is supported, {@code false otherwise}. + * @hide */ public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS new file mode 100644 index 000000000000..657b3f2add2e --- /dev/null +++ b/tests/BootImageProfileTest/OWNERS @@ -0,0 +1,4 @@ +mathieuc@google.com +calin@google.com +yawanng@google.com +sehr@google.com diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index 59049160e885..1361df30e9d7 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -321,8 +321,7 @@ public class MemoryUsageTest extends InstrumentationTestCase { } mAtm.startActivityAndWait(null, - getInstrumentation().getContext().getBasePackageName(), - getInstrumentation().getContext().getFeatureId(), mLaunchIntent, + getInstrumentation().getContext().getBasePackageName(), mLaunchIntent, mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 74dfde848191..342c47de755a 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,6 +18,7 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], + test_suites: ["device-tests"], static_libs: [ "junit", "androidx.test.runner", diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java index 2c2e8282ff51..0393248c34d2 100644 --- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java @@ -16,22 +16,98 @@ package com.android.tests.rollback.host; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import com.android.tradefed.device.LogcatReceiver; +import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + /** * Runs the network rollback tests. */ @RunWith(DeviceJUnit4ClassRunner.class) public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { /** + * Runs the given phase of a test by calling into the device. + * Throws an exception if the test phase fails. + * <p> + * For example, <code>runPhase("testApkOnlyEnableRollback");</code> + */ + private void runPhase(String phase) throws Exception { + assertTrue(runDeviceTests("com.android.tests.rollback", + "com.android.tests.rollback.NetworkStagedRollbackTest", + phase)); + } + + private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK"; + + private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; + private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; + + private LogcatReceiver mReceiver; + + @Before + public void setUp() throws Exception { + mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger", + getDevice().getOptions().getMaxLogcatDataSize(), 0); + mReceiver.start(); + } + + @After + public void tearDown() throws Exception { + mReceiver.stop(); + mReceiver.clear(); + } + + /** * Tests failed network health check triggers watchdog staged rollbacks. */ @Test public void testNetworkFailedRollback() throws Exception { + try { + // Disconnect internet so we can test network health triggered rollbacks + getDevice().executeShellCommand("svc wifi disable"); + getDevice().executeShellCommand("svc data disable"); + + runPhase("testNetworkFailedRollback_Phase1"); + // Reboot device to activate staged package + getDevice().reboot(); + + // Verify rollback was enabled + runPhase("testNetworkFailedRollback_Phase2"); + assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3")); + + getDevice().waitForDeviceAvailable(); + // Verify rollback was executed after health check deadline + runPhase("testNetworkFailedRollback_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_EXPLICIT_HEALTH_CHECK, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } + } finally { + // Reconnect internet again so we won't break tests which assume internet available + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + } } /** @@ -40,4 +116,57 @@ public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { @Test public void testNetworkPassedDoesNotRollback() throws Exception { } + + /** + * Returns a list of all Watchdog logging events which have occurred. + */ + private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource) + throws Exception { + List<String> watchdogEvents = new ArrayList<>(); + InputStream inputStream = inputStreamSource.createInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("Watchdog event occurred")) { + watchdogEvents.add(line); + } + } + return watchdogEvents; + } + + /** + * Returns whether a Watchdog event has occurred that matches the given criteria. + * + * Check the value of all non-null parameters against the list of Watchdog events that have + * occurred, and return {@code true} if an event exists which matches all criteria. + */ + private boolean watchdogEventOccurred(List<String> loggingEvents, + String type, String logPackage, + String rollbackReason, String failedPackageName) throws Exception { + List<String> eventCriteria = new ArrayList<>(); + if (type != null) { + eventCriteria.add("type: " + type); + } + if (logPackage != null) { + eventCriteria.add("logPackage: " + logPackage); + } + if (rollbackReason != null) { + eventCriteria.add("rollbackReason: " + rollbackReason); + } + if (failedPackageName != null) { + eventCriteria.add("failedPackageName: " + failedPackageName); + } + for (String loggingEvent: loggingEvents) { + boolean matchesCriteria = true; + for (String criterion: eventCriteria) { + if (!loggingEvent.contains(criterion)) { + matchesCriteria = false; + } + } + if (matchesCriteria) { + return true; + } + } + return false; + } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java index 04004d6abb5a..e5c8a685813f 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -16,9 +16,143 @@ package com.android.tests.rollback; +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; +import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.rollback.RollbackManager; +import android.os.ParcelFileDescriptor; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; +import com.android.cts.rollback.lib.RollbackUtils; + +import libcore.io.IoUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; +import java.util.concurrent.TimeUnit; + @RunWith(JUnit4.class) public class NetworkStagedRollbackTest { + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; + private static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS = + "watchdog_request_timeout_millis"; + + private static final TestApp NETWORK_STACK = new TestApp("NetworkStack", + getNetworkStackPackageName(), -1, false, findNetworkStackApk()); + + private static File findNetworkStackApk() { + final File apk = new File("/system/priv-app/NetworkStack/NetworkStack.apk"); + if (apk.isFile()) { + return apk; + } + return new File("/system/priv-app/NetworkStackNext/NetworkStackNext.apk"); + } + + /** + * Adopts common shell permissions needed for rollback tests. + */ + @Before + public void adoptShellPermissions() { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.FORCE_STOP_PACKAGES, + Manifest.permission.WRITE_DEVICE_CONFIG); + } + + /** + * Drops shell permissions needed for rollback tests. + */ + @After + public void dropShellPermissions() { + InstallUtils.dropShellPermissionIdentity(); + } + + @Test + public void testNetworkFailedRollback_Phase1() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + RollbackManager rm = RollbackUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + rm.expireRollbackForPackage(networkStack); + uninstallNetworkStackPackage(); + + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)).isNull(); + + // Reduce health check deadline + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, + Integer.toString(120000), false); + // Simulate re-installation of new NetworkStack with rollbacks enabled + installNetworkStackPackage(); + } + + @Test + public void testNetworkFailedRollback_Phase2() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())).isNotNull(); + + // Sleep for < health check deadline + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + // Verify rollback was not executed before health check deadline + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNull(); + } + + @Test + public void testNetworkFailedRollback_Phase3() throws Exception { + // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot) + // The device is expected to reboot during sleeping. This device method will fail and + // the host will catch the assertion. If reboot doesn't happen, the host will fail the + // assertion. + Thread.sleep(TimeUnit.SECONDS.toMillis(240)); + } + + @Test + public void testNetworkFailedRollback_Phase4() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNotNull(); + } + + private static String getNetworkStackPackageName() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService( + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(), 0); + return comp.getPackageName(); + } + + private static void installNetworkStackPackage() throws Exception { + Install.single(NETWORK_STACK).setStaged().setEnableRollback() + .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit(); + } + + private static void uninstallNetworkStackPackage() { + // Uninstall the package as a privileged user so we won't fail due to permission. + runShellCommand("pm uninstall " + getNetworkStackPackageName()); + } + + private static void runShellCommand(String cmd) { + ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand(cmd); + IoUtils.closeQuietly(pfd); + } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 0fdcbc52bffa..bddd93c6de36 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -274,55 +274,6 @@ public class StagedRollbackTest { TestApp.B)).isNotNull(); } - @Test - public void testNetworkFailedRollback_Phase1() throws Exception { - // Remove available rollbacks and uninstall NetworkStack on /data/ - RollbackManager rm = RollbackUtils.getRollbackManager(); - String networkStack = getNetworkStackPackageName(); - - rm.expireRollbackForPackage(networkStack); - uninstallNetworkStackPackage(); - - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - networkStack)).isNull(); - - // Reduce health check deadline - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, - Integer.toString(120000), false); - // Simulate re-installation of new NetworkStack with rollbacks enabled - installNetworkStackPackage(); - } - - @Test - public void testNetworkFailedRollback_Phase2() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - getNetworkStackPackageName())).isNotNull(); - - // Sleep for < health check deadline - Thread.sleep(TimeUnit.SECONDS.toMillis(5)); - // Verify rollback was not executed before health check deadline - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNull(); - } - - @Test - public void testNetworkFailedRollback_Phase3() throws Exception { - // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot) - // The device is expected to reboot during sleeping. This device method will fail and - // the host will catch the assertion. If reboot doesn't happen, the host will fail the - // assertion. - Thread.sleep(TimeUnit.SECONDS.toMillis(240)); - } - - @Test - public void testNetworkFailedRollback_Phase4() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNotNull(); - } - private static String getNetworkStackPackageName() { Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); ComponentName comp = intent.resolveSystemService( diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 032f18240a55..3c5eaef86bcc 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -228,44 +228,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests failed network health check triggers watchdog staged rollbacks. - */ - @Test - public void testNetworkFailedRollback() throws Exception { - try { - // Disconnect internet so we can test network health triggered rollbacks - getDevice().executeShellCommand("svc wifi disable"); - getDevice().executeShellCommand("svc data disable"); - - runPhase("testNetworkFailedRollback_Phase1"); - // Reboot device to activate staged package - getDevice().reboot(); - - // Verify rollback was enabled - runPhase("testNetworkFailedRollback_Phase2"); - assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3")); - - getDevice().waitForDeviceAvailable(); - // Verify rollback was executed after health check deadline - runPhase("testNetworkFailedRollback_Phase4"); - InputStreamSource logcatStream = mReceiver.getLogcatData(); - try { - List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); - assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, - REASON_EXPLICIT_HEALTH_CHECK, null)); - assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, - null, null)); - } finally { - logcatStream.close(); - } - } finally { - // Reconnect internet again so we won't break tests which assume internet available - getDevice().executeShellCommand("svc wifi enable"); - getDevice().executeShellCommand("svc data enable"); - } - } - - /** * Tests passed network health check does not trigger watchdog staged rollbacks. */ @Test diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING index fefde5b4be12..0f4c4603f9b4 100644 --- a/tests/RollbackTest/TEST_MAPPING +++ b/tests/RollbackTest/TEST_MAPPING @@ -7,6 +7,9 @@ "name": "StagedRollbackTest" }, { + "name": "NetworkStagedRollbackTest" + }, + { "name": "MultiUserRollbackTest" } ] diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 2af118c40138..5aa32f868104 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -391,6 +391,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["intent"] = intent_filter_action; + manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities")); // TODO: more complicated component name tag manifest_action["key-sets"]["key-set"]["public-key"]; diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index d4e024dbe0c7..1c330e263d7e 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -272,4 +272,8 @@ interface IWifiManager boolean isScanThrottleEnabled(); Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult); + + void setAutoWakeupEnabled(boolean enable); + + boolean isAutoWakeupEnabled(); } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index a720236689d3..0cce23d196cf 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -951,7 +951,7 @@ public class WifiConfiguration implements Parcelable { * Indicate whether the network is trusted or not. Networks are considered trusted * if the user explicitly allowed this network connection. * This bit can be used by suggestion network, see - * {@link WifiNetworkSuggestion.Builder#setUnTrusted(boolean)} + * {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} * @hide */ public boolean trusted; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index fb30910c17b1..b6f4490a1872 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -6163,4 +6163,50 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Enable/disable wifi auto wakeup feature. + * + * <p> + * The feature is described in + * <a href="Wi-Fi Turn on automatically"> + * https://source.android.com/devices/tech/connect/wifi-infrastructure + * #turn_on_wi-fi_automatically + * </a> + * + * @param enable true to enable, false to disable. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAutoWakeupEnabled(boolean enable) { + try { + mService.setAutoWakeupEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the persisted Wi-Fi auto wakeup feature state. Defaults to false, unless changed by the + * user via Settings. + * + * <p> + * The feature is described in + * <a href="Wi-Fi Turn on automatically"> + * https://source.android.com/devices/tech/connect/wifi-infrastructure + * #turn_on_wi-fi_automatically + * </a> + * + * @return true to indicate that wakeup feature is enabled, false to indicate that wakeup + * feature is disabled. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public boolean isAutoWakeupEnabled() { + try { + return mService.isAutoWakeupEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java index 7cc617d61b00..bd99476afe43 100644 --- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java +++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java @@ -16,14 +16,21 @@ package android.net.wifi.wificond; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -33,6 +40,8 @@ import java.util.List; */ @SystemApi public final class NativeScanResult implements Parcelable { + private static final String TAG = "NativeScanResult"; + /** @hide */ @VisibleForTesting public byte[] ssid; @@ -53,7 +62,7 @@ public final class NativeScanResult implements Parcelable { public long tsf; /** @hide */ @VisibleForTesting - public int capability; + @BssCapabilityBits public int capability; /** @hide */ @VisibleForTesting public boolean associated; @@ -71,14 +80,17 @@ public final class NativeScanResult implements Parcelable { } /** - * Returns raw bytes representing the MAC address (BSSID) of the AP represented by this scan - * result. + * Returns the MAC address (BSSID) of the AP represented by this scan result. * - * @return a byte array, possibly null or containing the incorrect number of bytes for a MAC - * address. + * @return a MacAddress or null on error. */ - @NonNull public byte[] getBssid() { - return bssid; + @Nullable public MacAddress getBssid() { + try { + return MacAddress.fromBytes(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e); + return null; + } } /** @@ -127,31 +139,103 @@ public final class NativeScanResult implements Parcelable { return associated; } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"}, + value = {BSS_CAPABILITY_ESS, + BSS_CAPABILITY_IBSS, + BSS_CAPABILITY_CF_POLLABLE, + BSS_CAPABILITY_CF_POLL_REQUEST, + BSS_CAPABILITY_PRIVACY, + BSS_CAPABILITY_SHORT_PREAMBLE, + BSS_CAPABILITY_PBCC, + BSS_CAPABILITY_CHANNEL_AGILITY, + BSS_CAPABILITY_SPECTRUM_MANAGEMENT, + BSS_CAPABILITY_QOS, + BSS_CAPABILITY_SHORT_SLOT_TIME, + BSS_CAPABILITY_APSD, + BSS_CAPABILITY_RADIO_MANAGEMENT, + BSS_CAPABILITY_DSSS_OFDM, + BSS_CAPABILITY_DELAYED_BLOCK_ACK, + BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK + }) + public @interface BssCapabilityBits { } + + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS. + */ + public static final int BSS_CAPABILITY_ESS = 0x1 << 0; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS. + */ + public static final int BSS_CAPABILITY_IBSS = 0x1 << 1; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable. + */ + public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request. + */ + public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy. + */ + public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble. + */ + public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC. + */ + public static final int BSS_CAPABILITY_PBCC = 0x1 << 6; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility. + */ + public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management. + */ + public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS. + */ + public static final int BSS_CAPABILITY_QOS = 0x1 << 9; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time. + */ + public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD. + */ + public static final int BSS_CAPABILITY_APSD = 0x1 << 11; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management. + */ + public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM. + */ + public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack. + */ + public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack. + */ + public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15; + /** * Returns the capabilities of the AP repseresented by this scan result as advertised in the * received probe response or beacon. * - * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: - * Bit 0 - ESS - * Bit 1 - IBSS - * Bit 2 - CF Pollable - * Bit 3 - CF-Poll Request - * Bit 4 - Privacy - * Bit 5 - Short Preamble - * Bit 6 - PBCC - * Bit 7 - Channel Agility - * Bit 8 - Spectrum Management - * Bit 9 - QoS - * Bit 10 - Short Slot Time - * Bit 11 - APSD - * Bit 12 - Radio Measurement - * Bit 13 - DSSS-OFDM - * Bit 14 - Delayed Block Ack - * Bit 15 - Immediate Block Ack + * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one + * of the {@code BSS_CAPABILITY_*} flags. * * @return a bit mask of capabilities. */ - @NonNull public int getCapabilities() { + @BssCapabilityBits public int getCapabilities() { return capability; } diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java index 916c11579075..9ad2a2769add 100644 --- a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java +++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java @@ -17,11 +17,13 @@ package android.net.wifi.wificond; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; -import java.util.Arrays; +import java.util.Objects; /** * Structure providing information about clients (STAs) associated with a SoftAp. @@ -30,16 +32,21 @@ import java.util.Arrays; */ @SystemApi public final class NativeWifiClient implements Parcelable { + private final MacAddress mMacAddress; + /** - * The raw bytes of the MAC address of the client (STA) represented by this object. + * The MAC address of the client (STA) represented by this object. The MAC address may be null + * in case of an error. */ - @NonNull public final byte[] macAddress; + @Nullable public MacAddress getMacAddress() { + return mMacAddress; + } /** * Construct a native Wi-Fi client. */ - public NativeWifiClient(@NonNull byte[] macAddress) { - this.macAddress = macAddress; + public NativeWifiClient(@Nullable MacAddress macAddress) { + this.mMacAddress = macAddress; } /** override comparator */ @@ -50,13 +57,13 @@ public final class NativeWifiClient implements Parcelable { return false; } NativeWifiClient other = (NativeWifiClient) rhs; - return Arrays.equals(macAddress, other.macAddress); + return Objects.equals(mMacAddress, other.mMacAddress); } /** override hash code */ @Override public int hashCode() { - return Arrays.hashCode(macAddress); + return mMacAddress.hashCode(); } /** implement Parcelable interface */ @@ -71,7 +78,7 @@ public final class NativeWifiClient implements Parcelable { */ @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeByteArray(macAddress); + out.writeByteArray(mMacAddress.toByteArray()); } /** implement Parcelable interface */ @@ -79,9 +86,11 @@ public final class NativeWifiClient implements Parcelable { new Parcelable.Creator<NativeWifiClient>() { @Override public NativeWifiClient createFromParcel(Parcel in) { - byte[] macAddress = in.createByteArray(); - if (macAddress == null) { - macAddress = new byte[0]; + MacAddress macAddress; + try { + macAddress = MacAddress.fromBytes(in.createByteArray()); + } catch (IllegalArgumentException e) { + macAddress = null; } return new NativeWifiClient(macAddress); } diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java index 7a31a5afab05..61f18e0b7191 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java @@ -41,19 +41,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; /** - * This class encapsulates the interface the wificond (Wi-Fi Conductor) daemon presents to the - * Wi-Fi framework. The interface is only for use by the Wi-Fi framework and access is protected - * by SELinux permissions: only the system server and wpa_supplicant can use WifiCondManager. + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The + * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. * * @hide */ @@ -371,7 +368,7 @@ public class WifiCondManager { public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { if (mVerboseLoggingEnabled) { Log.d(TAG, "onConnectedClientsChanged called with " - + client.macAddress + " isConnected: " + isConnected); + + client.getMacAddress() + " isConnected: " + isConnected); } Binder.clearCallingIdentity(); @@ -1046,13 +1043,13 @@ public class WifiCondManager { * WifiScanner.WIFI_BAND_5_GHZ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY * WifiScanner.WIFI_BAND_6_GHZ - * @return frequencies List of valid frequencies (MHz), or an empty list for error. + * @return frequencies vector of valid frequencies (MHz), or an empty array for error. * @throws IllegalArgumentException if band is not recognized. */ - public @NonNull List<Integer> getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { + public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { if (mWificond == null) { Log.e(TAG, "No valid wificond scanner interface handler"); - return Collections.emptyList(); + return new int[0]; } int[] result = null; try { @@ -1076,9 +1073,9 @@ public class WifiCondManager { Log.e(TAG, "Failed to request getChannelsForBand due to remote exception"); } if (result == null) { - return Collections.emptyList(); + result = new int[0]; } - return Arrays.stream(result).boxed().collect(Collectors.toList()); + return result; } /** Helper function to look up the interface handle using name */ diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 847040ca914a..853212aafcdf 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -2391,4 +2391,14 @@ public class WifiManagerTest { assertFalse(mWifiManager.isScanThrottleEnabled()); verify(mWifiService).isScanThrottleEnabled(); } + + @Test + public void testAutoWakeup() throws Exception { + mWifiManager.setAutoWakeupEnabled(true); + verify(mWifiService).setAutoWakeupEnabled(true); + + when(mWifiService.isAutoWakeupEnabled()).thenReturn(false); + assertFalse(mWifiManager.isAutoWakeupEnabled()); + verify(mWifiService).isAutoWakeupEnabled(); + } } diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java index 32105be6ae4c..b745a341b459 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.app.test.TestAlarmManager; import android.content.Context; +import android.net.MacAddress; import android.net.wifi.ScanResult; import android.net.wifi.SoftApInfo; import android.net.wifi.WifiConfiguration; @@ -65,8 +66,6 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -121,7 +120,8 @@ public class WifiCondManagerTest { private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\""; private static final int[] TEST_FREQUENCIES_1 = {}; private static final int[] TEST_FREQUENCIES_2 = {2500, 5124}; - private static final byte[] TEST_RAW_MAC_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes( + new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}); private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = new ArrayList<byte[]>() {{ @@ -742,43 +742,11 @@ public class WifiCondManagerTest { verify(deathHandler).run(); // The handles should be cleared after death. - assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).size()); + assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length); verify(mWificond, never()).getAvailable5gNonDFSChannels(); } /** - * Verify primitive array to list translation of channel API. - */ - @Test - public void testGetChannels() throws Exception { - int[] resultsEmpty = new int[0]; - int[] resultsSingle = new int[]{100}; - int[] resultsMore = new int[]{100, 200}; - - List<Integer> emptyList = Collections.emptyList(); - List<Integer> singleList = Arrays.asList(100); - List<Integer> moreList = Arrays.asList(100, 200); - - when(mWificond.getAvailable2gChannels()).thenReturn(null); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - emptyList); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ), - emptyList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsEmpty); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - emptyList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsSingle); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - singleList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsMore); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - moreList); - } - - /** * sendMgmtFrame() should fail if a null callback is passed in. */ @Test |